github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/interfaces/udev/spec.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017-2018 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 udev
    21  
    22  import (
    23  	"fmt"
    24  	"sort"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/dirs"
    28  	"github.com/snapcore/snapd/interfaces"
    29  	"github.com/snapcore/snapd/snap"
    30  	"github.com/snapcore/snapd/strutil"
    31  )
    32  
    33  type entry struct {
    34  	snippet string
    35  	iface   string
    36  	tag     string
    37  }
    38  
    39  // Specification assists in collecting udev snippets associated with an interface.
    40  type Specification struct {
    41  	// Snippets are stored in a map for de-duplication
    42  	snippets map[string]bool
    43  	entries  []entry
    44  	iface    string
    45  
    46  	securityTags             []string
    47  	udevadmSubsystemTriggers []string
    48  	controlsDeviceCgroup     bool
    49  }
    50  
    51  // SetControlsDeviceCgroup marks a specification as needing to control
    52  // its own device cgroup which prevents generation of any udev tagging rules
    53  // for this snap name
    54  func (spec *Specification) SetControlsDeviceCgroup() {
    55  	spec.controlsDeviceCgroup = true
    56  }
    57  
    58  // ControlsDeviceCgroup returns whether a specification was marked as needing to
    59  // control its own device cgroup which prevents generation of any udev tagging
    60  // rules for this snap name.
    61  func (spec *Specification) ControlsDeviceCgroup() bool {
    62  	return spec.controlsDeviceCgroup
    63  }
    64  
    65  func (spec *Specification) addEntry(snippet, tag string) {
    66  	if spec.snippets == nil {
    67  		spec.snippets = make(map[string]bool)
    68  	}
    69  	if !spec.snippets[snippet] {
    70  		spec.snippets[snippet] = true
    71  		e := entry{
    72  			snippet: snippet,
    73  			iface:   spec.iface,
    74  			tag:     tag,
    75  		}
    76  		spec.entries = append(spec.entries, e)
    77  	}
    78  }
    79  
    80  // AddSnippet adds a new udev snippet.
    81  func (spec *Specification) AddSnippet(snippet string) {
    82  	spec.addEntry(snippet, "")
    83  }
    84  
    85  func udevTag(securityTag string) string {
    86  	return strings.Replace(securityTag, ".", "_", -1)
    87  }
    88  
    89  // TagDevice adds an app/hook specific udev tag to devices described by the
    90  // snippet and adds an app/hook-specific RUN rule for hotplugging.
    91  func (spec *Specification) TagDevice(snippet string) {
    92  	for _, securityTag := range spec.securityTags {
    93  		tag := udevTag(securityTag)
    94  		spec.addEntry(fmt.Sprintf("# %s\n%s, TAG+=\"%s\"", spec.iface, snippet, tag), tag)
    95  		spec.addEntry(fmt.Sprintf("TAG==\"%s\", RUN+=\"%s/snap-device-helper $env{ACTION} %s $devpath $major:$minor\"",
    96  			tag, dirs.DistroLibExecDir, tag), tag)
    97  	}
    98  }
    99  
   100  type byTagAndSnippet []entry
   101  
   102  func (c byTagAndSnippet) Len() int      { return len(c) }
   103  func (c byTagAndSnippet) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
   104  func (c byTagAndSnippet) Less(i, j int) bool {
   105  	if c[i].tag != c[j].tag {
   106  		return c[i].tag < c[j].tag
   107  	}
   108  	return c[i].snippet < c[j].snippet
   109  }
   110  
   111  // Snippets returns a copy of all the snippets added so far.
   112  func (spec *Specification) Snippets() (result []string) {
   113  	// If one of the interfaces controls it's own device cgroup, then
   114  	// we don't want to enforce a device cgroup, which is only turned on if
   115  	// there are udev rules, and as such we don't want to generate any udev
   116  	// rules
   117  
   118  	if spec.ControlsDeviceCgroup() {
   119  		return nil
   120  	}
   121  	entries := make([]entry, len(spec.entries))
   122  	copy(entries, spec.entries)
   123  	sort.Sort(byTagAndSnippet(entries))
   124  
   125  	result = make([]string, 0, len(spec.entries))
   126  	for _, entry := range entries {
   127  		result = append(result, entry.snippet)
   128  	}
   129  	return result
   130  }
   131  
   132  // Implementation of methods required by interfaces.Specification
   133  
   134  // AddConnectedPlug records udev-specific side-effects of having a connected plug.
   135  func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   136  	type definer interface {
   137  		UDevConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
   138  	}
   139  	ifname := iface.Name()
   140  	if iface, ok := iface.(definer); ok {
   141  		spec.securityTags = plug.SecurityTags()
   142  		spec.iface = ifname
   143  		defer func() { spec.securityTags = nil; spec.iface = "" }()
   144  		return iface.UDevConnectedPlug(spec, plug, slot)
   145  	}
   146  	return nil
   147  }
   148  
   149  // AddConnectedSlot records mount-specific side-effects of having a connected slot.
   150  func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   151  	type definer interface {
   152  		UDevConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
   153  	}
   154  	ifname := iface.Name()
   155  	if iface, ok := iface.(definer); ok {
   156  		spec.securityTags = slot.SecurityTags()
   157  		spec.iface = ifname
   158  		defer func() { spec.securityTags = nil; spec.iface = "" }()
   159  		return iface.UDevConnectedSlot(spec, plug, slot)
   160  	}
   161  	return nil
   162  }
   163  
   164  // AddPermanentPlug records mount-specific side-effects of having a plug.
   165  func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error {
   166  	type definer interface {
   167  		UDevPermanentPlug(spec *Specification, plug *snap.PlugInfo) error
   168  	}
   169  	ifname := iface.Name()
   170  	if iface, ok := iface.(definer); ok {
   171  		spec.securityTags = plug.SecurityTags()
   172  		spec.iface = ifname
   173  		defer func() { spec.securityTags = nil; spec.iface = "" }()
   174  		return iface.UDevPermanentPlug(spec, plug)
   175  	}
   176  	return nil
   177  }
   178  
   179  // AddPermanentSlot records mount-specific side-effects of having a slot.
   180  func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error {
   181  	type definer interface {
   182  		UDevPermanentSlot(spec *Specification, slot *snap.SlotInfo) error
   183  	}
   184  	ifname := iface.Name()
   185  	if iface, ok := iface.(definer); ok {
   186  		spec.securityTags = slot.SecurityTags()
   187  		spec.iface = ifname
   188  		defer func() { spec.securityTags = nil; spec.iface = "" }()
   189  		return iface.UDevPermanentSlot(spec, slot)
   190  	}
   191  	return nil
   192  }
   193  
   194  // TriggerSubsystem informs ReloadRules() to also do
   195  // 'udevadm trigger <subsystem specific>'.
   196  // IMPORTANT: because there is currently no way to call TriggerSubsystem during
   197  // interface disconnect, TriggerSubsystem() should typically only by used in
   198  // UDevPermanentSlot since the rules are permanent until the snap is removed.
   199  func (spec *Specification) TriggerSubsystem(subsystem string) {
   200  	if subsystem == "" {
   201  		return
   202  	}
   203  
   204  	if strutil.ListContains(spec.udevadmSubsystemTriggers, subsystem) {
   205  		return
   206  	}
   207  	spec.udevadmSubsystemTriggers = append(spec.udevadmSubsystemTriggers, subsystem)
   208  }
   209  
   210  func (spec *Specification) TriggeredSubsystems() []string {
   211  	if len(spec.udevadmSubsystemTriggers) == 0 {
   212  		return nil
   213  	}
   214  	c := make([]string, len(spec.udevadmSubsystemTriggers))
   215  	copy(c, spec.udevadmSubsystemTriggers)
   216  	return c
   217  }