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