github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/interfaces/core.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 interfaces
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/snap"
    28  )
    29  
    30  // BeforePreparePlug sanitizes a plug with a given snapd interface.
    31  func BeforePreparePlug(iface Interface, plugInfo *snap.PlugInfo) error {
    32  	if iface.Name() != plugInfo.Interface {
    33  		return fmt.Errorf("cannot sanitize plug %q (interface %q) using interface %q",
    34  			PlugRef{Snap: plugInfo.Snap.InstanceName(), Name: plugInfo.Name}, plugInfo.Interface, iface.Name())
    35  	}
    36  	var err error
    37  	if iface, ok := iface.(PlugSanitizer); ok {
    38  		err = iface.BeforePreparePlug(plugInfo)
    39  	}
    40  	return err
    41  }
    42  
    43  // ByName returns an Interface for the given interface name. Note that in order for
    44  // this to work properly, the package "interfaces/builtin" must also eventually be
    45  // imported to populate the full list of interfaces.
    46  var ByName = func(name string) (iface Interface, err error) {
    47  	panic("ByName is unset, import interfaces/builtin to initialize this")
    48  }
    49  
    50  // PlugRef is a reference to a plug.
    51  type PlugRef struct {
    52  	Snap string `json:"snap"`
    53  	Name string `json:"plug"`
    54  }
    55  
    56  // String returns the "snap:plug" representation of a plug reference.
    57  func (ref PlugRef) String() string {
    58  	return fmt.Sprintf("%s:%s", ref.Snap, ref.Name)
    59  }
    60  
    61  // SortsBefore returns true when plug should be sorted before the other
    62  func (ref PlugRef) SortsBefore(other PlugRef) bool {
    63  	if ref.Snap != other.Snap {
    64  		return ref.Snap < other.Snap
    65  	}
    66  	return ref.Name < other.Name
    67  }
    68  
    69  // Sanitize slot with a given snapd interface.
    70  func BeforePrepareSlot(iface Interface, slotInfo *snap.SlotInfo) error {
    71  	if iface.Name() != slotInfo.Interface {
    72  		return fmt.Errorf("cannot sanitize slot %q (interface %q) using interface %q",
    73  			SlotRef{Snap: slotInfo.Snap.InstanceName(), Name: slotInfo.Name}, slotInfo.Interface, iface.Name())
    74  	}
    75  	var err error
    76  	if iface, ok := iface.(SlotSanitizer); ok {
    77  		err = iface.BeforePrepareSlot(slotInfo)
    78  	}
    79  	return err
    80  }
    81  
    82  // SlotRef is a reference to a slot.
    83  type SlotRef struct {
    84  	Snap string `json:"snap"`
    85  	Name string `json:"slot"`
    86  }
    87  
    88  // String returns the "snap:slot" representation of a slot reference.
    89  func (ref SlotRef) String() string {
    90  	return fmt.Sprintf("%s:%s", ref.Snap, ref.Name)
    91  }
    92  
    93  // SortsBefore returns true when slot should be sorted before the other
    94  func (ref SlotRef) SortsBefore(other SlotRef) bool {
    95  	if ref.Snap != other.Snap {
    96  		return ref.Snap < other.Snap
    97  	}
    98  	return ref.Name < other.Name
    99  }
   100  
   101  // Interfaces holds information about a list of plugs, slots and their connections.
   102  type Interfaces struct {
   103  	Plugs       []*snap.PlugInfo
   104  	Slots       []*snap.SlotInfo
   105  	Connections []*ConnRef
   106  }
   107  
   108  // Info holds information about a given interface and its instances.
   109  type Info struct {
   110  	Name    string
   111  	Summary string
   112  	DocURL  string
   113  	Plugs   []*snap.PlugInfo
   114  	Slots   []*snap.SlotInfo
   115  }
   116  
   117  // ConnRef holds information about plug and slot reference that form a particular connection.
   118  type ConnRef struct {
   119  	PlugRef PlugRef
   120  	SlotRef SlotRef
   121  }
   122  
   123  // NewConnRef creates a connection reference for given plug and slot
   124  func NewConnRef(plug *snap.PlugInfo, slot *snap.SlotInfo) *ConnRef {
   125  	return &ConnRef{
   126  		PlugRef: PlugRef{Snap: plug.Snap.InstanceName(), Name: plug.Name},
   127  		SlotRef: SlotRef{Snap: slot.Snap.InstanceName(), Name: slot.Name},
   128  	}
   129  }
   130  
   131  // ID returns a string identifying a given connection.
   132  func (conn *ConnRef) ID() string {
   133  	return fmt.Sprintf("%s:%s %s:%s", conn.PlugRef.Snap, conn.PlugRef.Name, conn.SlotRef.Snap, conn.SlotRef.Name)
   134  }
   135  
   136  // SortsBefore returns true when connection should be sorted before the other
   137  func (conn *ConnRef) SortsBefore(other *ConnRef) bool {
   138  	if conn.PlugRef != other.PlugRef {
   139  		return conn.PlugRef.SortsBefore(other.PlugRef)
   140  	}
   141  	return conn.SlotRef.SortsBefore(other.SlotRef)
   142  }
   143  
   144  // ParseConnRef parses an ID string
   145  func ParseConnRef(id string) (*ConnRef, error) {
   146  	var conn ConnRef
   147  	parts := strings.SplitN(id, " ", 2)
   148  	if len(parts) != 2 {
   149  		return nil, fmt.Errorf("malformed connection identifier: %q", id)
   150  	}
   151  	plugParts := strings.Split(parts[0], ":")
   152  	slotParts := strings.Split(parts[1], ":")
   153  	if len(plugParts) != 2 || len(slotParts) != 2 {
   154  		return nil, fmt.Errorf("malformed connection identifier: %q", id)
   155  	}
   156  	conn.PlugRef.Snap = plugParts[0]
   157  	conn.PlugRef.Name = plugParts[1]
   158  	conn.SlotRef.Snap = slotParts[0]
   159  	conn.SlotRef.Name = slotParts[1]
   160  	return &conn, nil
   161  }
   162  
   163  // Interface describes a group of interchangeable capabilities with common features.
   164  // Interfaces act as a contract between system builders, application developers
   165  // and end users.
   166  type Interface interface {
   167  	// Unique and public name of this interface.
   168  	Name() string
   169  
   170  	// AutoConnect returns whether plug and slot should be
   171  	// implicitly auto-connected assuming they will be an
   172  	// unambiguous connection candidate and declaration-based checks
   173  	// allow.
   174  	AutoConnect(plug *snap.PlugInfo, slot *snap.SlotInfo) bool
   175  }
   176  
   177  // PlugSanitizer can be implemented by Interfaces that have reasons to sanitize their plugs.
   178  type PlugSanitizer interface {
   179  	BeforePreparePlug(plug *snap.PlugInfo) error
   180  }
   181  
   182  // SlotSanitizer can be implemented by Interfaces that have reasons to sanitize their slots.
   183  type SlotSanitizer interface {
   184  	BeforePrepareSlot(slot *snap.SlotInfo) error
   185  }
   186  
   187  // StaticInfo describes various static-info of a given interface.
   188  //
   189  // The Summary must be a one-line string of length suitable for listing views.
   190  // The DocsURL can point to website (e.g. a forum thread) that goes into more
   191  // depth and documents the interface in detail.
   192  type StaticInfo struct {
   193  	Summary string `json:"summary,omitempty"`
   194  	DocURL  string `json:"doc-url,omitempty"`
   195  
   196  	// ImplicitOnCore controls if a slot is automatically added to core (non-classic) systems.
   197  	ImplicitOnCore bool `json:"implicit-on-core,omitempty"`
   198  	// ImplicitOnClassic controls if a slot is automatically added to classic systems.
   199  	ImplicitOnClassic bool `json:"implicit-on-classic,omitempty"`
   200  
   201  	// BaseDeclarationPlugs defines an optional extension to the base-declaration assertion relevant for this interface.
   202  	BaseDeclarationPlugs string
   203  	// BaseDeclarationSlots defines an optional extension to the base-declaration assertion relevant for this interface.
   204  	BaseDeclarationSlots string
   205  }
   206  
   207  // PermanentPlugServiceSnippets will return the set of snippets for the systemd
   208  // service unit that should be generated for a snap with the specified plug.
   209  // The list returned is not unique, callers must de-duplicate themselves.
   210  // The plug is provided because the snippet may depend on plug attributes for
   211  // example. The plug is sanitized before the snippets are returned.
   212  func PermanentPlugServiceSnippets(iface Interface, plug *snap.PlugInfo) (snips []string, err error) {
   213  	// sanitize the plug first
   214  	err = BeforePreparePlug(iface, plug)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	type serviceSnippetPlugger interface {
   220  		ServicePermanentPlug(plug *snap.PlugInfo) []string
   221  	}
   222  	if iface, ok := iface.(serviceSnippetPlugger); ok {
   223  		snips = iface.ServicePermanentPlug(plug)
   224  	}
   225  	return snips, nil
   226  }
   227  
   228  // StaticInfoOf returns the static-info of the given interface.
   229  func StaticInfoOf(iface Interface) (si StaticInfo) {
   230  	type metaDataProvider interface {
   231  		StaticInfo() StaticInfo
   232  	}
   233  	if iface, ok := iface.(metaDataProvider); ok {
   234  		si = iface.StaticInfo()
   235  	}
   236  	return si
   237  }
   238  
   239  // Specification describes interactions between backends and interfaces.
   240  type Specification interface {
   241  	// AddPermanentSlot records side-effects of having a slot.
   242  	AddPermanentSlot(iface Interface, slot *snap.SlotInfo) error
   243  	// AddPermanentPlug records side-effects of having a plug.
   244  	AddPermanentPlug(iface Interface, plug *snap.PlugInfo) error
   245  	// AddConnectedSlot records side-effects of having a connected slot.
   246  	AddConnectedSlot(iface Interface, plug *ConnectedPlug, slot *ConnectedSlot) error
   247  	// AddConnectedPlug records side-effects of having a connected plug.
   248  	AddConnectedPlug(iface Interface, plug *ConnectedPlug, slot *ConnectedSlot) error
   249  }
   250  
   251  // SecuritySystem is a name of a security system.
   252  type SecuritySystem string
   253  
   254  const (
   255  	// SecurityAppArmor identifies the apparmor security system.
   256  	SecurityAppArmor SecuritySystem = "apparmor"
   257  	// SecuritySecComp identifies the seccomp security system.
   258  	SecuritySecComp SecuritySystem = "seccomp"
   259  	// SecurityDBus identifies the DBus security system.
   260  	SecurityDBus SecuritySystem = "dbus"
   261  	// SecurityUDev identifies the UDev security system.
   262  	SecurityUDev SecuritySystem = "udev"
   263  	// SecurityMount identifies the mount security system.
   264  	SecurityMount SecuritySystem = "mount"
   265  	// SecurityKMod identifies the kernel modules security system.
   266  	SecurityKMod SecuritySystem = "kmod"
   267  	// SecuritySystemd identifies the systemd services security system.
   268  	SecuritySystemd SecuritySystem = "systemd"
   269  )
   270  
   271  var isValidBusName = regexp.MustCompile(`^[a-zA-Z_-][a-zA-Z0-9_-]*(\.[a-zA-Z_-][a-zA-Z0-9_-]*)+$`).MatchString
   272  
   273  // ValidateDBusBusName checks if a string conforms to
   274  // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names
   275  func ValidateDBusBusName(busName string) error {
   276  	if len(busName) == 0 {
   277  		return fmt.Errorf("DBus bus name must be set")
   278  	} else if len(busName) > 255 {
   279  		return fmt.Errorf("DBus bus name is too long (must be <= 255)")
   280  	}
   281  
   282  	if !isValidBusName(busName) {
   283  		return fmt.Errorf("invalid DBus bus name: %q", busName)
   284  	}
   285  	return nil
   286  }