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