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 }