forked from snapcore/snapd
/
is_connected.go
181 lines (154 loc) · 5.75 KB
/
is_connected.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package ctlcmd
import (
"fmt"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/overlord/ifacestate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/sandbox/apparmor"
"github.com/snapcore/snapd/sandbox/cgroup"
"github.com/snapcore/snapd/snap"
)
var cgroupSnapNameFromPid = cgroup.SnapNameFromPid
const (
classicSnapCode = 10
notASnapCode = 11
)
type isConnectedCommand struct {
baseCommand
Positional struct {
PlugOrSlotSpec string `positional-args:"true" positional-arg-name:"<plug|slot>"`
} `positional-args:"true" required:"true"`
Pid int `long:"pid" description:"Process ID for a plausibly connected process"`
AppArmorLabel string `long:"apparmor-label" description:"AppArmor label for a plausibly connected process"`
}
var shortIsConnectedHelp = i18n.G(`Return success if the given plug or slot is connected, and failure otherwise`)
var longIsConnectedHelp = i18n.G(`
The is-connected command returns success if the given plug or slot of the
calling snap is connected, and failure otherwise.
$ snapctl is-connected plug
$ echo $?
1
Snaps can only query their own plugs and slots - snap name is implicit and
implied by the snapctl execution context.
The --pid and --aparmor-label options can be used to determine whether
a plug or slot is connected to the snap identified by the given
process ID or AppArmor label. In this mode, additional failure exit
codes may be returned: 10 if the other snap is not connected but uses
classic confinement, or 11 if the other process is not snap confined.
The --pid and --apparmor-label options may only be used with slots of
interface type "pulseaudio", "audio-record", or "cups-control".
`)
func init() {
addCommand("is-connected", shortIsConnectedHelp, longIsConnectedHelp, func() command {
return &isConnectedCommand{}
})
}
func isConnectedPidCheckAllowed(info *snap.Info, plugOrSlot string) bool {
slot := info.Slots[plugOrSlot]
if slot != nil {
switch slot.Interface {
case "pulseaudio", "audio-record", "cups-control":
return true
}
}
return false
}
func (c *isConnectedCommand) Execute(args []string) error {
plugOrSlot := c.Positional.PlugOrSlotSpec
context := c.context()
if context == nil {
return fmt.Errorf("cannot check connection status without a context")
}
snapName := context.InstanceName()
st := context.State()
st.Lock()
defer st.Unlock()
info, err := snapstate.CurrentInfo(st, snapName)
if err != nil {
return fmt.Errorf("internal error: cannot get snap info: %s", err)
}
// XXX: This will fail for implicit slots. In practice, this
// would only affect calls that used the "core" snap as
// context. That snap does not have any hooks using
// is-connected, so the limitation is probably moot.
if info.Plugs[plugOrSlot] == nil && info.Slots[plugOrSlot] == nil {
return fmt.Errorf("snap %q has no plug or slot named %q", snapName, plugOrSlot)
}
conns, err := ifacestate.ConnectionStates(st)
if err != nil {
return fmt.Errorf("internal error: cannot get connections: %s", err)
}
var otherSnap *snap.Info
if c.AppArmorLabel != "" {
if !isConnectedPidCheckAllowed(info, plugOrSlot) {
return fmt.Errorf("cannot use --apparmor-label check with %s:%s", snapName, plugOrSlot)
}
name, _, _, err := apparmor.DecodeLabel(c.AppArmorLabel)
if err != nil {
return &UnsuccessfulError{ExitCode: notASnapCode}
}
otherSnap, err = snapstate.CurrentInfo(st, name)
if err != nil {
return fmt.Errorf("internal error: cannot get snap info for AppArmor label %q: %s", c.AppArmorLabel, err)
}
} else if c.Pid != 0 {
if !isConnectedPidCheckAllowed(info, plugOrSlot) {
return fmt.Errorf("cannot use --pid check with %s:%s", snapName, plugOrSlot)
}
name, err := cgroupSnapNameFromPid(c.Pid)
if err != nil {
// Indicate that this pid is not a snap
return &UnsuccessfulError{ExitCode: notASnapCode}
}
otherSnap, err = snapstate.CurrentInfo(st, name)
if err != nil {
return fmt.Errorf("internal error: cannot get snap info for pid %d: %s", c.Pid, err)
}
}
// snapName is the name of the snap executing snapctl command, it's
// obtained from the context (ephemeral if run by apps, or full if run by
// hooks). plug and slot names are unique within a snap, so there is no
// ambiguity when matching.
for refStr, connState := range conns {
if connState.Undesired || connState.HotplugGone {
continue
}
connRef, err := interfaces.ParseConnRef(refStr)
if err != nil {
return fmt.Errorf("internal error: %s", err)
}
matchingPlug := connRef.PlugRef.Snap == snapName && connRef.PlugRef.Name == plugOrSlot
matchingSlot := connRef.SlotRef.Snap == snapName && connRef.SlotRef.Name == plugOrSlot
if otherSnap != nil {
if matchingPlug && connRef.SlotRef.Snap == otherSnap.InstanceName() || matchingSlot && connRef.PlugRef.Snap == otherSnap.InstanceName() {
return nil
}
} else {
if matchingPlug || matchingSlot {
return nil
}
}
}
if otherSnap != nil && otherSnap.Confinement == snap.ClassicConfinement {
return &UnsuccessfulError{ExitCode: classicSnapCode}
}
return &UnsuccessfulError{ExitCode: 1}
}