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  }