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