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