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  }