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 }