github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_connections.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package daemon 21 22 import ( 23 "net/http" 24 "sort" 25 26 "github.com/snapcore/snapd/interfaces" 27 "github.com/snapcore/snapd/overlord/auth" 28 "github.com/snapcore/snapd/overlord/ifacestate" 29 "github.com/snapcore/snapd/overlord/snapstate" 30 "github.com/snapcore/snapd/overlord/state" 31 ) 32 33 var connectionsCmd = &Command{ 34 Path: "/v2/connections", 35 UserOK: true, 36 GET: getConnections, 37 } 38 39 type collectFilter struct { 40 snapName string 41 ifaceName string 42 connected bool 43 } 44 45 func (c *collectFilter) plugOrConnectedSlotMatches(plug *interfaces.PlugRef, connectedSlots []interfaces.SlotRef) bool { 46 for _, slot := range connectedSlots { 47 if c.slotOrConnectedPlugMatches(&slot, nil) { 48 return true 49 } 50 } 51 if c.snapName != "" && plug.Snap != c.snapName { 52 return false 53 } 54 return true 55 } 56 57 func (c *collectFilter) slotOrConnectedPlugMatches(slot *interfaces.SlotRef, connectedPlugs []interfaces.PlugRef) bool { 58 for _, plug := range connectedPlugs { 59 if c.plugOrConnectedSlotMatches(&plug, nil) { 60 return true 61 } 62 } 63 if c.snapName != "" && slot.Snap != c.snapName { 64 return false 65 } 66 return true 67 } 68 69 func (c *collectFilter) ifaceMatches(ifaceName string) bool { 70 if c.ifaceName != "" && c.ifaceName != ifaceName { 71 return false 72 } 73 return true 74 } 75 76 type bySlotRef []interfaces.SlotRef 77 78 func (b bySlotRef) Len() int { return len(b) } 79 func (b bySlotRef) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 80 func (b bySlotRef) Less(i, j int) bool { 81 return b[i].SortsBefore(b[j]) 82 } 83 84 type byPlugRef []interfaces.PlugRef 85 86 func (b byPlugRef) Len() int { return len(b) } 87 func (b byPlugRef) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 88 func (b byPlugRef) Less(i, j int) bool { 89 return b[i].SortsBefore(b[j]) 90 } 91 92 // mergeAttrs merges attributes from 2 disjoint sets of static and dynamic slot or 93 // plug attributes into a single map. 94 func mergeAttrs(one map[string]interface{}, other map[string]interface{}) map[string]interface{} { 95 merged := make(map[string]interface{}, len(one)+len(other)) 96 for k, v := range one { 97 merged[k] = v 98 } 99 for k, v := range other { 100 merged[k] = v 101 } 102 return merged 103 } 104 105 func collectConnections(ifaceMgr *ifacestate.InterfaceManager, filter collectFilter) (*connectionsJSON, error) { 106 repo := ifaceMgr.Repository() 107 ifaces := repo.Interfaces() 108 109 var connsjson connectionsJSON 110 var connStates map[string]ifacestate.ConnectionState 111 plugConns := map[string][]interfaces.SlotRef{} 112 slotConns := map[string][]interfaces.PlugRef{} 113 114 var err error 115 connStates, err = ifaceMgr.ConnectionStates() 116 if err != nil { 117 return nil, err 118 } 119 120 connsjson.Established = make([]connectionJSON, 0, len(connStates)) 121 connsjson.Plugs = make([]*plugJSON, 0, len(ifaces.Plugs)) 122 connsjson.Slots = make([]*slotJSON, 0, len(ifaces.Slots)) 123 124 for crefStr, cstate := range connStates { 125 if cstate.Undesired && filter.connected { 126 continue 127 } 128 if cstate.HotplugGone { 129 // XXX: hotplug connection - the device and slot are gone 130 continue 131 } 132 133 cref, err := interfaces.ParseConnRef(crefStr) 134 if err != nil { 135 return nil, err 136 } 137 138 // plug or slot not in the repository, e.g. cref is referring to an 139 // inactive revision of the snap; this can happen when the new revision 140 // doesn't have given plug/slot anymore (but the connection state is 141 // kept in case of revert). 142 // XXX: if we decide to show such connections with special tags, then 143 // this needs to be tweaked together with collectFilter definition and 144 // connectionJSON output. 145 if repo.Plug(cref.PlugRef.Snap, cref.PlugRef.Name) == nil || repo.Slot(cref.SlotRef.Snap, cref.SlotRef.Name) == nil { 146 continue 147 } 148 149 if !filter.plugOrConnectedSlotMatches(&cref.PlugRef, nil) && !filter.slotOrConnectedPlugMatches(&cref.SlotRef, nil) { 150 continue 151 } 152 if !filter.ifaceMatches(cstate.Interface) { 153 continue 154 } 155 plugRef := interfaces.PlugRef{Snap: cref.PlugRef.Snap, Name: cref.PlugRef.Name} 156 slotRef := interfaces.SlotRef{Snap: cref.SlotRef.Snap, Name: cref.SlotRef.Name} 157 plugID := plugRef.String() 158 slotID := slotRef.String() 159 160 cj := connectionJSON{ 161 Slot: slotRef, 162 Plug: plugRef, 163 Manual: cstate.Auto == false, 164 Gadget: cstate.ByGadget, 165 Interface: cstate.Interface, 166 PlugAttrs: mergeAttrs(cstate.StaticPlugAttrs, cstate.DynamicPlugAttrs), 167 SlotAttrs: mergeAttrs(cstate.StaticSlotAttrs, cstate.DynamicSlotAttrs), 168 } 169 if cstate.Undesired { 170 // explicitly disconnected are always manual 171 cj.Manual = true 172 connsjson.Undesired = append(connsjson.Undesired, cj) 173 } else { 174 plugConns[plugID] = append(plugConns[plugID], slotRef) 175 slotConns[slotID] = append(slotConns[slotID], plugRef) 176 177 connsjson.Established = append(connsjson.Established, cj) 178 } 179 } 180 181 for _, plug := range ifaces.Plugs { 182 plugRef := interfaces.PlugRef{Snap: plug.Snap.InstanceName(), Name: plug.Name} 183 connectedSlots, connected := plugConns[plugRef.String()] 184 if !connected && filter.connected { 185 continue 186 } 187 if !filter.ifaceMatches(plug.Interface) || !filter.plugOrConnectedSlotMatches(&plugRef, connectedSlots) { 188 continue 189 } 190 var apps []string 191 for _, app := range plug.Apps { 192 apps = append(apps, app.Name) 193 } 194 sort.Sort(bySlotRef(connectedSlots)) 195 pj := &plugJSON{ 196 Snap: plugRef.Snap, 197 Name: plugRef.Name, 198 Interface: plug.Interface, 199 Attrs: plug.Attrs, 200 Apps: apps, 201 Label: plug.Label, 202 Connections: connectedSlots, 203 } 204 connsjson.Plugs = append(connsjson.Plugs, pj) 205 } 206 for _, slot := range ifaces.Slots { 207 slotRef := interfaces.SlotRef{Snap: slot.Snap.InstanceName(), Name: slot.Name} 208 connectedPlugs, connected := slotConns[slotRef.String()] 209 if !connected && filter.connected { 210 continue 211 } 212 if !filter.ifaceMatches(slot.Interface) || !filter.slotOrConnectedPlugMatches(&slotRef, connectedPlugs) { 213 continue 214 } 215 var apps []string 216 for _, app := range slot.Apps { 217 apps = append(apps, app.Name) 218 } 219 sort.Sort(byPlugRef(connectedPlugs)) 220 sj := &slotJSON{ 221 Snap: slotRef.Snap, 222 Name: slotRef.Name, 223 Interface: slot.Interface, 224 Attrs: slot.Attrs, 225 Apps: apps, 226 Label: slot.Label, 227 Connections: connectedPlugs, 228 } 229 connsjson.Slots = append(connsjson.Slots, sj) 230 } 231 return &connsjson, nil 232 } 233 234 type byCrefConnJSON []connectionJSON 235 236 func (b byCrefConnJSON) Len() int { return len(b) } 237 func (b byCrefConnJSON) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 238 func (b byCrefConnJSON) Less(i, j int) bool { 239 icj := b[i] 240 jcj := b[j] 241 iCref := interfaces.ConnRef{PlugRef: icj.Plug, SlotRef: icj.Slot} 242 jCref := interfaces.ConnRef{PlugRef: jcj.Plug, SlotRef: jcj.Slot} 243 sortsBefore := iCref.SortsBefore(&jCref) 244 return sortsBefore 245 } 246 247 func checkSnapInstalled(st *state.State, name string) error { 248 st.Lock() 249 defer st.Unlock() 250 251 var snapst snapstate.SnapState 252 return snapstate.Get(st, name, &snapst) 253 } 254 255 func getConnections(c *Command, r *http.Request, user *auth.UserState) Response { 256 query := r.URL.Query() 257 snapName := query.Get("snap") 258 ifaceName := query.Get("interface") 259 qselect := query.Get("select") 260 if qselect != "all" && qselect != "" { 261 return BadRequest("unsupported select qualifier") 262 } 263 onlyConnected := qselect == "" 264 265 snapName = ifacestate.RemapSnapFromRequest(snapName) 266 if snapName != "" { 267 if err := checkSnapInstalled(c.d.overlord.State(), snapName); err != nil { 268 if err == state.ErrNoState { 269 return SnapNotFound(snapName, err) 270 } 271 return InternalError("cannot access snap state: %v", err) 272 } 273 } 274 275 connsjson, err := collectConnections(c.d.overlord.InterfaceManager(), collectFilter{ 276 snapName: snapName, 277 ifaceName: ifaceName, 278 connected: onlyConnected, 279 }) 280 if err != nil { 281 return InternalError("collecting connection information failed: %v", err) 282 } 283 sort.Sort(byCrefConnJSON(connsjson.Established)) 284 sort.Sort(byCrefConnJSON(connsjson.Undesired)) 285 286 return SyncResponse(connsjson, nil) 287 }