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