github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 }