github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 if !filter.plugOrConnectedSlotMatches(&cref.PlugRef, nil) && !filter.slotOrConnectedPlugMatches(&cref.SlotRef, nil) { 138 continue 139 } 140 if !filter.ifaceMatches(cstate.Interface) { 141 continue 142 } 143 plugRef := interfaces.PlugRef{Snap: cref.PlugRef.Snap, Name: cref.PlugRef.Name} 144 slotRef := interfaces.SlotRef{Snap: cref.SlotRef.Snap, Name: cref.SlotRef.Name} 145 plugID := plugRef.String() 146 slotID := slotRef.String() 147 148 cj := connectionJSON{ 149 Slot: slotRef, 150 Plug: plugRef, 151 Manual: cstate.Auto == false, 152 Gadget: cstate.ByGadget, 153 Interface: cstate.Interface, 154 PlugAttrs: mergeAttrs(cstate.StaticPlugAttrs, cstate.DynamicPlugAttrs), 155 SlotAttrs: mergeAttrs(cstate.StaticSlotAttrs, cstate.DynamicSlotAttrs), 156 } 157 if cstate.Undesired { 158 // explicitly disconnected are always manual 159 cj.Manual = true 160 connsjson.Undesired = append(connsjson.Undesired, cj) 161 } else { 162 plugConns[plugID] = append(plugConns[plugID], slotRef) 163 slotConns[slotID] = append(slotConns[slotID], plugRef) 164 165 connsjson.Established = append(connsjson.Established, cj) 166 } 167 } 168 169 for _, plug := range ifaces.Plugs { 170 plugRef := interfaces.PlugRef{Snap: plug.Snap.InstanceName(), Name: plug.Name} 171 connectedSlots, connected := plugConns[plugRef.String()] 172 if !connected && filter.connected { 173 continue 174 } 175 if !filter.ifaceMatches(plug.Interface) || !filter.plugOrConnectedSlotMatches(&plugRef, connectedSlots) { 176 continue 177 } 178 var apps []string 179 for _, app := range plug.Apps { 180 apps = append(apps, app.Name) 181 } 182 sort.Sort(bySlotRef(connectedSlots)) 183 pj := &plugJSON{ 184 Snap: plugRef.Snap, 185 Name: plugRef.Name, 186 Interface: plug.Interface, 187 Attrs: plug.Attrs, 188 Apps: apps, 189 Label: plug.Label, 190 Connections: connectedSlots, 191 } 192 connsjson.Plugs = append(connsjson.Plugs, pj) 193 } 194 for _, slot := range ifaces.Slots { 195 slotRef := interfaces.SlotRef{Snap: slot.Snap.InstanceName(), Name: slot.Name} 196 connectedPlugs, connected := slotConns[slotRef.String()] 197 if !connected && filter.connected { 198 continue 199 } 200 if !filter.ifaceMatches(slot.Interface) || !filter.slotOrConnectedPlugMatches(&slotRef, connectedPlugs) { 201 continue 202 } 203 var apps []string 204 for _, app := range slot.Apps { 205 apps = append(apps, app.Name) 206 } 207 sort.Sort(byPlugRef(connectedPlugs)) 208 sj := &slotJSON{ 209 Snap: slotRef.Snap, 210 Name: slotRef.Name, 211 Interface: slot.Interface, 212 Attrs: slot.Attrs, 213 Apps: apps, 214 Label: slot.Label, 215 Connections: connectedPlugs, 216 } 217 connsjson.Slots = append(connsjson.Slots, sj) 218 } 219 return &connsjson, nil 220 } 221 222 type byCrefConnJSON []connectionJSON 223 224 func (b byCrefConnJSON) Len() int { return len(b) } 225 func (b byCrefConnJSON) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 226 func (b byCrefConnJSON) Less(i, j int) bool { 227 icj := b[i] 228 jcj := b[j] 229 iCref := interfaces.ConnRef{PlugRef: icj.Plug, SlotRef: icj.Slot} 230 jCref := interfaces.ConnRef{PlugRef: jcj.Plug, SlotRef: jcj.Slot} 231 sortsBefore := iCref.SortsBefore(&jCref) 232 return sortsBefore 233 } 234 235 func checkSnapInstalled(st *state.State, name string) error { 236 st.Lock() 237 defer st.Unlock() 238 239 var snapst snapstate.SnapState 240 return snapstate.Get(st, name, &snapst) 241 } 242 243 func getConnections(c *Command, r *http.Request, user *auth.UserState) Response { 244 query := r.URL.Query() 245 snapName := query.Get("snap") 246 ifaceName := query.Get("interface") 247 qselect := query.Get("select") 248 if qselect != "all" && qselect != "" { 249 return BadRequest("unsupported select qualifier") 250 } 251 onlyConnected := qselect == "" 252 253 snapName = ifacestate.RemapSnapFromRequest(snapName) 254 if snapName != "" { 255 if err := checkSnapInstalled(c.d.overlord.State(), snapName); err != nil { 256 if err == state.ErrNoState { 257 return SnapNotFound(snapName, err) 258 } 259 return InternalError("cannot access snap state: %v", err) 260 } 261 } 262 263 connsjson, err := collectConnections(c.d.overlord.InterfaceManager(), collectFilter{ 264 snapName: snapName, 265 ifaceName: ifaceName, 266 connected: onlyConnected, 267 }) 268 if err != nil { 269 return InternalError("collecting connection information failed: %v", err) 270 } 271 sort.Sort(byCrefConnJSON(connsjson.Established)) 272 sort.Sort(byCrefConnJSON(connsjson.Undesired)) 273 274 return SyncResponse(connsjson, nil) 275 }