github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/systemd/systemd.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2021 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  	"encoding/json"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"os"
    30  	"os/exec"
    31  	"path/filepath"
    32  	"regexp"
    33  	"strconv"
    34  	"strings"
    35  	"sync"
    36  	"sync/atomic"
    37  	"time"
    38  
    39  	_ "github.com/snapcore/squashfuse"
    40  
    41  	"github.com/snapcore/snapd/dirs"
    42  	"github.com/snapcore/snapd/gadget/quantity"
    43  	"github.com/snapcore/snapd/osutil"
    44  	"github.com/snapcore/snapd/osutil/squashfs"
    45  	"github.com/snapcore/snapd/sandbox/selinux"
    46  )
    47  
    48  var (
    49  	// the output of "show" must match this for Stop to be done:
    50  	isStopDone = regexp.MustCompile(`(?m)\AActiveState=(?:failed|inactive)$`).Match
    51  
    52  	// how much time should Stop wait between calls to show
    53  	stopCheckDelay = 250 * time.Millisecond
    54  
    55  	// how much time should Stop wait between notifying the user of the waiting
    56  	stopNotifyDelay = 20 * time.Second
    57  
    58  	// daemonReloadLock is a package level lock to ensure that we
    59  	// do not run any `systemd daemon-reload` while a
    60  	// daemon-reload is in progress or a mount unit is
    61  	// generated/activated.
    62  	//
    63  	// See https://github.com/systemd/systemd/issues/10872 for the
    64  	// upstream systemd bug
    65  	daemonReloadLock extMutex
    66  
    67  	osutilIsMounted = osutil.IsMounted
    68  )
    69  
    70  // mu is a sync.Mutex that also supports to check if the lock is taken
    71  type extMutex struct {
    72  	lock sync.Mutex
    73  	muC  int32
    74  }
    75  
    76  // Lock acquires the mutex
    77  func (m *extMutex) Lock() {
    78  	m.lock.Lock()
    79  	atomic.AddInt32(&m.muC, 1)
    80  }
    81  
    82  // Unlock releases the mutex
    83  func (m *extMutex) Unlock() {
    84  	atomic.AddInt32(&m.muC, -1)
    85  	m.lock.Unlock()
    86  }
    87  
    88  // Taken will panic with the given error message if the lock is not
    89  // taken when this code runs. This is useful to internally check if
    90  // something is accessed without a valid lock.
    91  func (m *extMutex) Taken(errMsg string) {
    92  	if atomic.LoadInt32(&m.muC) != 1 {
    93  		panic("internal error: " + errMsg)
    94  	}
    95  }
    96  
    97  // systemctlCmd calls systemctl with the given args, returning its standard output (and wrapped error)
    98  var systemctlCmd = func(args ...string) ([]byte, error) {
    99  	// TODO: including stderr here breaks many things when systemd is in debug
   100  	// output mode, see LP #1885597
   101  	bs, err := exec.Command("systemctl", args...).CombinedOutput()
   102  	if err != nil {
   103  		exitCode, runErr := osutil.ExitCode(err)
   104  		return nil, &Error{cmd: args, exitCode: exitCode, runErr: runErr, msg: bs}
   105  	}
   106  
   107  	return bs, nil
   108  }
   109  
   110  // MockSystemctl is called from the commands to actually call out to
   111  // systemctl. It's exported so it can be overridden by testing.
   112  func MockSystemctl(f func(args ...string) ([]byte, error)) func() {
   113  	oldSystemctlCmd := systemctlCmd
   114  	systemctlCmd = f
   115  	return func() {
   116  		systemctlCmd = oldSystemctlCmd
   117  	}
   118  }
   119  
   120  // MockStopDelays is used from tests so that Stop can be less
   121  // forgiving there.
   122  func MockStopDelays(checkDelay, notifyDelay time.Duration) func() {
   123  	oldCheckDelay := stopCheckDelay
   124  	oldNotifyDelay := stopNotifyDelay
   125  	stopCheckDelay = checkDelay
   126  	stopNotifyDelay = notifyDelay
   127  	return func() {
   128  		stopCheckDelay = oldCheckDelay
   129  		stopNotifyDelay = oldNotifyDelay
   130  	}
   131  }
   132  
   133  func Available() error {
   134  	_, err := systemctlCmd("--version")
   135  	return err
   136  }
   137  
   138  // Version returns systemd version.
   139  func Version() (int, error) {
   140  	out, err := systemctlCmd("--version")
   141  	if err != nil {
   142  		return 0, err
   143  	}
   144  
   145  	// systemd version outpus is two lines - actual version and a list
   146  	// of features, e.g:
   147  	// systemd 229
   148  	// +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP ...
   149  	//
   150  	// The version string may have extra data (a case on newer ubuntu), e.g:
   151  	// systemd 245 (245.4-4ubuntu3)
   152  	r := bufio.NewScanner(bytes.NewReader(out))
   153  	r.Split(bufio.ScanWords)
   154  	var verstr string
   155  	for i := 0; i < 2; i++ {
   156  		if !r.Scan() {
   157  			return 0, fmt.Errorf("cannot read systemd version: %v", r.Err())
   158  		}
   159  		s := r.Text()
   160  		if i == 0 && s != "systemd" {
   161  			return 0, fmt.Errorf("cannot parse systemd version: expected \"systemd\", got %q", s)
   162  		}
   163  		if i == 1 {
   164  			verstr = strings.TrimSpace(s)
   165  		}
   166  	}
   167  
   168  	ver, err := strconv.Atoi(verstr)
   169  	if err != nil {
   170  		return 0, fmt.Errorf("cannot convert systemd version to number: %s", verstr)
   171  	}
   172  	return ver, nil
   173  }
   174  
   175  var osutilStreamCommand = osutil.StreamCommand
   176  
   177  // jctl calls journalctl to get the JSON logs of the given services.
   178  var jctl = func(svcs []string, n int, follow bool) (io.ReadCloser, error) {
   179  	// args will need two entries per service, plus a fixed number (give or take
   180  	// one) for the initial options.
   181  	args := make([]string, 0, 2*len(svcs)+6)        // the fixed number is 6
   182  	args = append(args, "-o", "json", "--no-pager") //   3...
   183  	if n < 0 {
   184  		args = append(args, "--no-tail") // < 2
   185  	} else {
   186  		args = append(args, "-n", strconv.Itoa(n)) // ... + 2 ...
   187  	}
   188  	if follow {
   189  		args = append(args, "-f") // ... + 1 == 6
   190  	}
   191  
   192  	for i := range svcs {
   193  		args = append(args, "-u", svcs[i]) // this is why 2×
   194  	}
   195  
   196  	return osutilStreamCommand("journalctl", args...)
   197  }
   198  
   199  func MockJournalctl(f func(svcs []string, n int, follow bool) (io.ReadCloser, error)) func() {
   200  	oldJctl := jctl
   201  	jctl = f
   202  	return func() {
   203  		jctl = oldJctl
   204  	}
   205  }
   206  
   207  // Systemd exposes a minimal interface to manage systemd via the systemctl command.
   208  type Systemd interface {
   209  	// DaemonReload reloads systemd's configuration.
   210  	DaemonReload() error
   211  	// DaemonRexec reexecutes systemd's system manager, should be
   212  	// only necessary to apply manager's configuration like
   213  	// watchdog.
   214  	DaemonReexec() error
   215  	// Enable the given service.
   216  	Enable(service string) error
   217  	// Disable the given service.
   218  	Disable(service string) error
   219  	// Start the given service or services.
   220  	Start(service ...string) error
   221  	// StartNoBlock starts the given service or services non-blocking.
   222  	StartNoBlock(service ...string) error
   223  	// Stop the given service, and wait until it has stopped.
   224  	Stop(service string, timeout time.Duration) error
   225  	// Kill all processes of the unit with the given signal.
   226  	Kill(service, signal, who string) error
   227  	// Restart the service, waiting for it to stop before starting it again.
   228  	Restart(service string, timeout time.Duration) error
   229  	// Reload or restart the service via 'systemctl reload-or-restart'
   230  	ReloadOrRestart(service string) error
   231  	// RestartAll restarts the given service using systemctl restart --all
   232  	RestartAll(service string) error
   233  	// Status fetches the status of given units. Statuses are
   234  	// returned in the same order as unit names passed in
   235  	// argument.
   236  	Status(units ...string) ([]*UnitStatus, error)
   237  	// InactiveEnterTimestamp returns the time that the given unit entered the
   238  	// inactive state as defined by the systemd docs. Specifically, this time is
   239  	// the most recent time in which the unit transitioned from deactivating
   240  	// ("Stopping") to dead ("Stopped"). It may be the zero time if this has
   241  	// never happened during the current boot, since this property is only
   242  	// tracked during the current boot. It specifically does not return a time
   243  	// that is monotonic, so the time returned here may be subject to bugs if
   244  	// there was a discontinuous time jump on the system before or during the
   245  	// unit's transition to inactive.
   246  	// TODO: incorporate this result into Status instead?
   247  	InactiveEnterTimestamp(unit string) (time.Time, error)
   248  	// IsEnabled checks whether the given service is enabled.
   249  	IsEnabled(service string) (bool, error)
   250  	// IsActive checks whether the given service is Active
   251  	IsActive(service string) (bool, error)
   252  	// LogReader returns a reader for the given services' log.
   253  	LogReader(services []string, n int, follow bool) (io.ReadCloser, error)
   254  	// AddMountUnitFile adds/enables/starts a mount unit.
   255  	AddMountUnitFile(name, revision, what, where, fstype string) (string, error)
   256  	// RemoveMountUnitFile unmounts/stops/disables/removes a mount unit.
   257  	RemoveMountUnitFile(baseDir string) error
   258  	// Mask the given service.
   259  	Mask(service string) error
   260  	// Unmask the given service.
   261  	Unmask(service string) error
   262  	// Mount requests a mount of what under where with options.
   263  	Mount(what, where string, options ...string) error
   264  	// Umount requests a mount from what or at where to be unmounted.
   265  	Umount(whatOrWhere string) error
   266  	// CurrentMemoryUsage returns the current memory usage for the specified
   267  	// unit.
   268  	CurrentMemoryUsage(unit string) (quantity.Size, error)
   269  	// CurrentTasksCount returns the number of tasks (processes, threads, kernel
   270  	// threads if enabled, etc) part of the unit, which can be a service or a
   271  	// slice.
   272  	CurrentTasksCount(unit string) (uint64, error)
   273  }
   274  
   275  // A Log is a single entry in the systemd journal.
   276  // In almost all cases, the strings map to a single string value, but as per the
   277  // manpage for journalctl, under the json format,
   278  //
   279  //    Journal entries permit non-unique fields within the same log entry. JSON
   280  //    does not allow non-unique fields within objects. Due to this, if a
   281  //    non-unique field is encountered a JSON array is used as field value,
   282  //    listing all field values as elements.
   283  //
   284  // and this snippet as well,
   285  //
   286  //    Fields containing non-printable or non-UTF8 bytes are
   287  //    encoded as arrays containing the raw bytes individually
   288  //    formatted as unsigned numbers.
   289  //
   290  // as such, we sometimes get array values which need to be handled differently,
   291  // so we manually try to decode the json for each message into different types.
   292  type Log map[string]*json.RawMessage
   293  
   294  const (
   295  	// the default target for systemd units that we generate
   296  	ServicesTarget = "multi-user.target"
   297  
   298  	// the target prerequisite for systemd units we generate
   299  	PrerequisiteTarget = "network.target"
   300  
   301  	// the default target for systemd socket units that we generate
   302  	SocketsTarget = "sockets.target"
   303  
   304  	// the default target for systemd timer units that we generate
   305  	TimersTarget = "timers.target"
   306  
   307  	// the target for systemd user session units that we generate
   308  	UserServicesTarget = "default.target"
   309  )
   310  
   311  type reporter interface {
   312  	Notify(string)
   313  }
   314  
   315  // New returns a Systemd that uses the default root directory and omits
   316  // --root argument when executing systemctl.
   317  func New(mode InstanceMode, rep reporter) Systemd {
   318  	return &systemd{mode: mode, reporter: rep}
   319  }
   320  
   321  // NewUnderRoot returns a Systemd that operates on the given rootdir.
   322  func NewUnderRoot(rootDir string, mode InstanceMode, rep reporter) Systemd {
   323  	return &systemd{rootDir: rootDir, mode: mode, reporter: rep}
   324  }
   325  
   326  // NewEmulationMode returns a Systemd that runs in emulation mode where
   327  // systemd is not really called, but instead its functions are emulated
   328  // by other means.
   329  func NewEmulationMode(rootDir string) Systemd {
   330  	if rootDir == "" {
   331  		rootDir = dirs.GlobalRootDir
   332  	}
   333  	return &emulation{
   334  		rootDir: rootDir,
   335  	}
   336  }
   337  
   338  // InstanceMode determines which instance of systemd to control.
   339  //
   340  // SystemMode refers to the system instance (i.e. pid 1).  UserMode
   341  // refers to the instance launched to manage the user's desktop
   342  // session.  GlobalUserMode controls configuration respected by all
   343  // user instances on the system.
   344  //
   345  // As GlobalUserMode does not refer to a single instance of systemd,
   346  // some operations are not supported such as starting and stopping
   347  // daemons.
   348  type InstanceMode int
   349  
   350  const (
   351  	SystemMode InstanceMode = iota
   352  	UserMode
   353  	GlobalUserMode
   354  )
   355  
   356  type systemd struct {
   357  	rootDir  string
   358  	reporter reporter
   359  	mode     InstanceMode
   360  }
   361  
   362  func (s *systemd) systemctl(args ...string) ([]byte, error) {
   363  	switch s.mode {
   364  	case SystemMode:
   365  	case UserMode:
   366  		args = append([]string{"--user"}, args...)
   367  	case GlobalUserMode:
   368  		args = append([]string{"--user", "--global"}, args...)
   369  	default:
   370  		panic("unknown InstanceMode")
   371  	}
   372  	return systemctlCmd(args...)
   373  }
   374  
   375  func (s *systemd) DaemonReload() error {
   376  	if s.mode == GlobalUserMode {
   377  		panic("cannot call daemon-reload with GlobalUserMode")
   378  	}
   379  	daemonReloadLock.Lock()
   380  	defer daemonReloadLock.Unlock()
   381  
   382  	return s.daemonReloadNoLock()
   383  }
   384  
   385  func (s *systemd) daemonReloadNoLock() error {
   386  	daemonReloadLock.Taken("cannot use daemon-reload without lock")
   387  
   388  	_, err := s.systemctl("daemon-reload")
   389  	return err
   390  }
   391  
   392  func (s *systemd) DaemonReexec() error {
   393  	if s.mode == GlobalUserMode {
   394  		panic("cannot call daemon-reexec with GlobalUserMode")
   395  	}
   396  	daemonReloadLock.Lock()
   397  	defer daemonReloadLock.Unlock()
   398  
   399  	_, err := s.systemctl("daemon-reexec")
   400  	return err
   401  }
   402  
   403  func (s *systemd) Enable(serviceName string) error {
   404  	var err error
   405  	if s.rootDir != "" {
   406  		_, err = s.systemctl("--root", s.rootDir, "enable", serviceName)
   407  	} else {
   408  		_, err = s.systemctl("enable", serviceName)
   409  	}
   410  	return err
   411  }
   412  
   413  func (s *systemd) Unmask(serviceName string) error {
   414  	var err error
   415  	if s.rootDir != "" {
   416  		_, err = s.systemctl("--root", s.rootDir, "unmask", serviceName)
   417  	} else {
   418  		_, err = s.systemctl("unmask", serviceName)
   419  	}
   420  	return err
   421  }
   422  
   423  func (s *systemd) Disable(serviceName string) error {
   424  	var err error
   425  	if s.rootDir != "" {
   426  		_, err = s.systemctl("--root", s.rootDir, "disable", serviceName)
   427  	} else {
   428  		_, err = s.systemctl("disable", serviceName)
   429  	}
   430  	return err
   431  }
   432  
   433  func (s *systemd) Mask(serviceName string) error {
   434  	var err error
   435  	if s.rootDir != "" {
   436  		_, err = s.systemctl("--root", s.rootDir, "mask", serviceName)
   437  	} else {
   438  		_, err = s.systemctl("mask", serviceName)
   439  	}
   440  	return err
   441  }
   442  
   443  func (s *systemd) Start(serviceNames ...string) error {
   444  	if s.mode == GlobalUserMode {
   445  		panic("cannot call start with GlobalUserMode")
   446  	}
   447  	_, err := s.systemctl(append([]string{"start"}, serviceNames...)...)
   448  	return err
   449  }
   450  
   451  func (s *systemd) StartNoBlock(serviceNames ...string) error {
   452  	if s.mode == GlobalUserMode {
   453  		panic("cannot call start with GlobalUserMode")
   454  	}
   455  	_, err := s.systemctl(append([]string{"start", "--no-block"}, serviceNames...)...)
   456  	return err
   457  }
   458  
   459  func (*systemd) LogReader(serviceNames []string, n int, follow bool) (io.ReadCloser, error) {
   460  	return jctl(serviceNames, n, follow)
   461  }
   462  
   463  var statusregex = regexp.MustCompile(`(?m)^(?:(.+?)=(.*)|(.*))?$`)
   464  
   465  type UnitStatus struct {
   466  	Daemon   string
   467  	UnitName string
   468  	Enabled  bool
   469  	Active   bool
   470  	// Installed is false if the queried unit doesn't exist.
   471  	Installed bool
   472  }
   473  
   474  var baseProperties = []string{"Id", "ActiveState", "UnitFileState"}
   475  var extendedProperties = []string{"Id", "ActiveState", "UnitFileState", "Type"}
   476  var unitProperties = map[string][]string{
   477  	".timer":  baseProperties,
   478  	".socket": baseProperties,
   479  	// in service units, Type is the daemon type
   480  	".service": extendedProperties,
   481  	// in mount units, Type is the fs type
   482  	".mount": extendedProperties,
   483  }
   484  
   485  func (s *systemd) getUnitStatus(properties []string, unitNames []string) ([]*UnitStatus, error) {
   486  	cmd := make([]string, len(unitNames)+2)
   487  	cmd[0] = "show"
   488  	// ask for all properties, regardless of unit type
   489  	cmd[1] = "--property=" + strings.Join(properties, ",")
   490  	copy(cmd[2:], unitNames)
   491  	bs, err := s.systemctl(cmd...)
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  
   496  	sts := make([]*UnitStatus, 0, len(unitNames))
   497  	cur := &UnitStatus{}
   498  	seen := map[string]bool{}
   499  
   500  	for _, bs := range statusregex.FindAllSubmatch(bs, -1) {
   501  		if len(bs[0]) == 0 {
   502  			// systemctl separates data pertaining to particular services by an empty line
   503  			unitType := filepath.Ext(cur.UnitName)
   504  			expected := unitProperties[unitType]
   505  			if expected == nil {
   506  				expected = baseProperties
   507  			}
   508  
   509  			missing := make([]string, 0, len(expected))
   510  			for _, k := range expected {
   511  				if !seen[k] {
   512  					missing = append(missing, k)
   513  				}
   514  			}
   515  			if len(missing) > 0 {
   516  				return nil, fmt.Errorf("cannot get unit %q status: missing %s in ‘systemctl show’ output", cur.UnitName, strings.Join(missing, ", "))
   517  			}
   518  			sts = append(sts, cur)
   519  			if len(sts) > len(unitNames) {
   520  				break // wut
   521  			}
   522  			if cur.UnitName != unitNames[len(sts)-1] {
   523  				return nil, fmt.Errorf("cannot get unit status: queried status of %q but got status of %q", unitNames[len(sts)-1], cur.UnitName)
   524  			}
   525  
   526  			cur = &UnitStatus{}
   527  			seen = map[string]bool{}
   528  			continue
   529  		}
   530  		if len(bs[3]) > 0 {
   531  			return nil, fmt.Errorf("cannot get unit status: bad line %q in ‘systemctl show’ output", bs[3])
   532  		}
   533  		k := string(bs[1])
   534  		v := string(bs[2])
   535  
   536  		if v == "" && k != "UnitFileState" && k != "Type" {
   537  			return nil, fmt.Errorf("cannot get unit status: empty field %q in ‘systemctl show’ output", k)
   538  		}
   539  
   540  		switch k {
   541  		case "Id":
   542  			cur.UnitName = v
   543  		case "Type":
   544  			cur.Daemon = v
   545  		case "ActiveState":
   546  			// made to match “systemctl is-active” behaviour, at least at systemd 229
   547  			cur.Active = v == "active" || v == "reloading"
   548  		case "UnitFileState":
   549  			// "static" means it can't be disabled
   550  			cur.Enabled = v == "enabled" || v == "static"
   551  			cur.Installed = v != ""
   552  		default:
   553  			return nil, fmt.Errorf("cannot get unit status: unexpected field %q in ‘systemctl show’ output", k)
   554  		}
   555  
   556  		if seen[k] {
   557  			return nil, fmt.Errorf("cannot get unit status: duplicate field %q in ‘systemctl show’ output", k)
   558  		}
   559  		seen[k] = true
   560  	}
   561  
   562  	if len(sts) != len(unitNames) {
   563  		return nil, fmt.Errorf("cannot get unit status: expected %d results, got %d", len(unitNames), len(sts))
   564  	}
   565  	return sts, nil
   566  }
   567  
   568  func (s *systemd) getGlobalUserStatus(unitNames ...string) ([]*UnitStatus, error) {
   569  	// As there is one instance per user, the active state does
   570  	// not make sense.  We can determine the global "enabled"
   571  	// state of the services though.
   572  	cmd := append([]string{"is-enabled"}, unitNames...)
   573  	if s.rootDir != "" {
   574  		cmd = append([]string{"--root", s.rootDir}, cmd...)
   575  	}
   576  	bs, err := s.systemctl(cmd...)
   577  	if err != nil {
   578  		// is-enabled returns non-zero if no units are
   579  		// enabled.  We still need to examine the output to
   580  		// track the other units.
   581  		sysdErr := err.(systemctlError)
   582  		bs = sysdErr.Msg()
   583  	}
   584  
   585  	results := bytes.Split(bytes.Trim(bs, "\n"), []byte("\n"))
   586  	if len(results) != len(unitNames) {
   587  		return nil, fmt.Errorf("cannot get enabled status of services: expected %d results, got %d", len(unitNames), len(results))
   588  	}
   589  
   590  	sts := make([]*UnitStatus, len(unitNames))
   591  	for i, line := range results {
   592  		sts[i] = &UnitStatus{
   593  			UnitName: unitNames[i],
   594  			Enabled:  bytes.Equal(line, []byte("enabled")) || bytes.Equal(line, []byte("static")),
   595  		}
   596  	}
   597  	return sts, nil
   598  }
   599  
   600  func (s *systemd) getPropertyStringValue(unit, key string) (string, error) {
   601  	// XXX: ignore stderr of systemctl command to avoid further infractions
   602  	//      around LP #1885597
   603  	out, err := s.systemctl("show", "--property", key, unit)
   604  	if err != nil {
   605  		return "", osutil.OutputErr(out, err)
   606  	}
   607  	cleanVal := strings.TrimSpace(string(out))
   608  
   609  	// strip the <property>= from the output
   610  	splitVal := strings.SplitN(cleanVal, "=", 2)
   611  	if len(splitVal) != 2 {
   612  		return "", fmt.Errorf("invalid property format from systemd for %s (got %s)", key, cleanVal)
   613  	}
   614  
   615  	return strings.TrimSpace(splitVal[1]), nil
   616  }
   617  
   618  var errNotSet = errors.New("property value is not available")
   619  
   620  func (s *systemd) getPropertyUintValue(unit, key string) (uint64, error) {
   621  	valStr, err := s.getPropertyStringValue(unit, key)
   622  	if err != nil {
   623  		return 0, err
   624  	}
   625  
   626  	// if the unit is inactive or doesn't exist, the value can be reported as
   627  	// "[not set]"
   628  	if valStr == "[not set]" {
   629  		return 0, errNotSet
   630  	}
   631  
   632  	intVal, err := strconv.ParseUint(valStr, 10, 64)
   633  	if err != nil {
   634  		return 0, fmt.Errorf("invalid property value from systemd for %s: cannot parse %q as an integer", key, valStr)
   635  	}
   636  
   637  	return intVal, nil
   638  }
   639  
   640  func (s *systemd) CurrentTasksCount(unit string) (uint64, error) {
   641  	tasksCount, err := s.getPropertyUintValue(unit, "TasksCurrent")
   642  	if err != nil && err != errNotSet {
   643  		return 0, err
   644  	}
   645  
   646  	if err == errNotSet {
   647  		return 0, fmt.Errorf("tasks count unavailable")
   648  	}
   649  
   650  	return tasksCount, nil
   651  }
   652  
   653  func (s *systemd) CurrentMemoryUsage(unit string) (quantity.Size, error) {
   654  	memBytes, err := s.getPropertyUintValue(unit, "MemoryCurrent")
   655  	if err != nil && err != errNotSet {
   656  		return 0, err
   657  	}
   658  
   659  	if err == errNotSet {
   660  		return 0, fmt.Errorf("memory usage unavailable")
   661  	}
   662  
   663  	return quantity.Size(memBytes), nil
   664  }
   665  
   666  func (s *systemd) InactiveEnterTimestamp(unit string) (time.Time, error) {
   667  	timeStr, err := s.getPropertyStringValue(unit, "InactiveEnterTimestamp")
   668  	if err != nil {
   669  		return time.Time{}, err
   670  	}
   671  
   672  	if timeStr == "" {
   673  		return time.Time{}, nil
   674  	}
   675  
   676  	// finally parse the time string
   677  	inactiveEnterTime, err := time.Parse("Mon 2006-01-02 15:04:05 MST", timeStr)
   678  	if err != nil {
   679  		return time.Time{}, fmt.Errorf("internal error: systemctl time output (%s) is malformed", timeStr)
   680  	}
   681  	return inactiveEnterTime, nil
   682  }
   683  
   684  func (s *systemd) Status(unitNames ...string) ([]*UnitStatus, error) {
   685  	if s.mode == GlobalUserMode {
   686  		return s.getGlobalUserStatus(unitNames...)
   687  	}
   688  	unitToStatus := make(map[string]*UnitStatus, len(unitNames))
   689  
   690  	var limitedUnits []string
   691  	var extendedUnits []string
   692  
   693  	for _, name := range unitNames {
   694  		if strings.HasSuffix(name, ".timer") || strings.HasSuffix(name, ".socket") {
   695  			limitedUnits = append(limitedUnits, name)
   696  		} else {
   697  			extendedUnits = append(extendedUnits, name)
   698  		}
   699  	}
   700  
   701  	for _, set := range []struct {
   702  		units      []string
   703  		properties []string
   704  	}{
   705  		{units: extendedUnits, properties: extendedProperties},
   706  		{units: limitedUnits, properties: baseProperties},
   707  	} {
   708  		if len(set.units) == 0 {
   709  			continue
   710  		}
   711  		sts, err := s.getUnitStatus(set.properties, set.units)
   712  		if err != nil {
   713  			return nil, err
   714  		}
   715  		for _, status := range sts {
   716  			unitToStatus[status.UnitName] = status
   717  		}
   718  	}
   719  
   720  	// unpack to preserve the promised order
   721  	sts := make([]*UnitStatus, len(unitNames))
   722  	for idx, name := range unitNames {
   723  		var ok bool
   724  		sts[idx], ok = unitToStatus[name]
   725  		if !ok {
   726  			return nil, fmt.Errorf("cannot determine status of unit %q", name)
   727  		}
   728  	}
   729  
   730  	return sts, nil
   731  }
   732  
   733  func (s *systemd) IsEnabled(serviceName string) (bool, error) {
   734  	var err error
   735  	if s.rootDir != "" {
   736  		_, err = s.systemctl("--root", s.rootDir, "is-enabled", serviceName)
   737  	} else {
   738  		_, err = s.systemctl("is-enabled", serviceName)
   739  	}
   740  	if err == nil {
   741  		return true, nil
   742  	}
   743  	// "systemctl is-enabled <name>" prints `disabled\n` to stderr and returns exit code 1
   744  	// for disabled services
   745  	sysdErr, ok := err.(systemctlError)
   746  	if ok && sysdErr.ExitCode() == 1 && strings.TrimSpace(string(sysdErr.Msg())) == "disabled" {
   747  		return false, nil
   748  	}
   749  	return false, err
   750  }
   751  
   752  func (s *systemd) IsActive(serviceName string) (bool, error) {
   753  	if s.mode == GlobalUserMode {
   754  		panic("cannot call is-active with GlobalUserMode")
   755  	}
   756  	var err error
   757  	if s.rootDir != "" {
   758  		_, err = s.systemctl("--root", s.rootDir, "is-active", serviceName)
   759  	} else {
   760  		_, err = s.systemctl("is-active", serviceName)
   761  	}
   762  	if err == nil {
   763  		return true, nil
   764  	}
   765  	// "systemctl is-active <name>" returns exit code 3 for inactive services,
   766  	// the stderr output may be `unknown\n` for services that were not found,
   767  	// `inactive\n` for services that are inactive (or not found for some
   768  	// systemd versions), or `failed\n` for services that are in a failed state;
   769  	// nevertheless make sure to check any non-0 exit code
   770  	sysdErr, ok := err.(systemctlError)
   771  	if ok {
   772  		switch strings.TrimSpace(string(sysdErr.Msg())) {
   773  		case "inactive", "failed", "unknown":
   774  			return false, nil
   775  		}
   776  	}
   777  	return false, err
   778  }
   779  
   780  func (s *systemd) Stop(serviceName string, timeout time.Duration) error {
   781  	if s.mode == GlobalUserMode {
   782  		panic("cannot call stop with GlobalUserMode")
   783  	}
   784  	if _, err := s.systemctl("stop", serviceName); err != nil {
   785  		return err
   786  	}
   787  
   788  	// and now wait for it to actually stop
   789  	giveup := time.NewTimer(timeout)
   790  	notify := time.NewTicker(stopNotifyDelay)
   791  	defer notify.Stop()
   792  	check := time.NewTicker(stopCheckDelay)
   793  	defer check.Stop()
   794  
   795  	firstCheck := true
   796  loop:
   797  	for {
   798  		select {
   799  		case <-giveup.C:
   800  			break loop
   801  		case <-check.C:
   802  			bs, err := s.systemctl("show", "--property=ActiveState", serviceName)
   803  			if err != nil {
   804  				return err
   805  			}
   806  			if isStopDone(bs) {
   807  				return nil
   808  			}
   809  			if !firstCheck {
   810  				continue loop
   811  			}
   812  			firstCheck = false
   813  		case <-notify.C:
   814  		}
   815  		// after notify delay or after a failed first check
   816  		s.reporter.Notify(fmt.Sprintf("Waiting for %s to stop.", serviceName))
   817  	}
   818  
   819  	return &Timeout{action: "stop", service: serviceName}
   820  }
   821  
   822  func (s *systemd) Kill(serviceName, signal, who string) error {
   823  	if s.mode == GlobalUserMode {
   824  		panic("cannot call kill with GlobalUserMode")
   825  	}
   826  	if who == "" {
   827  		who = "all"
   828  	}
   829  	_, err := s.systemctl("kill", serviceName, "-s", signal, "--kill-who="+who)
   830  	return err
   831  }
   832  
   833  func (s *systemd) Restart(serviceName string, timeout time.Duration) error {
   834  	if s.mode == GlobalUserMode {
   835  		panic("cannot call restart with GlobalUserMode")
   836  	}
   837  	if err := s.Stop(serviceName, timeout); err != nil {
   838  		return err
   839  	}
   840  	return s.Start(serviceName)
   841  }
   842  
   843  func (s *systemd) RestartAll(serviceName string) error {
   844  	if s.mode == GlobalUserMode {
   845  		panic("cannot call restart with GlobalUserMode")
   846  	}
   847  	_, err := s.systemctl("restart", serviceName, "--all")
   848  	return err
   849  }
   850  
   851  type systemctlError interface {
   852  	Msg() []byte
   853  	ExitCode() int
   854  	Error() string
   855  }
   856  
   857  // Error is returned if the systemd action failed
   858  type Error struct {
   859  	cmd      []string
   860  	msg      []byte
   861  	exitCode int
   862  	runErr   error
   863  }
   864  
   865  func (e *Error) Msg() []byte {
   866  	return e.msg
   867  }
   868  
   869  func (e *Error) ExitCode() int {
   870  	return e.exitCode
   871  }
   872  
   873  func (e *Error) Error() string {
   874  	var msg string
   875  	if len(e.msg) > 0 {
   876  		msg = fmt.Sprintf(": %s", e.msg)
   877  	}
   878  	if e.runErr != nil {
   879  		return fmt.Sprintf("systemctl command %v failed with: %v%s", e.cmd, e.runErr, msg)
   880  	}
   881  	return fmt.Sprintf("systemctl command %v failed with exit status %d%s", e.cmd, e.exitCode, msg)
   882  }
   883  
   884  // Timeout is returned if the systemd action failed to reach the
   885  // expected state in a reasonable amount of time
   886  type Timeout struct {
   887  	action  string
   888  	service string
   889  }
   890  
   891  func (e *Timeout) Error() string {
   892  	return fmt.Sprintf("%v failed to %v: timeout", e.service, e.action)
   893  }
   894  
   895  // IsTimeout checks whether the given error is a Timeout
   896  func IsTimeout(err error) bool {
   897  	_, isTimeout := err.(*Timeout)
   898  	return isTimeout
   899  }
   900  
   901  func (l Log) parseLogRawMessageString(key string, sliceHandler func([]string) (string, error)) (string, error) {
   902  	valObject, ok := l[key]
   903  	if !ok {
   904  		// NOTE: journalctl says that sometimes if a json string would be too
   905  		// large null is returned, so we may miss a message here
   906  		return "", fmt.Errorf("key %q missing from message", key)
   907  	}
   908  	if valObject == nil {
   909  		// NOTE: journalctl says that sometimes if a json string would be too
   910  		// large null is returned, so in this case the message may be truncated
   911  		return "", fmt.Errorf("message key %q truncated", key)
   912  	}
   913  
   914  	// first try normal string
   915  	s := ""
   916  	err := json.Unmarshal(*valObject, &s)
   917  	if err == nil {
   918  		return s, nil
   919  	}
   920  
   921  	// next up, try a list of bytes that is utf-8 next, this is the case if
   922  	// journald thinks the output is not valid utf-8 or is not printable ascii
   923  	b := []byte{}
   924  	err = json.Unmarshal(*valObject, &b)
   925  	if err == nil {
   926  		// we have an array of bytes here, and there is a chance that it is
   927  		// not valid utf-8, but since this feature is used in snapd to present
   928  		// user-facing messages, we simply let Go do its best to turn the bytes
   929  		// into a string, with the chance that some byte sequences that are
   930  		// invalid end up getting replaced with Go's hex encoding of the byte
   931  		// sequence.
   932  		// Programs that are concerned with reading the exact sequence of
   933  		// characters or binary data, etc. should probably talk to journald
   934  		// directly instead of going through snapd using this API.
   935  		return string(b), nil
   936  	}
   937  
   938  	// next, try slice of slices of bytes
   939  	bb := [][]byte{}
   940  	err = json.Unmarshal(*valObject, &bb)
   941  	if err == nil {
   942  		// turn the slice of slices of bytes into a slice of strings to call the
   943  		// handler on it, see above about how invalid utf8 bytes are handled
   944  		l := make([]string, 0, len(bb))
   945  		for _, r := range bb {
   946  			l = append(l, string(r))
   947  		}
   948  		return sliceHandler(l)
   949  	}
   950  
   951  	// finally try list of strings
   952  	stringSlice := []string{}
   953  	err = json.Unmarshal(*valObject, &stringSlice)
   954  	if err == nil {
   955  		// if the slice is of length 1, just promote it to a plain scalar string
   956  		if len(stringSlice) == 1 {
   957  			return stringSlice[0], nil
   958  		}
   959  		// otherwise let the caller handle it
   960  		return sliceHandler(stringSlice)
   961  	}
   962  
   963  	// some examples of input data that would get here would be a raw scalar
   964  	// number, or a JSON map object, etc.
   965  	return "", fmt.Errorf("unsupported JSON encoding format")
   966  }
   967  
   968  // Time returns the time the Log was received by the journal.
   969  func (l Log) Time() (time.Time, error) {
   970  	// since the __REALTIME_TIMESTAMP is underscored and thus "trusted" by
   971  	// systemd, we assume that it will always be a valid string and not try to
   972  	// handle any possible array cases
   973  	sus, err := l.parseLogRawMessageString("__REALTIME_TIMESTAMP", func([]string) (string, error) {
   974  		return "", errors.New("no timestamp")
   975  	})
   976  	if err != nil {
   977  		return time.Time{}, err
   978  	}
   979  
   980  	// according to systemd.journal-fields(7) it's microseconds as a decimal string
   981  	us, err := strconv.ParseInt(sus, 10, 64)
   982  	if err != nil {
   983  		return time.Time{}, fmt.Errorf("timestamp not a decimal number: %#v", sus)
   984  	}
   985  
   986  	return time.Unix(us/1000000, 1000*(us%1000000)).UTC(), nil
   987  }
   988  
   989  // Message of the Log, if any; otherwise, "-".
   990  func (l Log) Message() string {
   991  	// for MESSAGE, if there are multiple strings, just concatenate them with a
   992  	// newline to keep as much data from journald as possible
   993  	msg, err := l.parseLogRawMessageString("MESSAGE", func(stringSlice []string) (string, error) {
   994  		return strings.Join(stringSlice, "\n"), nil
   995  	})
   996  	if err != nil {
   997  		if _, ok := l["MESSAGE"]; !ok {
   998  			// if the MESSAGE key is just missing, then return "-"
   999  			return "-"
  1000  		}
  1001  		return fmt.Sprintf("- (error decoding original message: %v)", err)
  1002  	}
  1003  	return msg
  1004  }
  1005  
  1006  // SID is the syslog identifier of the Log, if any; otherwise, "-".
  1007  func (l Log) SID() string {
  1008  	// if there are multiple SYSLOG_IDENTIFIER values, just act like there was
  1009  	// not one, making an arbitrary choice here is probably not helpful
  1010  	sid, err := l.parseLogRawMessageString("SYSLOG_IDENTIFIER", func([]string) (string, error) {
  1011  		return "", fmt.Errorf("multiple identifiers not supported")
  1012  	})
  1013  	if err != nil || sid == "" {
  1014  		return "-"
  1015  	}
  1016  	return sid
  1017  }
  1018  
  1019  // PID is the pid of the client pid, if any; otherwise, "-".
  1020  func (l Log) PID() string {
  1021  	// look for _PID first as that is underscored and thus "trusted" from
  1022  	// systemd, also don't support multiple arrays if we find then
  1023  	multiplePIDsErr := fmt.Errorf("multiple pids not supported")
  1024  	pid, err := l.parseLogRawMessageString("_PID", func([]string) (string, error) {
  1025  		return "", multiplePIDsErr
  1026  	})
  1027  	if err == nil && pid != "" {
  1028  		return pid
  1029  	}
  1030  
  1031  	pid, err = l.parseLogRawMessageString("SYSLOG_PID", func([]string) (string, error) {
  1032  		return "", multiplePIDsErr
  1033  	})
  1034  	if err == nil && pid != "" {
  1035  		return pid
  1036  	}
  1037  
  1038  	return "-"
  1039  }
  1040  
  1041  // MountUnitPath returns the path of a {,auto}mount unit
  1042  func MountUnitPath(baseDir string) string {
  1043  	escapedPath := EscapeUnitNamePath(baseDir)
  1044  	return filepath.Join(dirs.SnapServicesDir, escapedPath+".mount")
  1045  }
  1046  
  1047  var squashfsFsType = squashfs.FsType
  1048  
  1049  // XXX: After=zfs-mount.service is a workaround for LP: #1922293 (a problem
  1050  // with order of mounting most likely related to zfs-linux and/or systemd).
  1051  var mountUnitTemplate = `[Unit]
  1052  Description=Mount unit for %s, revision %s
  1053  Before=snapd.service
  1054  After=zfs-mount.service
  1055  
  1056  [Mount]
  1057  What=%s
  1058  Where=%s
  1059  Type=%s
  1060  Options=%s
  1061  LazyUnmount=yes
  1062  
  1063  [Install]
  1064  WantedBy=multi-user.target
  1065  `
  1066  
  1067  func writeMountUnitFile(snapName, revision, what, where, fstype string, options []string) (mountUnitName string, err error) {
  1068  	content := fmt.Sprintf(mountUnitTemplate, snapName, revision, what, where, fstype, strings.Join(options, ","))
  1069  	mu := MountUnitPath(where)
  1070  	mountUnitName, err = filepath.Base(mu), osutil.AtomicWriteFile(mu, []byte(content), 0644, 0)
  1071  	if err != nil {
  1072  		return "", err
  1073  	}
  1074  	return mountUnitName, nil
  1075  }
  1076  
  1077  func fsMountOptions(fstype string) []string {
  1078  	options := []string{"nodev"}
  1079  	if fstype == "squashfs" {
  1080  		if selinux.ProbedLevel() != selinux.Unsupported {
  1081  			if mountCtx := selinux.SnapMountContext(); mountCtx != "" {
  1082  				options = append(options, "context="+mountCtx)
  1083  			}
  1084  		}
  1085  	}
  1086  	return options
  1087  }
  1088  
  1089  // hostFsTypeAndMountOptions returns filesystem type and options to actually
  1090  // mount the given fstype at runtime, i.e. it determines if fuse should be used
  1091  // for squashfs.
  1092  func hostFsTypeAndMountOptions(fstype string) (hostFsType string, options []string) {
  1093  	options = fsMountOptions(fstype)
  1094  	hostFsType = fstype
  1095  	if fstype == "squashfs" {
  1096  		newFsType, newOptions := squashfsFsType()
  1097  		options = append(options, newOptions...)
  1098  		hostFsType = newFsType
  1099  	}
  1100  	return hostFsType, options
  1101  }
  1102  
  1103  func (s *systemd) AddMountUnitFile(snapName, revision, what, where, fstype string) (string, error) {
  1104  	daemonReloadLock.Lock()
  1105  	defer daemonReloadLock.Unlock()
  1106  
  1107  	hostFsType, options := hostFsTypeAndMountOptions(fstype)
  1108  	if osutil.IsDirectory(what) {
  1109  		options = append(options, "bind")
  1110  		hostFsType = "none"
  1111  	}
  1112  	mountUnitName, err := writeMountUnitFile(snapName, revision, what, where, hostFsType, options)
  1113  	if err != nil {
  1114  		return "", err
  1115  	}
  1116  
  1117  	// we need to do a daemon-reload here to ensure that systemd really
  1118  	// knows about this new mount unit file
  1119  	if err := s.daemonReloadNoLock(); err != nil {
  1120  		return "", err
  1121  	}
  1122  
  1123  	if err := s.Enable(mountUnitName); err != nil {
  1124  		return "", err
  1125  	}
  1126  	if err := s.Start(mountUnitName); err != nil {
  1127  		return "", err
  1128  	}
  1129  
  1130  	return mountUnitName, nil
  1131  }
  1132  
  1133  func (s *systemd) RemoveMountUnitFile(mountedDir string) error {
  1134  	daemonReloadLock.Lock()
  1135  	defer daemonReloadLock.Unlock()
  1136  
  1137  	unit := MountUnitPath(dirs.StripRootDir(mountedDir))
  1138  	if !osutil.FileExists(unit) {
  1139  		return nil
  1140  	}
  1141  
  1142  	// use umount -d (cleanup loopback devices) -l (lazy) to ensure that even busy mount points
  1143  	// can be unmounted.
  1144  	// note that the long option --lazy is not supported on trusty.
  1145  	// the explicit -d is only needed on trusty.
  1146  	isMounted, err := osutilIsMounted(mountedDir)
  1147  	if err != nil {
  1148  		return err
  1149  	}
  1150  	if isMounted {
  1151  		if output, err := exec.Command("umount", "-d", "-l", mountedDir).CombinedOutput(); err != nil {
  1152  			return osutil.OutputErr(output, err)
  1153  		}
  1154  
  1155  		if err := s.Stop(filepath.Base(unit), time.Duration(1*time.Second)); err != nil {
  1156  			return err
  1157  		}
  1158  	}
  1159  	if err := s.Disable(filepath.Base(unit)); err != nil {
  1160  		return err
  1161  	}
  1162  	if err := os.Remove(unit); err != nil {
  1163  		return err
  1164  	}
  1165  	// daemon-reload to ensure that systemd actually really
  1166  	// forgets about this mount unit
  1167  	if err := s.daemonReloadNoLock(); err != nil {
  1168  		return err
  1169  	}
  1170  
  1171  	return nil
  1172  }
  1173  
  1174  func (s *systemd) ReloadOrRestart(serviceName string) error {
  1175  	if s.mode == GlobalUserMode {
  1176  		panic("cannot call restart with GlobalUserMode")
  1177  	}
  1178  	_, err := s.systemctl("reload-or-restart", serviceName)
  1179  	return err
  1180  }
  1181  
  1182  func (s *systemd) Mount(what, where string, options ...string) error {
  1183  	args := make([]string, 0, 2+len(options))
  1184  	if len(options) > 0 {
  1185  		args = append(args, options...)
  1186  	}
  1187  	args = append(args, what, where)
  1188  	if output, err := exec.Command("systemd-mount", args...).CombinedOutput(); err != nil {
  1189  		return osutil.OutputErr(output, err)
  1190  	}
  1191  	return nil
  1192  }
  1193  
  1194  func (s *systemd) Umount(whatOrWhere string) error {
  1195  	if output, err := exec.Command("systemd-mount", "--umount", whatOrWhere).CombinedOutput(); err != nil {
  1196  		return osutil.OutputErr(output, err)
  1197  	}
  1198  	return nil
  1199  }