github.com/rigado/snapd@v2.42.5-go-mod+incompatible/systemd/systemd.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 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 systemd
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"regexp"
    30  	"strconv"
    31  	"strings"
    32  	"sync"
    33  	"sync/atomic"
    34  	"time"
    35  
    36  	_ "github.com/snapcore/squashfuse"
    37  
    38  	"github.com/snapcore/snapd/dirs"
    39  	"github.com/snapcore/snapd/osutil"
    40  	"github.com/snapcore/snapd/osutil/squashfs"
    41  	"github.com/snapcore/snapd/release"
    42  	"github.com/snapcore/snapd/selinux"
    43  )
    44  
    45  var (
    46  	// the output of "show" must match this for Stop to be done:
    47  	isStopDone = regexp.MustCompile(`(?m)\AActiveState=(?:failed|inactive)$`).Match
    48  
    49  	// how much time should Stop wait between calls to show
    50  	stopCheckDelay = 250 * time.Millisecond
    51  
    52  	// how much time should Stop wait between notifying the user of the waiting
    53  	stopNotifyDelay = 20 * time.Second
    54  
    55  	// daemonReloadLock is a package level lock to ensure that we
    56  	// do not run any `systemd daemon-reload` while a
    57  	// daemon-reload is in progress or a mount unit is
    58  	// generated/activated.
    59  	//
    60  	// See https://github.com/systemd/systemd/issues/10872 for the
    61  	// upstream systemd bug
    62  	daemonReloadLock extMutex
    63  )
    64  
    65  // mu is a sync.Mutex that also supports to check if the lock is taken
    66  type extMutex struct {
    67  	lock sync.Mutex
    68  	muC  int32
    69  }
    70  
    71  // Lock acquires the mutex
    72  func (m *extMutex) Lock() {
    73  	m.lock.Lock()
    74  	atomic.AddInt32(&m.muC, 1)
    75  }
    76  
    77  // Unlock releases the mutex
    78  func (m *extMutex) Unlock() {
    79  	atomic.AddInt32(&m.muC, -1)
    80  	m.lock.Unlock()
    81  }
    82  
    83  // Taken will panic with the given error message if the lock is not
    84  // taken when this code runs. This is useful to internally check if
    85  // something is accessed without a valid lock.
    86  func (m *extMutex) Taken(errMsg string) {
    87  	if atomic.LoadInt32(&m.muC) != 1 {
    88  		panic("internal error: " + errMsg)
    89  	}
    90  }
    91  
    92  // systemctlCmd calls systemctl with the given args, returning its standard output (and wrapped error)
    93  var systemctlCmd = func(args ...string) ([]byte, error) {
    94  	bs, err := exec.Command("systemctl", args...).CombinedOutput()
    95  	if err != nil {
    96  		exitCode, _ := osutil.ExitCode(err)
    97  		return nil, &Error{cmd: args, exitCode: exitCode, msg: bs}
    98  	}
    99  
   100  	return bs, nil
   101  }
   102  
   103  // MockSystemctl is called from the commands to actually call out to
   104  // systemctl. It's exported so it can be overridden by testing.
   105  func MockSystemctl(f func(args ...string) ([]byte, error)) func() {
   106  	oldSystemctlCmd := systemctlCmd
   107  	systemctlCmd = f
   108  	return func() {
   109  		systemctlCmd = oldSystemctlCmd
   110  	}
   111  }
   112  
   113  // MockStopDelays is used from tests so that Stop can be less
   114  // forgiving there.
   115  func MockStopDelays(checkDelay, notifyDelay time.Duration) func() {
   116  	oldCheckDelay := stopCheckDelay
   117  	oldNotifyDelay := stopNotifyDelay
   118  	stopCheckDelay = checkDelay
   119  	stopNotifyDelay = notifyDelay
   120  	return func() {
   121  		stopCheckDelay = oldCheckDelay
   122  		stopNotifyDelay = oldNotifyDelay
   123  	}
   124  }
   125  
   126  func Available() error {
   127  	_, err := systemctlCmd("--version")
   128  	return err
   129  }
   130  
   131  var osutilStreamCommand = osutil.StreamCommand
   132  
   133  // jctl calls journalctl to get the JSON logs of the given services.
   134  var jctl = func(svcs []string, n int, follow bool) (io.ReadCloser, error) {
   135  	// args will need two entries per service, plus a fixed number (give or take
   136  	// one) for the initial options.
   137  	args := make([]string, 0, 2*len(svcs)+6)        // the fixed number is 6
   138  	args = append(args, "-o", "json", "--no-pager") //   3...
   139  	if n < 0 {
   140  		args = append(args, "--no-tail") // < 2
   141  	} else {
   142  		args = append(args, "-n", strconv.Itoa(n)) // ... + 2 ...
   143  	}
   144  	if follow {
   145  		args = append(args, "-f") // ... + 1 == 6
   146  	}
   147  
   148  	for i := range svcs {
   149  		args = append(args, "-u", svcs[i]) // this is why 2×
   150  	}
   151  
   152  	return osutilStreamCommand("journalctl", args...)
   153  }
   154  
   155  func MockJournalctl(f func(svcs []string, n int, follow bool) (io.ReadCloser, error)) func() {
   156  	oldJctl := jctl
   157  	jctl = f
   158  	return func() {
   159  		jctl = oldJctl
   160  	}
   161  }
   162  
   163  // Systemd exposes a minimal interface to manage systemd via the systemctl command.
   164  type Systemd interface {
   165  	DaemonReload() error
   166  	Enable(service string) error
   167  	Disable(service string) error
   168  	Start(service ...string) error
   169  	StartNoBlock(service ...string) error
   170  	Stop(service string, timeout time.Duration) error
   171  	Kill(service, signal, who string) error
   172  	Restart(service string, timeout time.Duration) error
   173  	Status(units ...string) ([]*UnitStatus, error)
   174  	IsEnabled(service string) (bool, error)
   175  	IsActive(service string) (bool, error)
   176  	LogReader(services []string, n int, follow bool) (io.ReadCloser, error)
   177  	AddMountUnitFile(name, revision, what, where, fstype string) (string, error)
   178  	RemoveMountUnitFile(baseDir string) error
   179  	Mask(service string) error
   180  	Unmask(service string) error
   181  }
   182  
   183  // A Log is a single entry in the systemd journal
   184  type Log map[string]string
   185  
   186  const (
   187  	// the default target for systemd units that we generate
   188  	ServicesTarget = "multi-user.target"
   189  
   190  	// the target prerequisite for systemd units we generate
   191  	PrerequisiteTarget = "network.target"
   192  
   193  	// the default target for systemd socket units that we generate
   194  	SocketsTarget = "sockets.target"
   195  
   196  	// the default target for systemd timer units that we generate
   197  	TimersTarget = "timers.target"
   198  )
   199  
   200  type reporter interface {
   201  	Notify(string)
   202  }
   203  
   204  // New returns a Systemd that uses the given rootDir
   205  func New(rootDir string, mode InstanceMode, rep reporter) Systemd {
   206  	return &systemd{rootDir: rootDir, mode: mode, reporter: rep}
   207  }
   208  
   209  // InstanceMode determines which instance of systemd to control.
   210  //
   211  // SystemMode refers to the system instance (i.e. pid 1).  UserMode
   212  // refers to the the instance launched to manage the user's desktop
   213  // session.  GlobalUserMode controls configuration respected by all
   214  // user instances on the system.
   215  //
   216  // As GlobalUserMode does not refer to a single instance of systemd,
   217  // some operations are not supported such as starting and stopping
   218  // daemons.
   219  type InstanceMode int
   220  
   221  const (
   222  	SystemMode InstanceMode = iota
   223  	UserMode
   224  	GlobalUserMode
   225  )
   226  
   227  type systemd struct {
   228  	rootDir  string
   229  	reporter reporter
   230  	mode     InstanceMode
   231  }
   232  
   233  func (s *systemd) systemctl(args ...string) ([]byte, error) {
   234  	switch s.mode {
   235  	case SystemMode:
   236  	case UserMode:
   237  		args = append([]string{"--user"}, args...)
   238  	case GlobalUserMode:
   239  		args = append([]string{"--user", "--global"}, args...)
   240  	default:
   241  		panic("unknown InstanceMode")
   242  	}
   243  	return systemctlCmd(args...)
   244  }
   245  
   246  // DaemonReload reloads systemd's configuration.
   247  func (s *systemd) DaemonReload() error {
   248  	if s.mode == GlobalUserMode {
   249  		panic("cannot call daemon-reload with GlobalUserMode")
   250  	}
   251  	daemonReloadLock.Lock()
   252  	defer daemonReloadLock.Unlock()
   253  
   254  	return s.daemonReloadNoLock()
   255  }
   256  
   257  func (s *systemd) daemonReloadNoLock() error {
   258  	daemonReloadLock.Taken("cannot use daemon-reload without lock")
   259  
   260  	_, err := s.systemctl("daemon-reload")
   261  	return err
   262  }
   263  
   264  // Enable the given service
   265  func (s *systemd) Enable(serviceName string) error {
   266  	_, err := s.systemctl("--root", s.rootDir, "enable", serviceName)
   267  	return err
   268  }
   269  
   270  // Unmask the given service
   271  func (s *systemd) Unmask(serviceName string) error {
   272  	_, err := s.systemctl("--root", s.rootDir, "unmask", serviceName)
   273  	return err
   274  }
   275  
   276  // Disable the given service
   277  func (s *systemd) Disable(serviceName string) error {
   278  	_, err := s.systemctl("--root", s.rootDir, "disable", serviceName)
   279  	return err
   280  }
   281  
   282  // Mask the given service
   283  func (s *systemd) Mask(serviceName string) error {
   284  	_, err := s.systemctl("--root", s.rootDir, "mask", serviceName)
   285  	return err
   286  }
   287  
   288  // Start the given service or services
   289  func (s *systemd) Start(serviceNames ...string) error {
   290  	if s.mode == GlobalUserMode {
   291  		panic("cannot call start with GlobalUserMode")
   292  	}
   293  	_, err := s.systemctl(append([]string{"start"}, serviceNames...)...)
   294  	return err
   295  }
   296  
   297  // StartNoBlock starts the given service or services non-blocking
   298  func (s *systemd) StartNoBlock(serviceNames ...string) error {
   299  	if s.mode == GlobalUserMode {
   300  		panic("cannot call start with GlobalUserMode")
   301  	}
   302  	_, err := s.systemctl(append([]string{"start", "--no-block"}, serviceNames...)...)
   303  	return err
   304  }
   305  
   306  // LogReader for the given services
   307  func (*systemd) LogReader(serviceNames []string, n int, follow bool) (io.ReadCloser, error) {
   308  	return jctl(serviceNames, n, follow)
   309  }
   310  
   311  var statusregex = regexp.MustCompile(`(?m)^(?:(.+?)=(.*)|(.*))?$`)
   312  
   313  type UnitStatus struct {
   314  	Daemon   string
   315  	UnitName string
   316  	Enabled  bool
   317  	Active   bool
   318  }
   319  
   320  var baseProperties = []string{"Id", "ActiveState", "UnitFileState"}
   321  var extendedProperties = []string{"Id", "ActiveState", "UnitFileState", "Type"}
   322  var unitProperties = map[string][]string{
   323  	".timer":  baseProperties,
   324  	".socket": baseProperties,
   325  	// in service units, Type is the daemon type
   326  	".service": extendedProperties,
   327  	// in mount units, Type is the fs type
   328  	".mount": extendedProperties,
   329  }
   330  
   331  func (s *systemd) getUnitStatus(properties []string, unitNames []string) ([]*UnitStatus, error) {
   332  	cmd := make([]string, len(unitNames)+2)
   333  	cmd[0] = "show"
   334  	// ask for all properties, regardless of unit type
   335  	cmd[1] = "--property=" + strings.Join(properties, ",")
   336  	copy(cmd[2:], unitNames)
   337  	bs, err := s.systemctl(cmd...)
   338  	if err != nil {
   339  		return nil, err
   340  	}
   341  
   342  	sts := make([]*UnitStatus, 0, len(unitNames))
   343  	cur := &UnitStatus{}
   344  	seen := map[string]bool{}
   345  
   346  	for _, bs := range statusregex.FindAllSubmatch(bs, -1) {
   347  		if len(bs[0]) == 0 {
   348  			// systemctl separates data pertaining to particular services by an empty line
   349  			unitType := filepath.Ext(cur.UnitName)
   350  			expected := unitProperties[unitType]
   351  			if expected == nil {
   352  				expected = baseProperties
   353  			}
   354  
   355  			missing := make([]string, 0, len(expected))
   356  			for _, k := range expected {
   357  				if !seen[k] {
   358  					missing = append(missing, k)
   359  				}
   360  			}
   361  			if len(missing) > 0 {
   362  				return nil, fmt.Errorf("cannot get unit %q status: missing %s in ‘systemctl show’ output", cur.UnitName, strings.Join(missing, ", "))
   363  			}
   364  			sts = append(sts, cur)
   365  			if len(sts) > len(unitNames) {
   366  				break // wut
   367  			}
   368  			if cur.UnitName != unitNames[len(sts)-1] {
   369  				return nil, fmt.Errorf("cannot get unit status: queried status of %q but got status of %q", unitNames[len(sts)-1], cur.UnitName)
   370  			}
   371  
   372  			cur = &UnitStatus{}
   373  			seen = map[string]bool{}
   374  			continue
   375  		}
   376  		if len(bs[3]) > 0 {
   377  			return nil, fmt.Errorf("cannot get unit status: bad line %q in ‘systemctl show’ output", bs[3])
   378  		}
   379  		k := string(bs[1])
   380  		v := string(bs[2])
   381  
   382  		if v == "" {
   383  			return nil, fmt.Errorf("cannot get unit status: empty field %q in ‘systemctl show’ output", k)
   384  		}
   385  
   386  		switch k {
   387  		case "Id":
   388  			cur.UnitName = v
   389  		case "Type":
   390  			cur.Daemon = v
   391  		case "ActiveState":
   392  			// made to match “systemctl is-active” behaviour, at least at systemd 229
   393  			cur.Active = v == "active" || v == "reloading"
   394  		case "UnitFileState":
   395  			// "static" means it can't be disabled
   396  			cur.Enabled = v == "enabled" || v == "static"
   397  		default:
   398  			return nil, fmt.Errorf("cannot get unit status: unexpected field %q in ‘systemctl show’ output", k)
   399  		}
   400  
   401  		if seen[k] {
   402  			return nil, fmt.Errorf("cannot get unit status: duplicate field %q in ‘systemctl show’ output", k)
   403  		}
   404  		seen[k] = true
   405  	}
   406  
   407  	if len(sts) != len(unitNames) {
   408  		return nil, fmt.Errorf("cannot get unit status: expected %d results, got %d", len(unitNames), len(sts))
   409  	}
   410  	return sts, nil
   411  }
   412  
   413  // Status fetches the status of given units. Statuses are returned in the same
   414  // order as unit names passed in argument.
   415  func (s *systemd) Status(unitNames ...string) ([]*UnitStatus, error) {
   416  	if s.mode == GlobalUserMode {
   417  		panic("cannot call status with GlobalUserMode")
   418  	}
   419  	unitToStatus := make(map[string]*UnitStatus, len(unitNames))
   420  
   421  	var limitedUnits []string
   422  	var extendedUnits []string
   423  
   424  	for _, name := range unitNames {
   425  		if strings.HasSuffix(name, ".timer") || strings.HasSuffix(name, ".socket") {
   426  			limitedUnits = append(limitedUnits, name)
   427  		} else {
   428  			extendedUnits = append(extendedUnits, name)
   429  		}
   430  	}
   431  
   432  	for _, set := range []struct {
   433  		units      []string
   434  		properties []string
   435  	}{
   436  		{units: extendedUnits, properties: extendedProperties},
   437  		{units: limitedUnits, properties: baseProperties},
   438  	} {
   439  		if len(set.units) == 0 {
   440  			continue
   441  		}
   442  		sts, err := s.getUnitStatus(set.properties, set.units)
   443  		if err != nil {
   444  			return nil, err
   445  		}
   446  		for _, status := range sts {
   447  			unitToStatus[status.UnitName] = status
   448  		}
   449  	}
   450  
   451  	// unpack to preserve the promised order
   452  	sts := make([]*UnitStatus, len(unitNames))
   453  	for idx, name := range unitNames {
   454  		var ok bool
   455  		sts[idx], ok = unitToStatus[name]
   456  		if !ok {
   457  			return nil, fmt.Errorf("cannot determine status of unit %q", name)
   458  		}
   459  	}
   460  
   461  	return sts, nil
   462  }
   463  
   464  // IsEnabled checkes whether the given service is enabled
   465  func (s *systemd) IsEnabled(serviceName string) (bool, error) {
   466  	if s.mode == GlobalUserMode {
   467  		panic("cannot call is-enabled with GlobalUserMode")
   468  	}
   469  	_, err := s.systemctl("--root", s.rootDir, "is-enabled", serviceName)
   470  	if err == nil {
   471  		return true, nil
   472  	}
   473  	// "systemctl is-enabled <name>" prints `disabled\n` to stderr and returns exit code 1
   474  	// for disabled services
   475  	sysdErr, ok := err.(*Error)
   476  	if ok && sysdErr.exitCode == 1 && strings.TrimSpace(string(sysdErr.msg)) == "disabled" {
   477  		return false, nil
   478  	}
   479  	return false, err
   480  }
   481  
   482  // IsActive checkes whether the given service is Active
   483  func (s *systemd) IsActive(serviceName string) (bool, error) {
   484  	if s.mode == GlobalUserMode {
   485  		panic("cannot call is-active with GlobalUserMode")
   486  	}
   487  	_, err := s.systemctl("--root", s.rootDir, "is-active", serviceName)
   488  	if err == nil {
   489  		return true, nil
   490  	}
   491  	// "systemctl is-active <name>" prints `inactive\n` to stderr and returns exit code 1 for inactive services
   492  	sysdErr, ok := err.(*Error)
   493  	if ok && sysdErr.exitCode > 0 && strings.TrimSpace(string(sysdErr.msg)) == "inactive" {
   494  		return false, nil
   495  	}
   496  	return false, err
   497  }
   498  
   499  // Stop the given service, and wait until it has stopped.
   500  func (s *systemd) Stop(serviceName string, timeout time.Duration) error {
   501  	if s.mode == GlobalUserMode {
   502  		panic("cannot call stop with GlobalUserMode")
   503  	}
   504  	if _, err := s.systemctl("stop", serviceName); err != nil {
   505  		return err
   506  	}
   507  
   508  	// and now wait for it to actually stop
   509  	giveup := time.NewTimer(timeout)
   510  	notify := time.NewTicker(stopNotifyDelay)
   511  	defer notify.Stop()
   512  	check := time.NewTicker(stopCheckDelay)
   513  	defer check.Stop()
   514  
   515  	firstCheck := true
   516  loop:
   517  	for {
   518  		select {
   519  		case <-giveup.C:
   520  			break loop
   521  		case <-check.C:
   522  			bs, err := s.systemctl("show", "--property=ActiveState", serviceName)
   523  			if err != nil {
   524  				return err
   525  			}
   526  			if isStopDone(bs) {
   527  				return nil
   528  			}
   529  			if !firstCheck {
   530  				continue loop
   531  			}
   532  			firstCheck = false
   533  		case <-notify.C:
   534  		}
   535  		// after notify delay or after a failed first check
   536  		s.reporter.Notify(fmt.Sprintf("Waiting for %s to stop.", serviceName))
   537  	}
   538  
   539  	return &Timeout{action: "stop", service: serviceName}
   540  }
   541  
   542  // Kill all processes of the unit with the given signal
   543  func (s *systemd) Kill(serviceName, signal, who string) error {
   544  	if s.mode == GlobalUserMode {
   545  		panic("cannot call kill with GlobalUserMode")
   546  	}
   547  	if who == "" {
   548  		who = "all"
   549  	}
   550  	_, err := s.systemctl("kill", serviceName, "-s", signal, "--kill-who="+who)
   551  	return err
   552  }
   553  
   554  // Restart the service, waiting for it to stop before starting it again.
   555  func (s *systemd) Restart(serviceName string, timeout time.Duration) error {
   556  	if s.mode == GlobalUserMode {
   557  		panic("cannot call restart with GlobalUserMode")
   558  	}
   559  	if err := s.Stop(serviceName, timeout); err != nil {
   560  		return err
   561  	}
   562  	return s.Start(serviceName)
   563  }
   564  
   565  // Error is returned if the systemd action failed
   566  type Error struct {
   567  	cmd      []string
   568  	msg      []byte
   569  	exitCode int
   570  }
   571  
   572  func (e *Error) Error() string {
   573  	return fmt.Sprintf("%v failed with exit status %d: %s", e.cmd, e.exitCode, e.msg)
   574  }
   575  
   576  // Timeout is returned if the systemd action failed to reach the
   577  // expected state in a reasonable amount of time
   578  type Timeout struct {
   579  	action  string
   580  	service string
   581  }
   582  
   583  func (e *Timeout) Error() string {
   584  	return fmt.Sprintf("%v failed to %v: timeout", e.service, e.action)
   585  }
   586  
   587  // IsTimeout checks whether the given error is a Timeout
   588  func IsTimeout(err error) bool {
   589  	_, isTimeout := err.(*Timeout)
   590  	return isTimeout
   591  }
   592  
   593  // Time returns the time the Log was received by the journal.
   594  func (l Log) Time() (time.Time, error) {
   595  	sus, ok := l["__REALTIME_TIMESTAMP"]
   596  	if !ok {
   597  		return time.Time{}, errors.New("no timestamp")
   598  	}
   599  	// according to systemd.journal-fields(7) it's microseconds as a decimal string
   600  	us, err := strconv.ParseInt(sus, 10, 64)
   601  	if err != nil {
   602  		return time.Time{}, fmt.Errorf("timestamp not a decimal number: %#v", sus)
   603  	}
   604  
   605  	return time.Unix(us/1000000, 1000*(us%1000000)).UTC(), nil
   606  }
   607  
   608  // Message of the Log, if any; otherwise, "-".
   609  func (l Log) Message() string {
   610  	if msg, ok := l["MESSAGE"]; ok {
   611  		return msg
   612  	}
   613  
   614  	return "-"
   615  }
   616  
   617  // SID is the syslog identifier of the Log, if any; otherwise, "-".
   618  func (l Log) SID() string {
   619  	if sid, ok := l["SYSLOG_IDENTIFIER"]; ok {
   620  		return sid
   621  	}
   622  
   623  	return "-"
   624  }
   625  
   626  // PID is the pid of the client pid, if any; otherwise, "-".
   627  func (l Log) PID() string {
   628  	if pid, ok := l["_PID"]; ok {
   629  		return pid
   630  	}
   631  	if pid, ok := l["SYSLOG_PID"]; ok {
   632  		return pid
   633  	}
   634  
   635  	return "-"
   636  }
   637  
   638  // MountUnitPath returns the path of a {,auto}mount unit
   639  func MountUnitPath(baseDir string) string {
   640  	escapedPath := EscapeUnitNamePath(baseDir)
   641  	return filepath.Join(dirs.SnapServicesDir, escapedPath+".mount")
   642  }
   643  
   644  // AddMountUnitFile adds/enables/starts a mount unit.
   645  func (s *systemd) AddMountUnitFile(snapName, revision, what, where, fstype string) (string, error) {
   646  	daemonReloadLock.Lock()
   647  	defer daemonReloadLock.Unlock()
   648  
   649  	options := []string{"nodev"}
   650  	if fstype == "squashfs" {
   651  		newFsType, newOptions, err := squashfs.FsType()
   652  		if err != nil {
   653  			return "", err
   654  		}
   655  		options = append(options, newOptions...)
   656  		fstype = newFsType
   657  		if release.SELinuxLevel() != release.NoSELinux {
   658  			if mountCtx := selinux.SnapMountContext(); mountCtx != "" {
   659  				options = append(options, "context="+mountCtx)
   660  			}
   661  		}
   662  	}
   663  	if osutil.IsDirectory(what) {
   664  		options = append(options, "bind")
   665  		fstype = "none"
   666  	}
   667  
   668  	c := fmt.Sprintf(`[Unit]
   669  Description=Mount unit for %s, revision %s
   670  Before=snapd.service
   671  
   672  [Mount]
   673  What=%s
   674  Where=%s
   675  Type=%s
   676  Options=%s
   677  LazyUnmount=yes
   678  
   679  [Install]
   680  WantedBy=multi-user.target
   681  `, snapName, revision, what, where, fstype, strings.Join(options, ","))
   682  
   683  	mu := MountUnitPath(where)
   684  	mountUnitName, err := filepath.Base(mu), osutil.AtomicWriteFile(mu, []byte(c), 0644, 0)
   685  	if err != nil {
   686  		return "", err
   687  	}
   688  
   689  	// we need to do a daemon-reload here to ensure that systemd really
   690  	// knows about this new mount unit file
   691  	if err := s.daemonReloadNoLock(); err != nil {
   692  		return "", err
   693  	}
   694  
   695  	if err := s.Enable(mountUnitName); err != nil {
   696  		return "", err
   697  	}
   698  	if err := s.Start(mountUnitName); err != nil {
   699  		return "", err
   700  	}
   701  
   702  	return mountUnitName, nil
   703  }
   704  
   705  func (s *systemd) RemoveMountUnitFile(mountedDir string) error {
   706  	daemonReloadLock.Lock()
   707  	defer daemonReloadLock.Unlock()
   708  
   709  	unit := MountUnitPath(dirs.StripRootDir(mountedDir))
   710  	if !osutil.FileExists(unit) {
   711  		return nil
   712  	}
   713  
   714  	// use umount -d (cleanup loopback devices) -l (lazy) to ensure that even busy mount points
   715  	// can be unmounted.
   716  	// note that the long option --lazy is not supported on trusty.
   717  	// the explicit -d is only needed on trusty.
   718  	isMounted, err := osutil.IsMounted(mountedDir)
   719  	if err != nil {
   720  		return err
   721  	}
   722  	if isMounted {
   723  		if output, err := exec.Command("umount", "-d", "-l", mountedDir).CombinedOutput(); err != nil {
   724  			return osutil.OutputErr(output, err)
   725  		}
   726  
   727  		if err := s.Stop(filepath.Base(unit), time.Duration(1*time.Second)); err != nil {
   728  			return err
   729  		}
   730  	}
   731  	if err := s.Disable(filepath.Base(unit)); err != nil {
   732  		return err
   733  	}
   734  	if err := os.Remove(unit); err != nil {
   735  		return err
   736  	}
   737  	// daemon-reload to ensure that systemd actually really
   738  	// forgets about this mount unit
   739  	if err := s.daemonReloadNoLock(); err != nil {
   740  		return err
   741  	}
   742  
   743  	return nil
   744  }