github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/interfaces/udev/backend.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-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 implements integration between snapd, udev and 21 // snap-confine around tagging character and block devices so that they 22 // can be accessed by applications. 23 // 24 // TODO: Document this better 25 package udev 26 27 import ( 28 "bytes" 29 "fmt" 30 "os" 31 "path/filepath" 32 "strings" 33 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/interfaces" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/sandbox/cgroup" 38 "github.com/snapcore/snapd/snap" 39 "github.com/snapcore/snapd/timings" 40 ) 41 42 // Backend is responsible for maintaining udev rules. 43 type Backend struct { 44 preseed bool 45 } 46 47 // Initialize does nothing. 48 func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error { 49 if opts != nil && opts.Preseed { 50 b.preseed = true 51 } 52 return nil 53 } 54 55 // Name returns the name of the backend. 56 func (b *Backend) Name() interfaces.SecuritySystem { 57 return interfaces.SecurityUDev 58 } 59 60 // snapRulesFileName returns the path of the snap udev rules file. 61 func snapRulesFilePath(snapName string) string { 62 rulesFileName := fmt.Sprintf("70-%s.rules", snap.SecurityTag(snapName)) 63 return filepath.Join(dirs.SnapUdevRulesDir, rulesFileName) 64 } 65 66 // Setup creates udev rules specific to a given snap. 67 // If any of the rules are changed or removed then udev database is reloaded. 68 // 69 // UDev has no concept of a complain mode so confinement options are ignored. 70 // 71 // If the method fails it should be re-tried (with a sensible strategy) by the caller. 72 func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error { 73 snapName := snapInfo.InstanceName() 74 spec, err := repo.SnapSpecification(b.Name(), snapName) 75 if err != nil { 76 return fmt.Errorf("cannot obtain udev specification for snap %q: %s", snapName, err) 77 } 78 content := b.deriveContent(spec.(*Specification), snapInfo) 79 subsystemTriggers := spec.(*Specification).TriggeredSubsystems() 80 81 dir := dirs.SnapUdevRulesDir 82 if err := os.MkdirAll(dir, 0755); err != nil { 83 return fmt.Errorf("cannot create directory for udev rules %q: %s", dir, err) 84 } 85 86 rulesFilePath := snapRulesFilePath(snapInfo.InstanceName()) 87 88 if len(content) == 0 { 89 // Make sure that the rules file gets removed when we don't have any 90 // content and exists. 91 err = os.Remove(rulesFilePath) 92 if err != nil && !os.IsNotExist(err) { 93 return err 94 } else if err == nil { 95 // FIXME: somehow detect the interfaces that were 96 // disconnected and set subsystemTriggers appropriately. 97 // ATM, it is always going to be empty on disconnect. 98 return b.reloadRules(subsystemTriggers) 99 } 100 return nil 101 } 102 103 var buffer bytes.Buffer 104 buffer.WriteString("# This file is automatically generated.\n") 105 if (opts.DevMode || opts.Classic) && !opts.JailMode { 106 buffer.WriteString("# udev tagging/device cgroups disabled with non-strict mode snaps\n") 107 } 108 for _, snippet := range content { 109 if (opts.DevMode || opts.Classic) && !opts.JailMode { 110 buffer.WriteRune('#') 111 snippet = strings.Replace(snippet, "\n", "\n#", -1) 112 } 113 buffer.WriteString(snippet) 114 buffer.WriteByte('\n') 115 } 116 117 rulesFileState := &osutil.MemoryFileState{ 118 Content: buffer.Bytes(), 119 Mode: 0644, 120 } 121 122 // EnsureFileState will make sure the file will be only updated when its content 123 // has changed and will otherwise return an error which prevents us from reloading 124 // udev rules when not needed. 125 err = osutil.EnsureFileState(rulesFilePath, rulesFileState) 126 if err == osutil.ErrSameState { 127 return nil 128 } else if err != nil { 129 return err 130 } 131 132 // FIXME: somehow detect the interfaces that were disconnected and set 133 // subsystemTriggers appropriately. ATM, it is always going to be empty 134 // on disconnect. 135 return b.reloadRules(subsystemTriggers) 136 } 137 138 // Remove removes udev rules specific to a given snap. 139 // If any of the rules are removed then udev database is reloaded. 140 // 141 // This method should be called after removing a snap. 142 // 143 // If the method fails it should be re-tried (with a sensible strategy) by the caller. 144 func (b *Backend) Remove(snapName string) error { 145 rulesFilePath := snapRulesFilePath(snapName) 146 err := os.Remove(rulesFilePath) 147 if os.IsNotExist(err) { 148 // If file doesn't exist we avoid reloading the udev rules when we return here 149 return nil 150 } else if err != nil { 151 return err 152 } 153 154 // FIXME: somehow detect the interfaces that were disconnected and set 155 // subsystemTriggers appropriately. ATM, it is always going to be empty 156 // on disconnect. 157 return b.reloadRules(nil) 158 } 159 160 func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info) (content []string) { 161 content = append(content, spec.Snippets()...) 162 return content 163 } 164 165 func (b *Backend) NewSpecification() interfaces.Specification { 166 return &Specification{} 167 } 168 169 // SandboxFeatures returns the list of features supported by snapd for mediating access to kernel devices. 170 func (b *Backend) SandboxFeatures() []string { 171 commonFeatures := []string{ 172 "tagging", /* Tagging dynamically associates new devices with specific snaps */ 173 } 174 cgroupv1Features := []string{ 175 "device-filtering", /* Snapd can limit device access for each snap */ 176 "device-cgroup-v1", /* Snapd creates a device group (v1) for each snap */ 177 } 178 179 if cgroup.IsUnified() { 180 // TODO: update v2 device cgroup is supported 181 return commonFeatures 182 } 183 184 features := append(cgroupv1Features, commonFeatures...) 185 return features 186 }