gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/mount/spec.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2017 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 mount 21 22 import ( 23 "fmt" 24 "path" 25 "sort" 26 27 "gitee.com/mysnapcore/mysnapd/dirs" 28 "gitee.com/mysnapcore/mysnapd/interfaces" 29 "gitee.com/mysnapcore/mysnapd/logger" 30 "gitee.com/mysnapcore/mysnapd/osutil" 31 "gitee.com/mysnapcore/mysnapd/snap" 32 "gitee.com/mysnapcore/mysnapd/strutil" 33 ) 34 35 // Specification assists in collecting mount entries associated with an interface. 36 // 37 // Unlike the Backend itself (which is stateless and non-persistent) this type 38 // holds internal state that is used by the mount backend during the interface 39 // setup process. 40 type Specification struct { 41 // The mount profile is internally re-sorted by snap-update-ns based on 42 // the source of given mount entry and MountEntry.Dir. See 43 // cmd/snap-update-ns/sorting.go for details. 44 45 layout []osutil.MountEntry 46 general []osutil.MountEntry 47 user []osutil.MountEntry 48 overname []osutil.MountEntry 49 } 50 51 // AddMountEntry adds a new mount entry. 52 func (spec *Specification) AddMountEntry(e osutil.MountEntry) error { 53 spec.general = append(spec.general, e) 54 return nil 55 } 56 57 //AddUserMountEntry adds a new user mount entry. 58 func (spec *Specification) AddUserMountEntry(e osutil.MountEntry) error { 59 spec.user = append(spec.user, e) 60 return nil 61 } 62 63 // AddOvernameMountEntry adds a new overname mount entry. 64 func (spec *Specification) AddOvernameMountEntry(e osutil.MountEntry) error { 65 spec.overname = append(spec.overname, e) 66 return nil 67 } 68 69 func mountEntryFromLayout(layout *snap.Layout) osutil.MountEntry { 70 var entry osutil.MountEntry 71 72 mountPoint := layout.Snap.ExpandSnapVariables(layout.Path) 73 entry.Dir = mountPoint 74 75 // XXX: what about ro mounts? 76 if layout.Bind != "" { 77 mountSource := layout.Snap.ExpandSnapVariables(layout.Bind) 78 entry.Options = []string{"rbind", "rw"} 79 entry.Name = mountSource 80 } 81 if layout.BindFile != "" { 82 mountSource := layout.Snap.ExpandSnapVariables(layout.BindFile) 83 entry.Options = []string{"bind", "rw", osutil.XSnapdKindFile()} 84 entry.Name = mountSource 85 } 86 87 if layout.Type == "tmpfs" { 88 entry.Type = "tmpfs" 89 entry.Name = "tmpfs" 90 } 91 92 if layout.Symlink != "" { 93 oldname := layout.Snap.ExpandSnapVariables(layout.Symlink) 94 entry.Options = []string{osutil.XSnapdKindSymlink(), osutil.XSnapdSymlink(oldname)} 95 } 96 97 var uid uint32 98 // Only root is allowed here until we support custom users. Root is default. 99 switch layout.User { 100 case "root", "": 101 uid = 0 102 } 103 if uid != 0 { 104 entry.Options = append(entry.Options, osutil.XSnapdUser(uid)) 105 } 106 107 var gid uint32 108 // Only root is allowed here until we support custom groups. Root is default. 109 // This is validated in spec.go. 110 switch layout.Group { 111 case "root", "": 112 gid = 0 113 } 114 if gid != 0 { 115 entry.Options = append(entry.Options, osutil.XSnapdGroup(gid)) 116 } 117 118 if layout.Mode != 0755 { 119 entry.Options = append(entry.Options, osutil.XSnapdMode(uint32(layout.Mode))) 120 } 121 122 // Indicate that this is a layout mount entry. 123 entry.Options = append(entry.Options, osutil.XSnapdOriginLayout()) 124 return entry 125 } 126 127 // AddLayout adds mount entries based on the layout of the snap. 128 func (spec *Specification) AddLayout(si *snap.Info) { 129 // TODO: handle layouts in base snaps as well as in this snap. 130 131 // walk the layout elements in deterministic order, by mount point name 132 paths := make([]string, 0, len(si.Layout)) 133 for path := range si.Layout { 134 paths = append(paths, path) 135 } 136 sort.Strings(paths) 137 138 for _, path := range paths { 139 entry := mountEntryFromLayout(si.Layout[path]) 140 spec.layout = append(spec.layout, entry) 141 } 142 } 143 144 // AddExtraLayouts adds mount entries based on additional layouts that 145 // are provided for the snap. 146 func (spec *Specification) AddExtraLayouts(layouts []snap.Layout) { 147 for _, layout := range layouts { 148 entry := mountEntryFromLayout(&layout) 149 spec.layout = append(spec.layout, entry) 150 } 151 } 152 153 // MountEntries returns a copy of the added mount entries. 154 func (spec *Specification) MountEntries() []osutil.MountEntry { 155 result := make([]osutil.MountEntry, 0, len(spec.overname)+len(spec.layout)+len(spec.general)) 156 // overname is the mappings that were added to support parallel 157 // installation of snaps and must come first, as they establish the base 158 // namespace for any further operations 159 result = append(result, spec.overname...) 160 result = append(result, spec.layout...) 161 result = append(result, spec.general...) 162 return unclashMountEntries(result) 163 } 164 165 // UserMountEntries returns a copy of the added user mount entries. 166 func (spec *Specification) UserMountEntries() []osutil.MountEntry { 167 result := make([]osutil.MountEntry, len(spec.user)) 168 copy(result, spec.user) 169 return unclashMountEntries(result) 170 } 171 172 // Assuming that two mount entries have the same source, target and type, this 173 // function computes the mount options that should be used when performing the 174 // mount, so that the most permissive options are kept. 175 // The following flags are considered (of course the operation is commutative): 176 // - "ro" + "rw" = "rw" 177 // - "bind" + "rbind" = "rbind 178 func mergeOptions(options ...[]string) []string { 179 mergedOptions := make([]string, 0, len(options[0])) 180 foundWritableEntry := false 181 foundRBindEntry := false 182 firstEntryIsBindMount := false 183 for i, opts := range options { 184 isReadOnly := false 185 isRBind := false 186 for _, o := range opts { 187 switch o { 188 case "ro": 189 isReadOnly = true 190 case "rbind": 191 isRBind = true 192 fallthrough 193 case "bind": 194 // We know that the passed entries will either be all 195 // bind-mounts, or none will be a bind-mount (because 196 // unclashMountEntries() invokes us only if the source, target, 197 // and FS type are the same). That's why we check only the 198 // first entry here. 199 if i == 0 { 200 firstEntryIsBindMount = true 201 } 202 case "rw", "async": 203 // these are default options for mount, do nothing 204 default: 205 // write all other options 206 if !strutil.ListContains(mergedOptions, o) { 207 mergedOptions = append(mergedOptions, o) 208 } 209 } 210 } 211 if !isReadOnly { 212 foundWritableEntry = true 213 } 214 if isRBind { 215 foundRBindEntry = true 216 } 217 } 218 219 if !foundWritableEntry { 220 mergedOptions = append(mergedOptions, "ro") 221 } 222 223 if firstEntryIsBindMount { 224 if foundRBindEntry { 225 mergedOptions = append(mergedOptions, "rbind") 226 } else { 227 mergedOptions = append(mergedOptions, "bind") 228 } 229 } 230 231 return mergedOptions 232 } 233 234 // unclashMountEntries renames mount points if they clash with other entries. 235 // 236 // Subsequent entries get suffixed with -2, -3, etc. 237 // The initial entry is unaltered (and does not become -1). 238 func unclashMountEntries(entries []osutil.MountEntry) []osutil.MountEntry { 239 result := make([]osutil.MountEntry, 0, len(entries)) 240 241 // The clashingEntry structure contains the information about different 242 // mount entries which use the same mount point. 243 type clashingEntry struct { 244 // Index in the `entries` array to the first entry of this clashing group 245 FirstIndex int 246 // Number of entries having this same mount point 247 Count int 248 // Merged options for the entries on this mount point 249 Options []string 250 } 251 entriesByMountPoint := make(map[string]*clashingEntry, len(entries)) 252 for i := range entries { 253 mountPoint := entries[i].Dir 254 entryInMap, found := entriesByMountPoint[mountPoint] 255 if !found { 256 index := len(result) 257 result = append(result, entries[i]) 258 entriesByMountPoint[mountPoint] = &clashingEntry{ 259 FirstIndex: index, 260 Count: 1, 261 } 262 continue 263 } 264 // If the source and the FS type is the same, we do not consider 265 // this to be a clash, and instead will try to combine the mount 266 // flags in a way that fulfils the permissions required by all 267 // requesting entries 268 firstEntry := &result[entryInMap.FirstIndex] 269 if firstEntry.Name == entries[i].Name && firstEntry.Type == entries[i].Type && 270 // Only merge entries that have no origin, or snap-update-ns will 271 // get confused 272 firstEntry.XSnapdOrigin() == "" && entries[i].XSnapdOrigin() == "" { 273 firstEntry.Options = mergeOptions(firstEntry.Options, entries[i].Options) 274 } else { 275 entryInMap.Count++ 276 newDir := fmt.Sprintf("%s-%d", entries[i].Dir, entryInMap.Count) 277 logger.Noticef("renaming mount entry for directory %q to %q to avoid a clash", entries[i].Dir, newDir) 278 entries[i].Dir = newDir 279 result = append(result, entries[i]) 280 } 281 } 282 return result 283 } 284 285 // Implementation of methods required by interfaces.Specification 286 287 // AddConnectedPlug records mount-specific side-effects of having a connected plug. 288 func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 289 type definer interface { 290 MountConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error 291 } 292 if iface, ok := iface.(definer); ok { 293 return iface.MountConnectedPlug(spec, plug, slot) 294 } 295 return nil 296 } 297 298 // AddConnectedSlot records mount-specific side-effects of having a connected slot. 299 func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { 300 type definer interface { 301 MountConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error 302 } 303 if iface, ok := iface.(definer); ok { 304 return iface.MountConnectedSlot(spec, plug, slot) 305 } 306 return nil 307 } 308 309 // AddPermanentPlug records mount-specific side-effects of having a plug. 310 func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { 311 type definer interface { 312 MountPermanentPlug(spec *Specification, plug *snap.PlugInfo) error 313 } 314 if iface, ok := iface.(definer); ok { 315 return iface.MountPermanentPlug(spec, plug) 316 } 317 return nil 318 } 319 320 // AddPermanentSlot records mount-specific side-effects of having a slot. 321 func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { 322 type definer interface { 323 MountPermanentSlot(spec *Specification, slot *snap.SlotInfo) error 324 } 325 if iface, ok := iface.(definer); ok { 326 return iface.MountPermanentSlot(spec, slot) 327 } 328 return nil 329 } 330 331 // AddOvername records mappings of snap directories. 332 // 333 // When the snap is installed with an instance key, set up its mount namespace 334 // such that it appears as a non-instance key snap. This ensures compatibility 335 // with code making assumptions about $SNAP{,_DATA,_COMMON} locations. That is, 336 // given a snap foo_bar, the mappings added are: 337 // 338 // - /snap/foo_bar -> /snap/foo 339 // - /var/snap/foo_bar -> /var/snap/foo 340 func (spec *Specification) AddOvername(info *snap.Info) { 341 if info.InstanceKey == "" { 342 return 343 } 344 345 // /snap/foo_bar -> /snap/foo 346 spec.AddOvernameMountEntry(osutil.MountEntry{ 347 Name: path.Join(dirs.CoreSnapMountDir, info.InstanceName()), 348 Dir: path.Join(dirs.CoreSnapMountDir, info.SnapName()), 349 Options: []string{"rbind", osutil.XSnapdOriginOvername()}, 350 }) 351 // /var/snap/foo_bar -> /var/snap/foo 352 spec.AddOvernameMountEntry(osutil.MountEntry{ 353 Name: path.Join(dirs.SnapDataDir, info.InstanceName()), 354 Dir: path.Join(dirs.SnapDataDir, info.SnapName()), 355 Options: []string{"rbind", osutil.XSnapdOriginOvername()}, 356 }) 357 }