github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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  	"github.com/snapcore/snapd/dirs"
    28  	"github.com/snapcore/snapd/interfaces"
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/osutil"
    31  	"github.com/snapcore/snapd/snap"
    32  )
    33  
    34  // Specification assists in collecting mount entries associated with an interface.
    35  //
    36  // Unlike the Backend itself (which is stateless and non-persistent) this type
    37  // holds internal state that is used by the mount backend during the interface
    38  // setup process.
    39  type Specification struct {
    40  	// The mount profile is internally re-sorted by snap-update-ns based on
    41  	// the source of given mount entry and MountEntry.Dir. See
    42  	// cmd/snap-update-ns/sorting.go for details.
    43  
    44  	layout   []osutil.MountEntry
    45  	general  []osutil.MountEntry
    46  	user     []osutil.MountEntry
    47  	overname []osutil.MountEntry
    48  }
    49  
    50  // AddMountEntry adds a new mount entry.
    51  func (spec *Specification) AddMountEntry(e osutil.MountEntry) error {
    52  	spec.general = append(spec.general, e)
    53  	return nil
    54  }
    55  
    56  //AddUserMountEntry adds a new user mount entry.
    57  func (spec *Specification) AddUserMountEntry(e osutil.MountEntry) error {
    58  	spec.user = append(spec.user, e)
    59  	return nil
    60  }
    61  
    62  // AddOvernameMountEntry adds a new overname mount entry.
    63  func (spec *Specification) AddOvernameMountEntry(e osutil.MountEntry) error {
    64  	spec.overname = append(spec.overname, e)
    65  	return nil
    66  }
    67  
    68  func mountEntryFromLayout(layout *snap.Layout) osutil.MountEntry {
    69  	var entry osutil.MountEntry
    70  
    71  	mountPoint := layout.Snap.ExpandSnapVariables(layout.Path)
    72  	entry.Dir = mountPoint
    73  
    74  	// XXX: what about ro mounts?
    75  	if layout.Bind != "" {
    76  		mountSource := layout.Snap.ExpandSnapVariables(layout.Bind)
    77  		entry.Options = []string{"rbind", "rw"}
    78  		entry.Name = mountSource
    79  	}
    80  	if layout.BindFile != "" {
    81  		mountSource := layout.Snap.ExpandSnapVariables(layout.BindFile)
    82  		entry.Options = []string{"bind", "rw", osutil.XSnapdKindFile()}
    83  		entry.Name = mountSource
    84  	}
    85  
    86  	if layout.Type == "tmpfs" {
    87  		entry.Type = "tmpfs"
    88  		entry.Name = "tmpfs"
    89  	}
    90  
    91  	if layout.Symlink != "" {
    92  		oldname := layout.Snap.ExpandSnapVariables(layout.Symlink)
    93  		entry.Options = []string{osutil.XSnapdKindSymlink(), osutil.XSnapdSymlink(oldname)}
    94  	}
    95  
    96  	var uid uint32
    97  	// Only root is allowed here until we support custom users. Root is default.
    98  	switch layout.User {
    99  	case "root", "":
   100  		uid = 0
   101  	}
   102  	if uid != 0 {
   103  		entry.Options = append(entry.Options, osutil.XSnapdUser(uid))
   104  	}
   105  
   106  	var gid uint32
   107  	// Only root is allowed here until we support custom groups. Root is default.
   108  	// This is validated in spec.go.
   109  	switch layout.Group {
   110  	case "root", "":
   111  		gid = 0
   112  	}
   113  	if gid != 0 {
   114  		entry.Options = append(entry.Options, osutil.XSnapdGroup(gid))
   115  	}
   116  
   117  	if layout.Mode != 0755 {
   118  		entry.Options = append(entry.Options, osutil.XSnapdMode(uint32(layout.Mode)))
   119  	}
   120  
   121  	// Indicate that this is a layout mount entry.
   122  	entry.Options = append(entry.Options, osutil.XSnapdOriginLayout())
   123  	return entry
   124  }
   125  
   126  // AddLayout adds mount entries based on the layout of the snap.
   127  func (spec *Specification) AddLayout(si *snap.Info) {
   128  	// TODO: handle layouts in base snaps as well as in this snap.
   129  
   130  	// walk the layout elements in deterministic order, by mount point name
   131  	paths := make([]string, 0, len(si.Layout))
   132  	for path := range si.Layout {
   133  		paths = append(paths, path)
   134  	}
   135  	sort.Strings(paths)
   136  
   137  	for _, path := range paths {
   138  		entry := mountEntryFromLayout(si.Layout[path])
   139  		spec.layout = append(spec.layout, entry)
   140  	}
   141  }
   142  
   143  // MountEntries returns a copy of the added mount entries.
   144  func (spec *Specification) MountEntries() []osutil.MountEntry {
   145  	result := make([]osutil.MountEntry, 0, len(spec.overname)+len(spec.layout)+len(spec.general))
   146  	// overname is the mappings that were added to support parallel
   147  	// installation of snaps and must come first, as they establish the base
   148  	// namespace for any further operations
   149  	result = append(result, spec.overname...)
   150  	result = append(result, spec.layout...)
   151  	result = append(result, spec.general...)
   152  	unclashMountEntries(result)
   153  	return result
   154  }
   155  
   156  // UserMountEntries returns a copy of the added user mount entries.
   157  func (spec *Specification) UserMountEntries() []osutil.MountEntry {
   158  	result := make([]osutil.MountEntry, len(spec.user))
   159  	copy(result, spec.user)
   160  	unclashMountEntries(result)
   161  	return result
   162  }
   163  
   164  // unclashMountEntries renames mount points if they clash with other entries.
   165  //
   166  // Subsequent entries get suffixed with -2, -3, etc.
   167  // The initial entry is unaltered (and does not become -1).
   168  func unclashMountEntries(entries []osutil.MountEntry) {
   169  	count := make(map[string]int, len(entries))
   170  	for i := range entries {
   171  		path := entries[i].Dir
   172  		count[path]++
   173  		if c := count[path]; c > 1 {
   174  			newDir := fmt.Sprintf("%s-%d", entries[i].Dir, c)
   175  			logger.Noticef("renaming mount entry for directory %q to %q to avoid a clash", entries[i].Dir, newDir)
   176  			entries[i].Dir = newDir
   177  		}
   178  	}
   179  }
   180  
   181  // Implementation of methods required by interfaces.Specification
   182  
   183  // AddConnectedPlug records mount-specific side-effects of having a connected plug.
   184  func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   185  	type definer interface {
   186  		MountConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
   187  	}
   188  	if iface, ok := iface.(definer); ok {
   189  		return iface.MountConnectedPlug(spec, plug, slot)
   190  	}
   191  	return nil
   192  }
   193  
   194  // AddConnectedSlot records mount-specific side-effects of having a connected slot.
   195  func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   196  	type definer interface {
   197  		MountConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
   198  	}
   199  	if iface, ok := iface.(definer); ok {
   200  		return iface.MountConnectedSlot(spec, plug, slot)
   201  	}
   202  	return nil
   203  }
   204  
   205  // AddPermanentPlug records mount-specific side-effects of having a plug.
   206  func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error {
   207  	type definer interface {
   208  		MountPermanentPlug(spec *Specification, plug *snap.PlugInfo) error
   209  	}
   210  	if iface, ok := iface.(definer); ok {
   211  		return iface.MountPermanentPlug(spec, plug)
   212  	}
   213  	return nil
   214  }
   215  
   216  // AddPermanentSlot records mount-specific side-effects of having a slot.
   217  func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error {
   218  	type definer interface {
   219  		MountPermanentSlot(spec *Specification, slot *snap.SlotInfo) error
   220  	}
   221  	if iface, ok := iface.(definer); ok {
   222  		return iface.MountPermanentSlot(spec, slot)
   223  	}
   224  	return nil
   225  }
   226  
   227  // AddOvername records mappings of snap directories.
   228  //
   229  // When the snap is installed with an instance key, set up its mount namespace
   230  // such that it appears as a non-instance key snap. This ensures compatibility
   231  // with code making assumptions about $SNAP{,_DATA,_COMMON} locations. That is,
   232  // given a snap foo_bar, the mappings added are:
   233  //
   234  // - /snap/foo_bar      -> /snap/foo
   235  // - /var/snap/foo_bar  -> /var/snap/foo
   236  func (spec *Specification) AddOvername(info *snap.Info) {
   237  	if info.InstanceKey == "" {
   238  		return
   239  	}
   240  
   241  	// /snap/foo_bar -> /snap/foo
   242  	spec.AddOvernameMountEntry(osutil.MountEntry{
   243  		Name:    path.Join(dirs.CoreSnapMountDir, info.InstanceName()),
   244  		Dir:     path.Join(dirs.CoreSnapMountDir, info.SnapName()),
   245  		Options: []string{"rbind", osutil.XSnapdOriginOvername()},
   246  	})
   247  	// /var/snap/foo_bar -> /var/snap/foo
   248  	spec.AddOvernameMountEntry(osutil.MountEntry{
   249  		Name:    path.Join(dirs.SnapDataDir, info.InstanceName()),
   250  		Dir:     path.Join(dirs.SnapDataDir, info.SnapName()),
   251  		Options: []string{"rbind", osutil.XSnapdOriginOvername()},
   252  	})
   253  }