mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
Snapd from the snap generate udev rules that executes snap-device-helper from the host. In cases when the snap is newer than the package, the new command line is rejected by the old snap-device-helper from the package. Because the new snap-device-helper accepts old command-line, but just ignores the extra parameters, it is safer for now to generate rules with the old command line.
256 lines
7.9 KiB
Go
256 lines
7.9 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2017-2018 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package udev
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/snapcore/snapd/dirs"
|
|
"github.com/snapcore/snapd/interfaces"
|
|
"github.com/snapcore/snapd/snap"
|
|
"github.com/snapcore/snapd/strutil"
|
|
)
|
|
|
|
type entry struct {
|
|
snippet string
|
|
iface string
|
|
tag string
|
|
}
|
|
|
|
// Specification assists in collecting udev snippets associated with an interface.
|
|
type Specification struct {
|
|
// Snippets are stored in a map for de-duplication
|
|
snippets map[string]bool
|
|
entries []entry
|
|
iface string
|
|
|
|
appSet *interfaces.SnapAppSet
|
|
|
|
securityTags []string
|
|
udevadmSubsystemTriggers []string
|
|
controlsDeviceCgroup bool
|
|
}
|
|
|
|
func NewSpecification(appSet *interfaces.SnapAppSet) *Specification {
|
|
return &Specification{
|
|
appSet: appSet,
|
|
}
|
|
}
|
|
|
|
func (spec *Specification) SnapAppSet() *interfaces.SnapAppSet {
|
|
return spec.appSet
|
|
}
|
|
|
|
// SetControlsDeviceCgroup marks a specification as needing to control
|
|
// its own device cgroup which prevents generation of any udev tagging rules
|
|
// for this snap name
|
|
// TODO: this setting should also imply setting Delegates=true in the
|
|
// ServicePermanentPlug somehow, perhaps just for the commonInterface
|
|
func (spec *Specification) SetControlsDeviceCgroup() {
|
|
spec.controlsDeviceCgroup = true
|
|
}
|
|
|
|
// ControlsDeviceCgroup returns whether a specification was marked as needing to
|
|
// control its own device cgroup which prevents generation of any udev tagging
|
|
// rules for this snap name.
|
|
func (spec *Specification) ControlsDeviceCgroup() bool {
|
|
return spec.controlsDeviceCgroup
|
|
}
|
|
|
|
func (spec *Specification) addEntry(snippet, tag string) {
|
|
if spec.snippets == nil {
|
|
spec.snippets = make(map[string]bool)
|
|
}
|
|
if !spec.snippets[snippet] {
|
|
spec.snippets[snippet] = true
|
|
e := entry{
|
|
snippet: snippet,
|
|
iface: spec.iface,
|
|
tag: tag,
|
|
}
|
|
spec.entries = append(spec.entries, e)
|
|
}
|
|
}
|
|
|
|
// AddSnippet adds a new udev snippet.
|
|
func (spec *Specification) AddSnippet(snippet string) {
|
|
spec.addEntry(snippet, "")
|
|
}
|
|
|
|
func udevTag(securityTag string) string {
|
|
return strings.Replace(securityTag, ".", "_", -1)
|
|
}
|
|
|
|
// TagDevice adds an app/hook specific udev tag to devices described by the
|
|
// snippet and adds an app/hook-specific RUN rule for hotplugging.
|
|
func (spec *Specification) TagDevice(snippet string) {
|
|
for _, securityTag := range spec.securityTags {
|
|
tag := udevTag(securityTag)
|
|
spec.addEntry(fmt.Sprintf("# %s\n%s, TAG+=\"%s\"", spec.iface, snippet, tag), tag)
|
|
// SUBSYSTEM=="module" is for kernel modules not devices.
|
|
// SUBSYSTEM=="subsystem" is for subsystems (the top directories in /sys/class). Not for devices.
|
|
// When loaded, they send an ADD event
|
|
// snap-device-helper expects devices only, not modules nor subsystems
|
|
spec.addEntry(fmt.Sprintf("TAG==\"%s\", SUBSYSTEM!=\"module\", SUBSYSTEM!=\"subsystem\", RUN+=\"%s/snap-device-helper $env{ACTION} %s $devpath $major:$minor\"",
|
|
tag, dirs.DistroLibExecDir, tag), tag)
|
|
}
|
|
}
|
|
|
|
type byTagAndSnippet []entry
|
|
|
|
func (c byTagAndSnippet) Len() int { return len(c) }
|
|
func (c byTagAndSnippet) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
|
func (c byTagAndSnippet) Less(i, j int) bool {
|
|
if c[i].tag != c[j].tag {
|
|
return c[i].tag < c[j].tag
|
|
}
|
|
return c[i].snippet < c[j].snippet
|
|
}
|
|
|
|
// Snippets returns a copy of all the snippets added so far.
|
|
func (spec *Specification) Snippets() (result []string) {
|
|
// If one of the interfaces controls it's own device cgroup, then
|
|
// we don't want to enforce a device cgroup, which is only turned on if
|
|
// there are udev rules, and as such we don't want to generate any udev
|
|
// rules
|
|
|
|
if spec.ControlsDeviceCgroup() {
|
|
return nil
|
|
}
|
|
entries := make([]entry, len(spec.entries))
|
|
copy(entries, spec.entries)
|
|
sort.Sort(byTagAndSnippet(entries))
|
|
|
|
result = make([]string, 0, len(spec.entries))
|
|
for _, entry := range entries {
|
|
result = append(result, entry.snippet)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Implementation of methods required by interfaces.Specification
|
|
|
|
// AddConnectedPlug records udev-specific side-effects of having a connected plug.
|
|
func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
|
|
type definer interface {
|
|
UDevConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
|
|
}
|
|
ifname := iface.Name()
|
|
if iface, ok := iface.(definer); ok {
|
|
tags, err := spec.appSet.SecurityTagsForConnectedPlug(plug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
spec.securityTags = tags
|
|
spec.iface = ifname
|
|
defer func() { spec.securityTags = nil; spec.iface = "" }()
|
|
return iface.UDevConnectedPlug(spec, plug, slot)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddConnectedSlot records mount-specific side-effects of having a connected slot.
|
|
func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
|
|
type definer interface {
|
|
UDevConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
|
|
}
|
|
ifname := iface.Name()
|
|
if iface, ok := iface.(definer); ok {
|
|
tags, err := spec.appSet.SecurityTagsForConnectedSlot(slot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
spec.securityTags = tags
|
|
spec.iface = ifname
|
|
defer func() { spec.securityTags = nil; spec.iface = "" }()
|
|
return iface.UDevConnectedSlot(spec, plug, slot)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddPermanentPlug records mount-specific side-effects of having a plug.
|
|
func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error {
|
|
type definer interface {
|
|
UDevPermanentPlug(spec *Specification, plug *snap.PlugInfo) error
|
|
}
|
|
ifname := iface.Name()
|
|
if iface, ok := iface.(definer); ok {
|
|
tags, err := spec.appSet.SecurityTagsForPlug(plug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
spec.securityTags = tags
|
|
spec.iface = ifname
|
|
defer func() { spec.securityTags = nil; spec.iface = "" }()
|
|
return iface.UDevPermanentPlug(spec, plug)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddPermanentSlot records mount-specific side-effects of having a slot.
|
|
func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error {
|
|
type definer interface {
|
|
UDevPermanentSlot(spec *Specification, slot *snap.SlotInfo) error
|
|
}
|
|
ifname := iface.Name()
|
|
if iface, ok := iface.(definer); ok {
|
|
tags, err := spec.appSet.SecurityTagsForSlot(slot)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
spec.securityTags = tags
|
|
spec.iface = ifname
|
|
defer func() { spec.securityTags = nil; spec.iface = "" }()
|
|
return iface.UDevPermanentSlot(spec, slot)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TriggerSubsystem informs ReloadRules() to also do
|
|
// 'udevadm trigger <subsystem specific>'.
|
|
// IMPORTANT: because there is currently no way to call TriggerSubsystem during
|
|
// interface disconnect, TriggerSubsystem() should typically only by used in
|
|
// UDevPermanentSlot since the rules are permanent until the snap is removed.
|
|
func (spec *Specification) TriggerSubsystem(subsystem string) {
|
|
if subsystem == "" {
|
|
return
|
|
}
|
|
|
|
if strutil.ListContains(spec.udevadmSubsystemTriggers, subsystem) {
|
|
return
|
|
}
|
|
spec.udevadmSubsystemTriggers = append(spec.udevadmSubsystemTriggers, subsystem)
|
|
}
|
|
|
|
func (spec *Specification) TriggeredSubsystems() []string {
|
|
if len(spec.udevadmSubsystemTriggers) == 0 {
|
|
return nil
|
|
}
|
|
c := make([]string, len(spec.udevadmSubsystemTriggers))
|
|
copy(c, spec.udevadmSubsystemTriggers)
|
|
return c
|
|
}
|