github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/systemd/systemd_test.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_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"os"
    29  	"path/filepath"
    30  	"strconv"
    31  	"testing"
    32  	"time"
    33  
    34  	. "gopkg.in/check.v1"
    35  
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/gadget/quantity"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/osutil/squashfs"
    40  	"github.com/snapcore/snapd/sandbox/selinux"
    41  	"github.com/snapcore/snapd/testutil"
    42  
    43  	. "github.com/snapcore/snapd/systemd"
    44  )
    45  
    46  type testreporter struct {
    47  	msgs []string
    48  }
    49  
    50  func (tr *testreporter) Notify(msg string) {
    51  	tr.msgs = append(tr.msgs, msg)
    52  }
    53  
    54  // Hook up check.v1 into the "go test" runner
    55  func Test(t *testing.T) { TestingT(t) }
    56  
    57  // systemd's testsuite
    58  type SystemdTestSuite struct {
    59  	i      int
    60  	argses [][]string
    61  	errors []error
    62  	outs   [][]byte
    63  
    64  	j        int
    65  	jns      []string
    66  	jsvcs    [][]string
    67  	jouts    [][]byte
    68  	jerrs    []error
    69  	jfollows []bool
    70  
    71  	rep *testreporter
    72  
    73  	restoreSystemctl  func()
    74  	restoreJournalctl func()
    75  	restoreSELinux    func()
    76  }
    77  
    78  var _ = Suite(&SystemdTestSuite{})
    79  
    80  func (s *SystemdTestSuite) SetUpTest(c *C) {
    81  	dirs.SetRootDir(c.MkDir())
    82  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
    83  	c.Assert(err, IsNil)
    84  	c.Assert(os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755), IsNil)
    85  
    86  	// force UTC timezone, for reproducible timestamps
    87  	os.Setenv("TZ", "")
    88  
    89  	s.restoreSystemctl = MockSystemctl(s.myRun)
    90  	s.i = 0
    91  	s.argses = nil
    92  	s.errors = nil
    93  	s.outs = nil
    94  
    95  	s.restoreJournalctl = MockJournalctl(s.myJctl)
    96  	s.j = 0
    97  	s.jns = nil
    98  	s.jsvcs = nil
    99  	s.jouts = nil
   100  	s.jerrs = nil
   101  	s.jfollows = nil
   102  
   103  	s.rep = new(testreporter)
   104  
   105  	s.restoreSELinux = selinux.MockIsEnabled(func() (bool, error) { return false, nil })
   106  }
   107  
   108  func (s *SystemdTestSuite) TearDownTest(c *C) {
   109  	s.restoreSystemctl()
   110  	s.restoreJournalctl()
   111  	s.restoreSELinux()
   112  }
   113  
   114  func (s *SystemdTestSuite) myRun(args ...string) (out []byte, err error) {
   115  	s.argses = append(s.argses, args)
   116  	if s.i < len(s.outs) {
   117  		out = s.outs[s.i]
   118  	}
   119  	if s.i < len(s.errors) {
   120  		err = s.errors[s.i]
   121  	}
   122  	s.i++
   123  	return out, err
   124  }
   125  
   126  func (s *SystemdTestSuite) myJctl(svcs []string, n int, follow bool) (io.ReadCloser, error) {
   127  	var err error
   128  	var out []byte
   129  
   130  	s.jns = append(s.jns, strconv.Itoa(n))
   131  	s.jsvcs = append(s.jsvcs, svcs)
   132  	s.jfollows = append(s.jfollows, follow)
   133  
   134  	if s.j < len(s.jouts) {
   135  		out = s.jouts[s.j]
   136  	}
   137  	if s.j < len(s.jerrs) {
   138  		err = s.jerrs[s.j]
   139  	}
   140  	s.j++
   141  
   142  	if out == nil {
   143  		return nil, err
   144  	}
   145  
   146  	return ioutil.NopCloser(bytes.NewReader(out)), err
   147  }
   148  
   149  func (s *SystemdTestSuite) TestDaemonReload(c *C) {
   150  	err := New(SystemMode, s.rep).DaemonReload()
   151  	c.Assert(err, IsNil)
   152  	c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reload"}})
   153  }
   154  
   155  func (s *SystemdTestSuite) TestDaemonReexec(c *C) {
   156  	err := New(SystemMode, s.rep).DaemonReexec()
   157  	c.Assert(err, IsNil)
   158  	c.Assert(s.argses, DeepEquals, [][]string{{"daemon-reexec"}})
   159  }
   160  
   161  func (s *SystemdTestSuite) TestStart(c *C) {
   162  	err := New(SystemMode, s.rep).Start("foo")
   163  	c.Assert(err, IsNil)
   164  	c.Check(s.argses, DeepEquals, [][]string{{"start", "foo"}})
   165  }
   166  
   167  func (s *SystemdTestSuite) TestStartMany(c *C) {
   168  	err := New(SystemMode, s.rep).Start("foo", "bar", "baz")
   169  	c.Assert(err, IsNil)
   170  	c.Check(s.argses, DeepEquals, [][]string{{"start", "foo", "bar", "baz"}})
   171  }
   172  
   173  func (s *SystemdTestSuite) TestStop(c *C) {
   174  	restore := MockStopDelays(time.Millisecond, 25*time.Second)
   175  	defer restore()
   176  	s.outs = [][]byte{
   177  		nil, // for the "stop" itself
   178  		[]byte("ActiveState=whatever\n"),
   179  		[]byte("ActiveState=active\n"),
   180  		[]byte("ActiveState=inactive\n"),
   181  	}
   182  	s.errors = []error{nil, nil, nil, nil, &Timeout{}}
   183  	err := New(SystemMode, s.rep).Stop("foo", 1*time.Second)
   184  	c.Assert(err, IsNil)
   185  	c.Assert(s.argses, HasLen, 4)
   186  	c.Check(s.argses[0], DeepEquals, []string{"stop", "foo"})
   187  	c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
   188  	c.Check(s.argses[1], DeepEquals, s.argses[2])
   189  	c.Check(s.argses[1], DeepEquals, s.argses[3])
   190  }
   191  
   192  func (s *SystemdTestSuite) TestStatus(c *C) {
   193  	s.outs = [][]byte{
   194  		[]byte(`
   195  Type=simple
   196  Id=foo.service
   197  ActiveState=active
   198  UnitFileState=enabled
   199  
   200  Type=simple
   201  Id=bar.service
   202  ActiveState=reloading
   203  UnitFileState=static
   204  
   205  Type=potato
   206  Id=baz.service
   207  ActiveState=inactive
   208  UnitFileState=disabled
   209  
   210  Type=
   211  Id=missing.service
   212  ActiveState=inactive
   213  UnitFileState=
   214  `[1:]),
   215  		[]byte(`
   216  Id=some.timer
   217  ActiveState=active
   218  UnitFileState=enabled
   219  
   220  Id=other.socket
   221  ActiveState=active
   222  UnitFileState=disabled
   223  `[1:]),
   224  	}
   225  	s.errors = []error{nil}
   226  	out, err := New(SystemMode, s.rep).Status("foo.service", "bar.service", "baz.service", "missing.service", "some.timer", "other.socket")
   227  	c.Assert(err, IsNil)
   228  	c.Check(out, DeepEquals, []*UnitStatus{
   229  		{
   230  			Daemon:    "simple",
   231  			UnitName:  "foo.service",
   232  			Active:    true,
   233  			Enabled:   true,
   234  			Installed: true,
   235  		}, {
   236  			Daemon:    "simple",
   237  			UnitName:  "bar.service",
   238  			Active:    true,
   239  			Enabled:   true,
   240  			Installed: true,
   241  		}, {
   242  			Daemon:    "potato",
   243  			UnitName:  "baz.service",
   244  			Active:    false,
   245  			Enabled:   false,
   246  			Installed: true,
   247  		}, {
   248  			Daemon:    "",
   249  			UnitName:  "missing.service",
   250  			Active:    false,
   251  			Enabled:   false,
   252  			Installed: false,
   253  		}, {
   254  			UnitName:  "some.timer",
   255  			Active:    true,
   256  			Enabled:   true,
   257  			Installed: true,
   258  		}, {
   259  			UnitName:  "other.socket",
   260  			Active:    true,
   261  			Enabled:   false,
   262  			Installed: true,
   263  		},
   264  	})
   265  	c.Check(s.rep.msgs, IsNil)
   266  	c.Assert(s.argses, DeepEquals, [][]string{
   267  		{"show", "--property=Id,ActiveState,UnitFileState,Type", "foo.service", "bar.service", "baz.service", "missing.service"},
   268  		{"show", "--property=Id,ActiveState,UnitFileState", "some.timer", "other.socket"},
   269  	})
   270  }
   271  
   272  func (s *SystemdTestSuite) TestStatusBadNumberOfValues(c *C) {
   273  	s.outs = [][]byte{
   274  		[]byte(`
   275  Type=simple
   276  Id=foo.service
   277  ActiveState=active
   278  UnitFileState=enabled
   279  
   280  Type=simple
   281  Id=foo.service
   282  ActiveState=active
   283  UnitFileState=enabled
   284  `[1:]),
   285  	}
   286  	s.errors = []error{nil}
   287  	out, err := New(SystemMode, s.rep).Status("foo.service")
   288  	c.Check(err, ErrorMatches, "cannot get unit status: expected 1 results, got 2")
   289  	c.Check(out, IsNil)
   290  	c.Check(s.rep.msgs, IsNil)
   291  }
   292  
   293  func (s *SystemdTestSuite) TestStatusBadLine(c *C) {
   294  	s.outs = [][]byte{
   295  		[]byte(`
   296  Type=simple
   297  Id=foo.service
   298  ActiveState=active
   299  UnitFileState=enabled
   300  Potatoes
   301  `[1:]),
   302  	}
   303  	s.errors = []error{nil}
   304  	out, err := New(SystemMode, s.rep).Status("foo.service")
   305  	c.Assert(err, ErrorMatches, `.* bad line "Potatoes" .*`)
   306  	c.Check(out, IsNil)
   307  }
   308  
   309  func (s *SystemdTestSuite) TestStatusBadId(c *C) {
   310  	s.outs = [][]byte{
   311  		[]byte(`
   312  Type=simple
   313  Id=bar.service
   314  ActiveState=active
   315  UnitFileState=enabled
   316  `[1:]),
   317  	}
   318  	s.errors = []error{nil}
   319  	out, err := New(SystemMode, s.rep).Status("foo.service")
   320  	c.Assert(err, ErrorMatches, `.* queried status of "foo.service" but got status of "bar.service"`)
   321  	c.Check(out, IsNil)
   322  }
   323  
   324  func (s *SystemdTestSuite) TestStatusBadField(c *C) {
   325  	s.outs = [][]byte{
   326  		[]byte(`
   327  Type=simple
   328  Id=foo.service
   329  ActiveState=active
   330  UnitFileState=enabled
   331  Potatoes=false
   332  `[1:]),
   333  	}
   334  	s.errors = []error{nil}
   335  	out, err := New(SystemMode, s.rep).Status("foo.service")
   336  	c.Assert(err, ErrorMatches, `.* unexpected field "Potatoes" .*`)
   337  	c.Check(out, IsNil)
   338  }
   339  
   340  func (s *SystemdTestSuite) TestStatusMissingRequiredFieldService(c *C) {
   341  	s.outs = [][]byte{
   342  		[]byte(`
   343  Id=foo.service
   344  ActiveState=active
   345  `[1:]),
   346  	}
   347  	s.errors = []error{nil}
   348  	out, err := New(SystemMode, s.rep).Status("foo.service")
   349  	c.Assert(err, ErrorMatches, `.* missing UnitFileState, Type .*`)
   350  	c.Check(out, IsNil)
   351  }
   352  
   353  func (s *SystemdTestSuite) TestStatusMissingRequiredFieldTimer(c *C) {
   354  	s.outs = [][]byte{
   355  		[]byte(`
   356  Id=foo.timer
   357  ActiveState=active
   358  `[1:]),
   359  	}
   360  	s.errors = []error{nil}
   361  	out, err := New(SystemMode, s.rep).Status("foo.timer")
   362  	c.Assert(err, ErrorMatches, `.* missing UnitFileState .*`)
   363  	c.Check(out, IsNil)
   364  }
   365  
   366  func (s *SystemdTestSuite) TestStatusDupeField(c *C) {
   367  	s.outs = [][]byte{
   368  		[]byte(`
   369  Type=simple
   370  Id=foo.service
   371  ActiveState=active
   372  ActiveState=active
   373  UnitFileState=enabled
   374  `[1:]),
   375  	}
   376  	s.errors = []error{nil}
   377  	out, err := New(SystemMode, s.rep).Status("foo.service")
   378  	c.Assert(err, ErrorMatches, `.* duplicate field "ActiveState" .*`)
   379  	c.Check(out, IsNil)
   380  }
   381  
   382  func (s *SystemdTestSuite) TestStatusEmptyField(c *C) {
   383  	s.outs = [][]byte{
   384  		[]byte(`
   385  Type=simple
   386  Id=
   387  ActiveState=active
   388  UnitFileState=enabled
   389  `[1:]),
   390  	}
   391  	s.errors = []error{nil}
   392  	out, err := New(SystemMode, s.rep).Status("foo.service")
   393  	c.Assert(err, ErrorMatches, `.* empty field "Id" .*`)
   394  	c.Check(out, IsNil)
   395  }
   396  
   397  func (s *SystemdTestSuite) TestStopTimeout(c *C) {
   398  	restore := MockStopDelays(time.Millisecond, 25*time.Second)
   399  	defer restore()
   400  	err := New(SystemMode, s.rep).Stop("foo", 10*time.Millisecond)
   401  	c.Assert(err, FitsTypeOf, &Timeout{})
   402  	c.Assert(len(s.rep.msgs) > 0, Equals, true)
   403  	c.Check(s.rep.msgs[0], Equals, "Waiting for foo to stop.")
   404  }
   405  
   406  func (s *SystemdTestSuite) TestDisable(c *C) {
   407  	err := New(SystemMode, s.rep).Disable("foo")
   408  	c.Assert(err, IsNil)
   409  	c.Check(s.argses, DeepEquals, [][]string{{"disable", "foo"}})
   410  }
   411  
   412  func (s *SystemdTestSuite) TestUnderRootDisable(c *C) {
   413  	err := NewUnderRoot("xyzzy", SystemMode, s.rep).Disable("foo")
   414  	c.Assert(err, IsNil)
   415  	c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "disable", "foo"}})
   416  }
   417  
   418  func (s *SystemdTestSuite) TestAvailable(c *C) {
   419  	err := Available()
   420  	c.Assert(err, IsNil)
   421  	c.Check(s.argses, DeepEquals, [][]string{{"--version"}})
   422  }
   423  
   424  func (s *SystemdTestSuite) TestVersion(c *C) {
   425  	s.outs = [][]byte{
   426  		[]byte("systemd 223\n+PAM\n"),
   427  		[]byte("systemd 245 (245.4-4ubuntu3)\n+PAM +AUDIT +SELINUX +IMA\n"),
   428  		// error cases
   429  		[]byte("foo 223\n+PAM\n"),
   430  		[]byte(""),
   431  		[]byte("systemd abc\n+PAM\n"),
   432  	}
   433  
   434  	v, err := Version()
   435  	c.Assert(err, IsNil)
   436  	c.Check(v, Equals, 223)
   437  
   438  	v, err = Version()
   439  	c.Assert(err, IsNil)
   440  	c.Check(v, Equals, 245)
   441  
   442  	_, err = Version()
   443  	c.Assert(err, ErrorMatches, `cannot parse systemd version: expected "systemd", got "foo"`)
   444  
   445  	_, err = Version()
   446  	c.Assert(err, ErrorMatches, `cannot read systemd version: <nil>`)
   447  
   448  	_, err = Version()
   449  	c.Assert(err, ErrorMatches, `cannot convert systemd version to number: abc`)
   450  
   451  	c.Check(s.argses, DeepEquals, [][]string{
   452  		{"--version"},
   453  		{"--version"},
   454  		{"--version"},
   455  		{"--version"},
   456  		{"--version"},
   457  	})
   458  }
   459  
   460  func (s *SystemdTestSuite) TestEnable(c *C) {
   461  	err := New(SystemMode, s.rep).Enable("foo")
   462  	c.Assert(err, IsNil)
   463  	c.Check(s.argses, DeepEquals, [][]string{{"enable", "foo"}})
   464  }
   465  
   466  func (s *SystemdTestSuite) TestEnableUnderRoot(c *C) {
   467  	err := NewUnderRoot("xyzzy", SystemMode, s.rep).Enable("foo")
   468  	c.Assert(err, IsNil)
   469  	c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "enable", "foo"}})
   470  }
   471  
   472  func (s *SystemdTestSuite) TestMask(c *C) {
   473  	err := New(SystemMode, s.rep).Mask("foo")
   474  	c.Assert(err, IsNil)
   475  	c.Check(s.argses, DeepEquals, [][]string{{"mask", "foo"}})
   476  }
   477  
   478  func (s *SystemdTestSuite) TestMaskUnderRoot(c *C) {
   479  	err := NewUnderRoot("xyzzy", SystemMode, s.rep).Mask("foo")
   480  	c.Assert(err, IsNil)
   481  	c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "mask", "foo"}})
   482  }
   483  
   484  func (s *SystemdTestSuite) TestUnmask(c *C) {
   485  	err := New(SystemMode, s.rep).Unmask("foo")
   486  	c.Assert(err, IsNil)
   487  	c.Check(s.argses, DeepEquals, [][]string{{"unmask", "foo"}})
   488  }
   489  
   490  func (s *SystemdTestSuite) TestUnmaskUnderRoot(c *C) {
   491  	err := NewUnderRoot("xyzzy", SystemMode, s.rep).Unmask("foo")
   492  	c.Assert(err, IsNil)
   493  	c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "unmask", "foo"}})
   494  }
   495  
   496  func (s *SystemdTestSuite) TestRestart(c *C) {
   497  	restore := MockStopDelays(time.Millisecond, 25*time.Second)
   498  	defer restore()
   499  	s.outs = [][]byte{
   500  		nil, // for the "stop" itself
   501  		[]byte("ActiveState=inactive\n"),
   502  		nil, // for the "start"
   503  	}
   504  	s.errors = []error{nil, nil, nil, nil, &Timeout{}}
   505  	err := New(SystemMode, s.rep).Restart("foo", 100*time.Millisecond)
   506  	c.Assert(err, IsNil)
   507  	c.Check(s.argses, HasLen, 3)
   508  	c.Check(s.argses[0], DeepEquals, []string{"stop", "foo"})
   509  	c.Check(s.argses[1], DeepEquals, []string{"show", "--property=ActiveState", "foo"})
   510  	c.Check(s.argses[2], DeepEquals, []string{"start", "foo"})
   511  }
   512  
   513  func (s *SystemdTestSuite) TestKill(c *C) {
   514  	c.Assert(New(SystemMode, s.rep).Kill("foo", "HUP", ""), IsNil)
   515  	c.Check(s.argses, DeepEquals, [][]string{{"kill", "foo", "-s", "HUP", "--kill-who=all"}})
   516  }
   517  
   518  func (s *SystemdTestSuite) TestIsTimeout(c *C) {
   519  	c.Check(IsTimeout(os.ErrInvalid), Equals, false)
   520  	c.Check(IsTimeout(&Timeout{}), Equals, true)
   521  }
   522  
   523  func (s *SystemdTestSuite) TestLogErrJctl(c *C) {
   524  	s.jerrs = []error{&Timeout{}}
   525  
   526  	reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false)
   527  	c.Check(err, NotNil)
   528  	c.Check(reader, IsNil)
   529  	c.Check(s.jns, DeepEquals, []string{"24"})
   530  	c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}})
   531  	c.Check(s.jfollows, DeepEquals, []bool{false})
   532  	c.Check(s.j, Equals, 1)
   533  }
   534  
   535  func (s *SystemdTestSuite) TestLogs(c *C) {
   536  	expected := `{"a": 1}
   537  {"a": 2}
   538  `
   539  	s.jouts = [][]byte{[]byte(expected)}
   540  
   541  	reader, err := New(SystemMode, s.rep).LogReader([]string{"foo"}, 24, false)
   542  	c.Check(err, IsNil)
   543  	logs, err := ioutil.ReadAll(reader)
   544  	c.Assert(err, IsNil)
   545  	c.Check(string(logs), Equals, expected)
   546  	c.Check(s.jns, DeepEquals, []string{"24"})
   547  	c.Check(s.jsvcs, DeepEquals, [][]string{{"foo"}})
   548  	c.Check(s.jfollows, DeepEquals, []bool{false})
   549  	c.Check(s.j, Equals, 1)
   550  }
   551  
   552  // mustJSONMarshal panic's if the value cannot be marshaled
   553  func mustJSONMarshal(v interface{}) *json.RawMessage {
   554  	b, err := json.Marshal(v)
   555  	if err != nil {
   556  		panic(fmt.Sprintf("couldn't marshal json in test fixture: %v", err))
   557  	}
   558  	msg := json.RawMessage(b)
   559  	return &msg
   560  }
   561  
   562  func (s *SystemdTestSuite) TestLogPIDWithNonTrivialKeyValues(c *C) {
   563  	l1 := Log{
   564  		"_PID": mustJSONMarshal([]string{}),
   565  	}
   566  	l2 := Log{
   567  		"_PID": mustJSONMarshal(6),
   568  	}
   569  	l3 := Log{
   570  		"_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
   571  	}
   572  	l4 := Log{
   573  		"SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
   574  	}
   575  	l5 := Log{
   576  		"_PID":       mustJSONMarshal("42"),
   577  		"SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
   578  	}
   579  	l6 := Log{
   580  		"_PID":       mustJSONMarshal([]string{"42"}),
   581  		"SYSLOG_PID": mustJSONMarshal([]string{"pid1", "pid2", "pid3"}),
   582  	}
   583  	l7 := Log{
   584  		"_PID":       mustJSONMarshal([]string{"42", "42"}),
   585  		"SYSLOG_PID": mustJSONMarshal([]string{"singlepid"}),
   586  	}
   587  	c.Check(Log{}.PID(), Equals, "-")
   588  	c.Check(l1.PID(), Equals, "-")
   589  	c.Check(l2.PID(), Equals, "-")
   590  	c.Check(l3.PID(), Equals, "-")
   591  	c.Check(l4.PID(), Equals, "-")
   592  	// things starting with underscore are "trusted", so we trust
   593  	// them more than the user-settable ones:
   594  	c.Check(l5.PID(), Equals, "42")
   595  	c.Check(l6.PID(), Equals, "42")
   596  	c.Check(l7.PID(), Equals, "singlepid")
   597  }
   598  
   599  func (s *SystemdTestSuite) TestLogsMessageWithNonUniqueKeys(c *C) {
   600  
   601  	tt := []struct {
   602  		msg     *json.RawMessage
   603  		exp     string
   604  		comment string
   605  	}{
   606  		{
   607  			mustJSONMarshal("m1"),
   608  			"m1",
   609  			"simple string",
   610  		},
   611  		{
   612  			mustJSONMarshal("Я"),
   613  			"Я",
   614  			"simple utf-8 string",
   615  		},
   616  		{
   617  			mustJSONMarshal([]rune{65, 66, 67, 192, 69}),
   618  			"ABC\xc0E",
   619  			"invalid utf-8 bytes",
   620  		},
   621  		{
   622  			mustJSONMarshal(""),
   623  			"",
   624  			"empty string",
   625  		},
   626  		{
   627  			mustJSONMarshal([]string{"m1", "m2", "m3"}),
   628  			"m1\nm2\nm3",
   629  			"slice of strings",
   630  		},
   631  		{
   632  			// this is just "hello" in ascii
   633  			mustJSONMarshal([]rune{104, 101, 108, 108, 111}),
   634  			"hello",
   635  			"rune arrays are converted to strings",
   636  		},
   637  		{
   638  			// this is "hello\r" in ascii, the \r char is unprintable
   639  			mustJSONMarshal([]rune{104, 101, 108, 108, 111, 13}),
   640  			"hello\r",
   641  			"rune arrays are converted to strings",
   642  		},
   643  		{
   644  			// this is "hel" and "lo" in ascii
   645  			mustJSONMarshal([][]rune{
   646  				{104, 101, 108},
   647  				{108, 111},
   648  			}),
   649  			"hel\nlo",
   650  			"arrays of rune arrays are converted to arrays of strings",
   651  		},
   652  		{
   653  			// this is invalid utf-8 string followed by a valid one
   654  			mustJSONMarshal([][]byte{
   655  				{65, 66, 67, 192, 69},
   656  				{104, 101, 108, 108, 111},
   657  			}),
   658  			"ABC\xc0E\nhello",
   659  			"arrays of bytes, some are invalid utf-8 strings",
   660  		},
   661  		{
   662  			mustJSONMarshal(5),
   663  			"- (error decoding original message: unsupported JSON encoding format)",
   664  			"invalid message format of raw scalar number",
   665  		},
   666  		{
   667  			mustJSONMarshal(map[string]int{"hello": 1}),
   668  			"- (error decoding original message: unsupported JSON encoding format)",
   669  			"invalid message format of map object",
   670  		},
   671  	}
   672  
   673  	// trivial case
   674  	c.Check(Log{}.Message(), Equals, "-")
   675  
   676  	// case where the JSON has a "null" JSON value for the key, which happens if
   677  	// the actual message is too large for journald to send
   678  	// we can't use the mustJSONMarshal helper for this in the test table
   679  	// because that gets decoded by Go differently than a verbatim nil here, it
   680  	// gets interpreted as the empty string rather than nil directly
   681  	c.Check(Log{"MESSAGE": nil}.Message(), Equals, "- (error decoding original message: message key \"MESSAGE\" truncated)")
   682  
   683  	for _, t := range tt {
   684  		if t.msg == nil {
   685  
   686  		}
   687  		c.Check(Log{
   688  			"MESSAGE": t.msg,
   689  		}.Message(), Equals, t.exp, Commentf(t.comment))
   690  	}
   691  }
   692  
   693  func (s *SystemdTestSuite) TestLogSID(c *C) {
   694  	c.Check(Log{}.SID(), Equals, "-")
   695  	c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal("abcdef")}.SID(), Equals, "abcdef")
   696  	c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal([]string{"abcdef"})}.SID(), Equals, "abcdef")
   697  	// multiple string values are not supported
   698  	c.Check(Log{"SYSLOG_IDENTIFIER": mustJSONMarshal([]string{"abc", "def"})}.SID(), Equals, "-")
   699  
   700  }
   701  
   702  func (s *SystemdTestSuite) TestLogPID(c *C) {
   703  	c.Check(Log{}.PID(), Equals, "-")
   704  	c.Check(Log{"_PID": mustJSONMarshal("99")}.PID(), Equals, "99")
   705  	c.Check(Log{"SYSLOG_PID": mustJSONMarshal("99")}.PID(), Equals, "99")
   706  	// things starting with underscore are "trusted", so we trust
   707  	// them more than the user-settable ones:
   708  	c.Check(Log{
   709  		"_PID":       mustJSONMarshal("42"),
   710  		"SYSLOG_PID": mustJSONMarshal("99"),
   711  	}.PID(), Equals, "42")
   712  }
   713  
   714  func (s *SystemdTestSuite) TestTime(c *C) {
   715  	t, err := Log{}.Time()
   716  	c.Check(t.IsZero(), Equals, true)
   717  	c.Check(err, ErrorMatches, "key \"__REALTIME_TIMESTAMP\" missing from message")
   718  
   719  	// multiple timestampe keys mean we don't have a timestamp
   720  	t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal([]string{"1", "2"})}.Time()
   721  	c.Check(t.IsZero(), Equals, true)
   722  	c.Check(err, ErrorMatches, `no timestamp`)
   723  
   724  	t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("what")}.Time()
   725  	c.Check(t.IsZero(), Equals, true)
   726  	c.Check(err, ErrorMatches, `timestamp not a decimal number: "what"`)
   727  
   728  	t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("0")}.Time()
   729  	c.Check(err, IsNil)
   730  	c.Check(t.String(), Equals, "1970-01-01 00:00:00 +0000 UTC")
   731  
   732  	t, err = Log{"__REALTIME_TIMESTAMP": mustJSONMarshal("42")}.Time()
   733  	c.Check(err, IsNil)
   734  	c.Check(t.String(), Equals, "1970-01-01 00:00:00.000042 +0000 UTC")
   735  }
   736  
   737  func (s *SystemdTestSuite) TestMountUnitPath(c *C) {
   738  	c.Assert(MountUnitPath("/apps/hello/1.1"), Equals, filepath.Join(dirs.SnapServicesDir, "apps-hello-1.1.mount"))
   739  }
   740  
   741  func makeMockFile(c *C, path string) {
   742  	err := os.MkdirAll(filepath.Dir(path), 0755)
   743  	c.Assert(err, IsNil)
   744  	err = ioutil.WriteFile(path, nil, 0644)
   745  	c.Assert(err, IsNil)
   746  }
   747  
   748  func (s *SystemdTestSuite) TestAddMountUnit(c *C) {
   749  	rootDir := dirs.GlobalRootDir
   750  
   751  	restore := squashfs.MockNeedsFuse(false)
   752  	defer restore()
   753  
   754  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
   755  	makeMockFile(c, mockSnapPath)
   756  
   757  	mountUnitName, err := NewUnderRoot(rootDir, SystemMode, nil).AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs")
   758  	c.Assert(err, IsNil)
   759  	defer os.Remove(mountUnitName)
   760  
   761  	c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
   762  [Unit]
   763  Description=Mount unit for foo, revision 42
   764  Before=snapd.service
   765  After=zfs-mount.service
   766  
   767  [Mount]
   768  What=%s
   769  Where=/snap/snapname/123
   770  Type=squashfs
   771  Options=nodev,ro,x-gdu.hide,x-gvfs-hide
   772  LazyUnmount=yes
   773  
   774  [Install]
   775  WantedBy=multi-user.target
   776  `[1:], mockSnapPath))
   777  
   778  	c.Assert(s.argses, DeepEquals, [][]string{
   779  		{"daemon-reload"},
   780  		{"--root", rootDir, "enable", "snap-snapname-123.mount"},
   781  		{"start", "snap-snapname-123.mount"},
   782  	})
   783  }
   784  
   785  func (s *SystemdTestSuite) TestAddMountUnitForDirs(c *C) {
   786  	restore := squashfs.MockNeedsFuse(false)
   787  	defer restore()
   788  
   789  	// a directory instead of a file produces a different output
   790  	snapDir := c.MkDir()
   791  	mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foodir", "x1", snapDir, "/snap/snapname/x1", "squashfs")
   792  	c.Assert(err, IsNil)
   793  	defer os.Remove(mountUnitName)
   794  
   795  	c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
   796  [Unit]
   797  Description=Mount unit for foodir, revision x1
   798  Before=snapd.service
   799  After=zfs-mount.service
   800  
   801  [Mount]
   802  What=%s
   803  Where=/snap/snapname/x1
   804  Type=none
   805  Options=nodev,ro,x-gdu.hide,x-gvfs-hide,bind
   806  LazyUnmount=yes
   807  
   808  [Install]
   809  WantedBy=multi-user.target
   810  `[1:], snapDir))
   811  
   812  	c.Assert(s.argses, DeepEquals, [][]string{
   813  		{"daemon-reload"},
   814  		{"enable", "snap-snapname-x1.mount"},
   815  		{"start", "snap-snapname-x1.mount"},
   816  	})
   817  }
   818  
   819  func (s *SystemdTestSuite) TestWriteSELinuxMountUnit(c *C) {
   820  	restore := selinux.MockIsEnabled(func() (bool, error) { return true, nil })
   821  	defer restore()
   822  	restore = selinux.MockIsEnforcing(func() (bool, error) { return true, nil })
   823  	defer restore()
   824  	restore = squashfs.MockNeedsFuse(false)
   825  	defer restore()
   826  
   827  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
   828  	err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755)
   829  	c.Assert(err, IsNil)
   830  	err = ioutil.WriteFile(mockSnapPath, nil, 0644)
   831  	c.Assert(err, IsNil)
   832  
   833  	mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs")
   834  	c.Assert(err, IsNil)
   835  	defer os.Remove(mountUnitName)
   836  
   837  	c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
   838  [Unit]
   839  Description=Mount unit for foo, revision 42
   840  Before=snapd.service
   841  After=zfs-mount.service
   842  
   843  [Mount]
   844  What=%s
   845  Where=/snap/snapname/123
   846  Type=squashfs
   847  Options=nodev,context=system_u:object_r:snappy_snap_t:s0,ro,x-gdu.hide,x-gvfs-hide
   848  LazyUnmount=yes
   849  
   850  [Install]
   851  WantedBy=multi-user.target
   852  `[1:], mockSnapPath))
   853  }
   854  
   855  func (s *SystemdTestSuite) TestFuseInContainer(c *C) {
   856  	if !osutil.FileExists("/dev/fuse") {
   857  		c.Skip("No /dev/fuse on the system")
   858  	}
   859  
   860  	systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", `
   861  echo lxc
   862  exit 0
   863  	`)
   864  	defer systemdCmd.Restore()
   865  
   866  	fuseCmd := testutil.MockCommand(c, "squashfuse", `
   867  exit 0
   868  	`)
   869  	defer fuseCmd.Restore()
   870  
   871  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
   872  	err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755)
   873  	c.Assert(err, IsNil)
   874  	err = ioutil.WriteFile(mockSnapPath, nil, 0644)
   875  	c.Assert(err, IsNil)
   876  
   877  	mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "x1", mockSnapPath, "/snap/snapname/123", "squashfs")
   878  	c.Assert(err, IsNil)
   879  	defer os.Remove(mountUnitName)
   880  
   881  	c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
   882  [Unit]
   883  Description=Mount unit for foo, revision x1
   884  Before=snapd.service
   885  After=zfs-mount.service
   886  
   887  [Mount]
   888  What=%s
   889  Where=/snap/snapname/123
   890  Type=fuse.squashfuse
   891  Options=nodev,ro,x-gdu.hide,x-gvfs-hide,allow_other
   892  LazyUnmount=yes
   893  
   894  [Install]
   895  WantedBy=multi-user.target
   896  `[1:], mockSnapPath))
   897  }
   898  
   899  func (s *SystemdTestSuite) TestFuseOutsideContainer(c *C) {
   900  	systemdCmd := testutil.MockCommand(c, "systemd-detect-virt", `
   901  echo none
   902  exit 0
   903  	`)
   904  	defer systemdCmd.Restore()
   905  
   906  	fuseCmd := testutil.MockCommand(c, "squashfuse", `
   907  exit 0
   908  	`)
   909  	defer fuseCmd.Restore()
   910  
   911  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
   912  	err := os.MkdirAll(filepath.Dir(mockSnapPath), 0755)
   913  	c.Assert(err, IsNil)
   914  	err = ioutil.WriteFile(mockSnapPath, nil, 0644)
   915  	c.Assert(err, IsNil)
   916  
   917  	mountUnitName, err := New(SystemMode, nil).AddMountUnitFile("foo", "x1", mockSnapPath, "/snap/snapname/123", "squashfs")
   918  	c.Assert(err, IsNil)
   919  	defer os.Remove(mountUnitName)
   920  
   921  	c.Assert(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(`
   922  [Unit]
   923  Description=Mount unit for foo, revision x1
   924  Before=snapd.service
   925  After=zfs-mount.service
   926  
   927  [Mount]
   928  What=%s
   929  Where=/snap/snapname/123
   930  Type=squashfs
   931  Options=nodev,ro,x-gdu.hide,x-gvfs-hide
   932  LazyUnmount=yes
   933  
   934  [Install]
   935  WantedBy=multi-user.target
   936  `[1:], mockSnapPath))
   937  }
   938  
   939  func (s *SystemdTestSuite) TestJctl(c *C) {
   940  	var args []string
   941  	var err error
   942  	MockOsutilStreamCommand(func(name string, myargs ...string) (io.ReadCloser, error) {
   943  		c.Check(cap(myargs) <= len(myargs)+2, Equals, true, Commentf("cap:%d, len:%d", cap(myargs), len(myargs)))
   944  		args = myargs
   945  		return nil, nil
   946  	})
   947  
   948  	_, err = Jctl([]string{"foo", "bar"}, 10, false)
   949  	c.Assert(err, IsNil)
   950  	c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "10", "-u", "foo", "-u", "bar"})
   951  	_, err = Jctl([]string{"foo", "bar", "baz"}, 99, true)
   952  	c.Assert(err, IsNil)
   953  	c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "-n", "99", "-f", "-u", "foo", "-u", "bar", "-u", "baz"})
   954  	_, err = Jctl([]string{"foo", "bar"}, -1, false)
   955  	c.Assert(err, IsNil)
   956  	c.Check(args, DeepEquals, []string{"-o", "json", "--no-pager", "--no-tail", "-u", "foo", "-u", "bar"})
   957  }
   958  
   959  func (s *SystemdTestSuite) TestIsActiveUnderRoot(c *C) {
   960  	sysErr := &Error{}
   961  	// manpage states that systemctl returns exit code 3 for inactive
   962  	// services, however we should check any non-0 exit status
   963  	sysErr.SetExitCode(1)
   964  	sysErr.SetMsg([]byte("inactive\n"))
   965  	s.errors = []error{sysErr}
   966  
   967  	_, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo")
   968  	c.Assert(err, IsNil)
   969  	c.Check(s.argses, DeepEquals, [][]string{{"--root", "xyzzy", "is-active", "foo"}})
   970  }
   971  
   972  func (s *SystemdTestSuite) TestIsActiveIsInactive(c *C) {
   973  	sysErr := &Error{}
   974  	// manpage states that systemctl returns exit code 3 for inactive
   975  	// services, however we should check any non-0 exit status
   976  	sysErr.SetExitCode(1)
   977  	sysErr.SetMsg([]byte("inactive\n"))
   978  	s.errors = []error{sysErr}
   979  
   980  	active, err := New(SystemMode, s.rep).IsActive("foo")
   981  	c.Assert(active, Equals, false)
   982  	c.Assert(err, IsNil)
   983  	c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
   984  }
   985  
   986  func (s *SystemdTestSuite) TestIsActiveIsInactiveAlternativeMessage(c *C) {
   987  	sysErr := &Error{}
   988  	// on Centos 7, with systemd 219 we see "unknown" returned when querying the
   989  	// active state for a slice unit which does not exist, check that we handle
   990  	// this case properly as well
   991  	sysErr.SetExitCode(3)
   992  	sysErr.SetMsg([]byte("unknown\n"))
   993  	s.errors = []error{sysErr}
   994  
   995  	active, err := New(SystemMode, s.rep).IsActive("foo")
   996  	c.Assert(active, Equals, false)
   997  	c.Assert(err, IsNil)
   998  	c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
   999  }
  1000  
  1001  func (s *SystemdTestSuite) TestIsActiveIsFailed(c *C) {
  1002  	sysErr := &Error{}
  1003  	// seen in the wild to be reported for a 'failed' service
  1004  	sysErr.SetExitCode(3)
  1005  	sysErr.SetMsg([]byte("failed\n"))
  1006  	s.errors = []error{sysErr}
  1007  
  1008  	active, err := New(SystemMode, s.rep).IsActive("foo")
  1009  	c.Assert(active, Equals, false)
  1010  	c.Assert(err, IsNil)
  1011  	c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
  1012  }
  1013  
  1014  func (s *SystemdTestSuite) TestIsActiveIsActive(c *C) {
  1015  	s.errors = []error{nil}
  1016  
  1017  	active, err := New(SystemMode, s.rep).IsActive("foo")
  1018  	c.Assert(active, Equals, true)
  1019  	c.Assert(err, IsNil)
  1020  	c.Check(s.argses, DeepEquals, [][]string{{"is-active", "foo"}})
  1021  }
  1022  
  1023  func (s *SystemdTestSuite) TestIsActiveUnexpectedErr(c *C) {
  1024  	sysErr := &Error{}
  1025  	sysErr.SetExitCode(1)
  1026  	sysErr.SetMsg([]byte("random-failure\n"))
  1027  	s.errors = []error{sysErr}
  1028  
  1029  	active, err := NewUnderRoot("xyzzy", SystemMode, s.rep).IsActive("foo")
  1030  	c.Assert(active, Equals, false)
  1031  	c.Assert(err, ErrorMatches, ".* failed with exit status 1: random-failure\n")
  1032  }
  1033  
  1034  func makeMockMountUnit(c *C, mountDir string) string {
  1035  	mountUnit := MountUnitPath(dirs.StripRootDir(mountDir))
  1036  	err := ioutil.WriteFile(mountUnit, nil, 0644)
  1037  	c.Assert(err, IsNil)
  1038  	return mountUnit
  1039  }
  1040  
  1041  // FIXME: also test for the "IsMounted" case
  1042  func (s *SystemdTestSuite) TestRemoveMountUnit(c *C) {
  1043  	rootDir := dirs.GlobalRootDir
  1044  
  1045  	restore := osutil.MockMountInfo("")
  1046  	defer restore()
  1047  
  1048  	mountDir := rootDir + "/snap/foo/42"
  1049  	mountUnit := makeMockMountUnit(c, mountDir)
  1050  	err := NewUnderRoot(rootDir, SystemMode, nil).RemoveMountUnitFile(mountDir)
  1051  
  1052  	c.Assert(err, IsNil)
  1053  	// the file is gone
  1054  	c.Check(osutil.FileExists(mountUnit), Equals, false)
  1055  	// and the unit is disabled and the daemon reloaded
  1056  	c.Check(s.argses, DeepEquals, [][]string{
  1057  		{"--root", rootDir, "disable", "snap-foo-42.mount"},
  1058  		{"daemon-reload"},
  1059  	})
  1060  }
  1061  
  1062  func (s *SystemdTestSuite) TestDaemonReloadMutex(c *C) {
  1063  	s.testDaemonReloadMutex(c, Systemd.DaemonReload)
  1064  }
  1065  
  1066  func (s *SystemdTestSuite) testDaemonReloadMutex(c *C, reload func(Systemd) error) {
  1067  	rootDir := dirs.GlobalRootDir
  1068  	sysd := NewUnderRoot(rootDir, SystemMode, nil)
  1069  
  1070  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
  1071  	makeMockFile(c, mockSnapPath)
  1072  
  1073  	// create a go-routine that will try to daemon-reload like crazy
  1074  	stopCh := make(chan bool, 1)
  1075  	stoppedCh := make(chan bool, 1)
  1076  	go func() {
  1077  		for {
  1078  			sysd.DaemonReload()
  1079  			select {
  1080  			case <-stopCh:
  1081  				close(stoppedCh)
  1082  				return
  1083  			default:
  1084  				//pass
  1085  			}
  1086  		}
  1087  	}()
  1088  
  1089  	// And now add a mount unit file while the go-routine tries to
  1090  	// daemon-reload. This will be serialized, if not this would
  1091  	// panic because systemd.daemonReloadNoLock ensures the lock is
  1092  	// taken when this happens.
  1093  	_, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/foo/42", "squashfs")
  1094  	c.Assert(err, IsNil)
  1095  	close(stopCh)
  1096  	<-stoppedCh
  1097  }
  1098  
  1099  func (s *SystemdTestSuite) TestDaemonReexecMutex(c *C) {
  1100  	s.testDaemonReloadMutex(c, Systemd.DaemonReexec)
  1101  }
  1102  
  1103  func (s *SystemdTestSuite) TestUserMode(c *C) {
  1104  	rootDir := dirs.GlobalRootDir
  1105  	sysd := NewUnderRoot(rootDir, UserMode, nil)
  1106  
  1107  	c.Assert(sysd.Enable("foo"), IsNil)
  1108  	c.Check(s.argses[0], DeepEquals, []string{"--user", "--root", rootDir, "enable", "foo"})
  1109  	c.Assert(sysd.Start("foo"), IsNil)
  1110  	c.Check(s.argses[1], DeepEquals, []string{"--user", "start", "foo"})
  1111  }
  1112  
  1113  func (s *SystemdTestSuite) TestGlobalUserMode(c *C) {
  1114  	rootDir := dirs.GlobalRootDir
  1115  	sysd := NewUnderRoot(rootDir, GlobalUserMode, nil)
  1116  
  1117  	c.Assert(sysd.Enable("foo"), IsNil)
  1118  	c.Check(s.argses[0], DeepEquals, []string{"--user", "--global", "--root", rootDir, "enable", "foo"})
  1119  	c.Assert(sysd.Disable("foo"), IsNil)
  1120  	c.Check(s.argses[1], DeepEquals, []string{"--user", "--global", "--root", rootDir, "disable", "foo"})
  1121  	c.Assert(sysd.Mask("foo"), IsNil)
  1122  	c.Check(s.argses[2], DeepEquals, []string{"--user", "--global", "--root", rootDir, "mask", "foo"})
  1123  	c.Assert(sysd.Unmask("foo"), IsNil)
  1124  	c.Check(s.argses[3], DeepEquals, []string{"--user", "--global", "--root", rootDir, "unmask", "foo"})
  1125  	_, err := sysd.IsEnabled("foo")
  1126  	c.Check(err, IsNil)
  1127  	c.Check(s.argses[4], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "foo"})
  1128  
  1129  	// Commands that don't make sense for GlobalUserMode panic
  1130  	c.Check(sysd.DaemonReload, Panics, "cannot call daemon-reload with GlobalUserMode")
  1131  	c.Check(sysd.DaemonReexec, Panics, "cannot call daemon-reexec with GlobalUserMode")
  1132  	c.Check(func() { sysd.Start("foo") }, Panics, "cannot call start with GlobalUserMode")
  1133  	c.Check(func() { sysd.StartNoBlock("foo") }, Panics, "cannot call start with GlobalUserMode")
  1134  	c.Check(func() { sysd.Stop("foo", 0) }, Panics, "cannot call stop with GlobalUserMode")
  1135  	c.Check(func() { sysd.Restart("foo", 0) }, Panics, "cannot call restart with GlobalUserMode")
  1136  	c.Check(func() { sysd.Kill("foo", "HUP", "") }, Panics, "cannot call kill with GlobalUserMode")
  1137  	c.Check(func() { sysd.IsActive("foo") }, Panics, "cannot call is-active with GlobalUserMode")
  1138  }
  1139  
  1140  func (s *SystemdTestSuite) TestStatusGlobalUserMode(c *C) {
  1141  	output := []byte("enabled\ndisabled\nstatic\n")
  1142  	sysdErr := &Error{}
  1143  	sysdErr.SetExitCode(1)
  1144  	sysdErr.SetMsg(output)
  1145  
  1146  	s.outs = [][]byte{output, nil, output}
  1147  	s.errors = []error{nil, sysdErr, nil}
  1148  
  1149  	rootDir := dirs.GlobalRootDir
  1150  	sysd := NewUnderRoot(rootDir, GlobalUserMode, nil)
  1151  	sts, err := sysd.Status("foo", "bar", "baz")
  1152  	c.Check(err, IsNil)
  1153  	c.Check(sts, DeepEquals, []*UnitStatus{
  1154  		{UnitName: "foo", Enabled: true},
  1155  		{UnitName: "bar", Enabled: false},
  1156  		{UnitName: "baz", Enabled: true},
  1157  	})
  1158  	c.Check(s.argses[0], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "foo", "bar", "baz"})
  1159  
  1160  	// Output is collected if systemctl has a non-zero exit status
  1161  	sts, err = sysd.Status("one", "two", "three")
  1162  	c.Check(err, IsNil)
  1163  	c.Check(sts, DeepEquals, []*UnitStatus{
  1164  		{UnitName: "one", Enabled: true},
  1165  		{UnitName: "two", Enabled: false},
  1166  		{UnitName: "three", Enabled: true},
  1167  	})
  1168  	c.Check(s.argses[1], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "one", "two", "three"})
  1169  
  1170  	// An error is returned if the wrong number of statuses are returned
  1171  	sts, err = sysd.Status("one")
  1172  	c.Check(err, ErrorMatches, "cannot get enabled status of services: expected 1 results, got 3")
  1173  	c.Check(sts, IsNil)
  1174  	c.Check(s.argses[2], DeepEquals, []string{"--user", "--global", "--root", rootDir, "is-enabled", "one"})
  1175  }
  1176  
  1177  const unitTemplate = `
  1178  [Unit]
  1179  Description=Mount unit for foo, revision 42
  1180  Before=snapd.service
  1181  After=zfs-mount.service
  1182  
  1183  [Mount]
  1184  What=%s
  1185  Where=/snap/snapname/123
  1186  Type=%s
  1187  Options=%s
  1188  LazyUnmount=yes
  1189  
  1190  [Install]
  1191  WantedBy=multi-user.target
  1192  `
  1193  
  1194  func (s *SystemdTestSuite) TestPreseedModeAddMountUnit(c *C) {
  1195  	sysd := NewEmulationMode(dirs.GlobalRootDir)
  1196  
  1197  	restore := squashfs.MockNeedsFuse(false)
  1198  	defer restore()
  1199  
  1200  	mockMountCmd := testutil.MockCommand(c, "mount", "")
  1201  	defer mockMountCmd.Restore()
  1202  
  1203  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
  1204  	makeMockFile(c, mockSnapPath)
  1205  
  1206  	mountUnitName, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs")
  1207  	c.Assert(err, IsNil)
  1208  	defer os.Remove(mountUnitName)
  1209  
  1210  	// systemd was not called
  1211  	c.Check(s.argses, HasLen, 0)
  1212  	// mount was called
  1213  	c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", mockSnapPath, "/snap/snapname/123", "-o", "nodev,ro,x-gdu.hide,x-gvfs-hide"})
  1214  	// unit was enabled with a symlink
  1215  	c.Check(osutil.IsSymlink(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", mountUnitName)), Equals, true)
  1216  	c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide,x-gvfs-hide"))
  1217  }
  1218  
  1219  func (s *SystemdTestSuite) TestPreseedModeAddMountUnitWithFuse(c *C) {
  1220  	sysd := NewEmulationMode(dirs.GlobalRootDir)
  1221  
  1222  	restore := MockSquashFsType(func() (string, []string) { return "fuse.squashfuse", []string{"a,b,c"} })
  1223  	defer restore()
  1224  
  1225  	mockMountCmd := testutil.MockCommand(c, "mount", "")
  1226  	defer mockMountCmd.Restore()
  1227  
  1228  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
  1229  	makeMockFile(c, mockSnapPath)
  1230  
  1231  	mountUnitName, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs")
  1232  	c.Assert(err, IsNil)
  1233  	defer os.Remove(mountUnitName)
  1234  
  1235  	c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "fuse.squashfuse", mockSnapPath, "/snap/snapname/123", "-o", "nodev,a,b,c"})
  1236  	c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide,x-gvfs-hide"))
  1237  }
  1238  
  1239  func (s *SystemdTestSuite) TestPreseedModeMountError(c *C) {
  1240  	sysd := NewEmulationMode(dirs.GlobalRootDir)
  1241  
  1242  	restore := squashfs.MockNeedsFuse(false)
  1243  	defer restore()
  1244  
  1245  	mockMountCmd := testutil.MockCommand(c, "mount", `echo "some failure"; exit 1`)
  1246  	defer mockMountCmd.Restore()
  1247  
  1248  	mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
  1249  	makeMockFile(c, mockSnapPath)
  1250  
  1251  	_, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "squashfs")
  1252  	c.Assert(err, ErrorMatches, `cannot mount .*/var/lib/snappy/snaps/foo_1.0.snap \(squashfs\) at /snap/snapname/123 in preseed mode: exit status 1; some failure\n`)
  1253  }
  1254  
  1255  func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnit(c *C) {
  1256  	mountDir := dirs.GlobalRootDir + "/snap/foo/42"
  1257  
  1258  	restore := MockOsutilIsMounted(func(path string) (bool, error) {
  1259  		c.Check(path, Equals, mountDir)
  1260  		return true, nil
  1261  	})
  1262  	defer restore()
  1263  
  1264  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
  1265  	defer mockUmountCmd.Restore()
  1266  
  1267  	sysd := NewEmulationMode(dirs.GlobalRootDir)
  1268  
  1269  	mountUnit := makeMockMountUnit(c, mountDir)
  1270  	symlinkPath := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", filepath.Base(mountUnit))
  1271  	c.Assert(os.Symlink(mountUnit, symlinkPath), IsNil)
  1272  	c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil)
  1273  
  1274  	// the file is gone
  1275  	c.Check(osutil.FileExists(mountUnit), Equals, false)
  1276  	// unit symlink is gone
  1277  	c.Check(osutil.IsSymlink(symlinkPath), Equals, false)
  1278  	// and systemd was not called
  1279  	c.Check(s.argses, HasLen, 0)
  1280  	// umount was called
  1281  	c.Check(mockUmountCmd.Calls(), DeepEquals, [][]string{{"umount", "-d", "-l", mountDir}})
  1282  }
  1283  
  1284  func (s *SystemdTestSuite) TestPreseedModeRemoveMountUnitUnmounted(c *C) {
  1285  	mountDir := dirs.GlobalRootDir + "/snap/foo/42"
  1286  
  1287  	restore := MockOsutilIsMounted(func(path string) (bool, error) {
  1288  		c.Check(path, Equals, mountDir)
  1289  		return false, nil
  1290  	})
  1291  	defer restore()
  1292  
  1293  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
  1294  	defer mockUmountCmd.Restore()
  1295  
  1296  	sysd := NewEmulationMode(dirs.GlobalRootDir)
  1297  	mountUnit := makeMockMountUnit(c, mountDir)
  1298  	symlinkPath := filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", filepath.Base(mountUnit))
  1299  	c.Assert(os.Symlink(mountUnit, symlinkPath), IsNil)
  1300  
  1301  	c.Assert(sysd.RemoveMountUnitFile(mountDir), IsNil)
  1302  
  1303  	// the file is gone
  1304  	c.Check(osutil.FileExists(mountUnit), Equals, false)
  1305  	// unit symlink is gone
  1306  	c.Check(osutil.IsSymlink(symlinkPath), Equals, false)
  1307  	// and systemd was not called
  1308  	c.Check(s.argses, HasLen, 0)
  1309  	// umount was not called
  1310  	c.Check(mockUmountCmd.Calls(), HasLen, 0)
  1311  }
  1312  
  1313  func (s *SystemdTestSuite) TestPreseedModeBindmountNotSupported(c *C) {
  1314  	sysd := NewEmulationMode(dirs.GlobalRootDir)
  1315  
  1316  	restore := squashfs.MockNeedsFuse(false)
  1317  	defer restore()
  1318  
  1319  	mockSnapPath := c.MkDir()
  1320  
  1321  	_, err := sysd.AddMountUnitFile("foo", "42", mockSnapPath, "/snap/snapname/123", "")
  1322  	c.Assert(err, ErrorMatches, `bind-mounted directory is not supported in emulation mode`)
  1323  }
  1324  
  1325  func (s *SystemdTestSuite) TestEnableInEmulationMode(c *C) {
  1326  	sysd := NewEmulationMode("/path")
  1327  	c.Assert(sysd.Enable("foo"), IsNil)
  1328  
  1329  	sysd = NewEmulationMode("")
  1330  	c.Assert(sysd.Enable("bar"), IsNil)
  1331  	c.Check(s.argses, DeepEquals, [][]string{
  1332  		{"--root", "/path", "enable", "foo"},
  1333  		{"--root", dirs.GlobalRootDir, "enable", "bar"}})
  1334  }
  1335  
  1336  func (s *SystemdTestSuite) TestDisableInEmulationMode(c *C) {
  1337  	sysd := NewEmulationMode("/path")
  1338  	c.Assert(sysd.Disable("foo"), IsNil)
  1339  
  1340  	c.Check(s.argses, DeepEquals, [][]string{
  1341  		{"--root", "/path", "disable", "foo"}})
  1342  }
  1343  
  1344  func (s *SystemdTestSuite) TestMaskInEmulationMode(c *C) {
  1345  	sysd := NewEmulationMode("/path")
  1346  	c.Assert(sysd.Mask("foo"), IsNil)
  1347  
  1348  	c.Check(s.argses, DeepEquals, [][]string{
  1349  		{"--root", "/path", "mask", "foo"}})
  1350  }
  1351  
  1352  func (s *SystemdTestSuite) TestUnmaskInEmulationMode(c *C) {
  1353  	sysd := NewEmulationMode("/path")
  1354  	c.Assert(sysd.Unmask("foo"), IsNil)
  1355  
  1356  	c.Check(s.argses, DeepEquals, [][]string{
  1357  		{"--root", "/path", "unmask", "foo"}})
  1358  }
  1359  
  1360  func (s *SystemdTestSuite) TestMountHappy(c *C) {
  1361  	sysd := New(SystemMode, nil)
  1362  
  1363  	cmd := testutil.MockCommand(c, "systemd-mount", "")
  1364  	defer cmd.Restore()
  1365  
  1366  	c.Assert(sysd.Mount("foo", "bar"), IsNil)
  1367  	c.Check(cmd.Calls(), DeepEquals, [][]string{
  1368  		{"systemd-mount", "foo", "bar"},
  1369  	})
  1370  	cmd.ForgetCalls()
  1371  	c.Assert(sysd.Mount("foo", "bar", "-o", "bind"), IsNil)
  1372  	c.Check(cmd.Calls(), DeepEquals, [][]string{
  1373  		{"systemd-mount", "-o", "bind", "foo", "bar"},
  1374  	})
  1375  }
  1376  
  1377  func (s *SystemdTestSuite) TestMountErr(c *C) {
  1378  	sysd := New(SystemMode, nil)
  1379  
  1380  	cmd := testutil.MockCommand(c, "systemd-mount", `echo "failed"; exit 111`)
  1381  	defer cmd.Restore()
  1382  
  1383  	err := sysd.Mount("foo", "bar")
  1384  	c.Assert(err, ErrorMatches, "failed")
  1385  	c.Check(cmd.Calls(), DeepEquals, [][]string{
  1386  		{"systemd-mount", "foo", "bar"},
  1387  	})
  1388  }
  1389  
  1390  func (s *SystemdTestSuite) TestUmountHappy(c *C) {
  1391  	sysd := New(SystemMode, nil)
  1392  
  1393  	cmd := testutil.MockCommand(c, "systemd-mount", "")
  1394  	defer cmd.Restore()
  1395  
  1396  	c.Assert(sysd.Umount("bar"), IsNil)
  1397  	c.Check(cmd.Calls(), DeepEquals, [][]string{
  1398  		{"systemd-mount", "--umount", "bar"},
  1399  	})
  1400  }
  1401  
  1402  func (s *SystemdTestSuite) TestUmountErr(c *C) {
  1403  	sysd := New(SystemMode, nil)
  1404  
  1405  	cmd := testutil.MockCommand(c, "systemd-mount", `echo "failed"; exit 111`)
  1406  	defer cmd.Restore()
  1407  
  1408  	err := sysd.Umount("bar")
  1409  	c.Assert(err, ErrorMatches, "failed")
  1410  	c.Check(cmd.Calls(), DeepEquals, [][]string{
  1411  		{"systemd-mount", "--umount", "bar"},
  1412  	})
  1413  }
  1414  
  1415  func (s *SystemdTestSuite) TestCurrentUsageFamilyReallyInvalid(c *C) {
  1416  	s.outs = [][]byte{
  1417  		[]byte(`gahstringsarehard`),
  1418  		[]byte(`gahstringsarehard`),
  1419  	}
  1420  	sysd := New(SystemMode, s.rep)
  1421  	_, err := sysd.CurrentMemoryUsage("bar.service")
  1422  	c.Assert(err, ErrorMatches, `invalid property format from systemd for MemoryCurrent \(got gahstringsarehard\)`)
  1423  	_, err = sysd.CurrentTasksCount("bar.service")
  1424  	c.Assert(err, ErrorMatches, `invalid property format from systemd for TasksCurrent \(got gahstringsarehard\)`)
  1425  	c.Check(s.argses, DeepEquals, [][]string{
  1426  		{"show", "--property", "MemoryCurrent", "bar.service"},
  1427  		{"show", "--property", "TasksCurrent", "bar.service"},
  1428  	})
  1429  }
  1430  
  1431  func (s *SystemdTestSuite) TestCurrentUsageFamilyInactive(c *C) {
  1432  	s.outs = [][]byte{
  1433  		[]byte(`MemoryCurrent=[not set]`),
  1434  		[]byte(`TasksCurrent=[not set]`),
  1435  	}
  1436  	sysd := New(SystemMode, s.rep)
  1437  	_, err := sysd.CurrentMemoryUsage("bar.service")
  1438  	c.Assert(err, ErrorMatches, "memory usage unavailable")
  1439  	_, err = sysd.CurrentTasksCount("bar.service")
  1440  	c.Assert(err, ErrorMatches, "tasks count unavailable")
  1441  	c.Check(s.argses, DeepEquals, [][]string{
  1442  		{"show", "--property", "MemoryCurrent", "bar.service"},
  1443  		{"show", "--property", "TasksCurrent", "bar.service"},
  1444  	})
  1445  }
  1446  
  1447  func (s *SystemdTestSuite) TestCurrentUsageFamilyInvalid(c *C) {
  1448  	s.outs = [][]byte{
  1449  		[]byte(`MemoryCurrent=blahhhhhhhhhhhhhh`),
  1450  		[]byte(`TasksCurrent=blahhhhhhhhhhhhhh`),
  1451  	}
  1452  	sysd := New(SystemMode, s.rep)
  1453  	_, err := sysd.CurrentMemoryUsage("bar.service")
  1454  	c.Assert(err, ErrorMatches, `invalid property value from systemd for MemoryCurrent: cannot parse "blahhhhhhhhhhhhhh" as an integer`)
  1455  	_, err = sysd.CurrentTasksCount("bar.service")
  1456  	c.Assert(err, ErrorMatches, `invalid property value from systemd for TasksCurrent: cannot parse "blahhhhhhhhhhhhhh" as an integer`)
  1457  	c.Check(s.argses, DeepEquals, [][]string{
  1458  		{"show", "--property", "MemoryCurrent", "bar.service"},
  1459  		{"show", "--property", "TasksCurrent", "bar.service"},
  1460  	})
  1461  }
  1462  
  1463  func (s *SystemdTestSuite) TestCurrentUsageFamilyHappy(c *C) {
  1464  	s.outs = [][]byte{
  1465  		[]byte(`MemoryCurrent=1024`),
  1466  		[]byte(`MemoryCurrent=18446744073709551615`), // special value from systemd bug
  1467  		[]byte(`TasksCurrent=10`),
  1468  	}
  1469  	sysd := New(SystemMode, s.rep)
  1470  	memUsage, err := sysd.CurrentMemoryUsage("bar.service")
  1471  	c.Assert(err, IsNil)
  1472  	c.Assert(memUsage, Equals, quantity.SizeKiB)
  1473  	memUsage, err = sysd.CurrentMemoryUsage("bar.service")
  1474  	c.Assert(err, IsNil)
  1475  	const sixteenExb = quantity.Size(1<<64 - 1)
  1476  	c.Assert(memUsage, Equals, sixteenExb)
  1477  	tasksUsage, err := sysd.CurrentTasksCount("bar.service")
  1478  	c.Assert(tasksUsage, Equals, uint64(10))
  1479  	c.Assert(err, IsNil)
  1480  	c.Check(s.argses, DeepEquals, [][]string{
  1481  		{"show", "--property", "MemoryCurrent", "bar.service"},
  1482  		{"show", "--property", "MemoryCurrent", "bar.service"},
  1483  		{"show", "--property", "TasksCurrent", "bar.service"},
  1484  	})
  1485  }
  1486  
  1487  func (s *SystemdTestSuite) TestInactiveEnterTimestampZero(c *C) {
  1488  	s.outs = [][]byte{
  1489  		[]byte(`InactiveEnterTimestamp=`),
  1490  	}
  1491  	sysd := New(SystemMode, s.rep)
  1492  	stamp, err := sysd.InactiveEnterTimestamp("bar.service")
  1493  	c.Assert(err, IsNil)
  1494  	c.Check(s.argses, DeepEquals, [][]string{
  1495  		{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
  1496  	})
  1497  	c.Check(stamp.IsZero(), Equals, true)
  1498  }
  1499  
  1500  func (s *SystemdTestSuite) TestInactiveEnterTimestampValidWhitespace(c *C) {
  1501  	s.outs = [][]byte{
  1502  		[]byte(`InactiveEnterTimestamp=Fri 2021-04-16 15:32:21 UTC
  1503  `),
  1504  	}
  1505  
  1506  	stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service")
  1507  	c.Assert(err, IsNil)
  1508  	c.Check(s.argses, DeepEquals, [][]string{
  1509  		{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
  1510  	})
  1511  	c.Check(stamp.Equal(time.Date(2021, time.April, 16, 15, 32, 21, 0, time.UTC)), Equals, true)
  1512  }
  1513  
  1514  func (s *SystemdTestSuite) TestInactiveEnterTimestampValid(c *C) {
  1515  	s.outs = [][]byte{
  1516  		[]byte(`InactiveEnterTimestamp=Fri 2021-04-16 15:32:21 UTC`),
  1517  	}
  1518  
  1519  	stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service")
  1520  	c.Assert(err, IsNil)
  1521  	c.Check(s.argses, DeepEquals, [][]string{
  1522  		{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
  1523  	})
  1524  	c.Check(stamp.Equal(time.Date(2021, time.April, 16, 15, 32, 21, 0, time.UTC)), Equals, true)
  1525  }
  1526  
  1527  func (s *SystemdTestSuite) TestInactiveEnterTimestampFailure(c *C) {
  1528  	s.outs = [][]byte{
  1529  		[]byte(`mocked failure`),
  1530  	}
  1531  	s.errors = []error{
  1532  		fmt.Errorf("mocked failure"),
  1533  	}
  1534  	stamp, err := New(SystemMode, s.rep).InactiveEnterTimestamp("bar.service")
  1535  	c.Assert(err, ErrorMatches, "mocked failure")
  1536  	c.Check(stamp.IsZero(), Equals, true)
  1537  }
  1538  
  1539  func (s *SystemdTestSuite) TestInactiveEnterTimestampMalformed(c *C) {
  1540  	s.outs = [][]byte{
  1541  		[]byte(`InactiveEnterTimestamp`),
  1542  		[]byte(``),
  1543  		[]byte(`some random garbage
  1544  with newlines`),
  1545  	}
  1546  	sysd := New(SystemMode, s.rep)
  1547  	for i := 0; i < len(s.outs); i++ {
  1548  		s.argses = nil
  1549  		stamp, err := sysd.InactiveEnterTimestamp("bar.service")
  1550  		c.Assert(err.Error(), testutil.Contains, `invalid property format from systemd for InactiveEnterTimestamp (got`)
  1551  		c.Check(s.argses, DeepEquals, [][]string{
  1552  			{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
  1553  		})
  1554  		c.Check(stamp.IsZero(), Equals, true)
  1555  	}
  1556  }
  1557  
  1558  func (s *SystemdTestSuite) TestInactiveEnterTimestampMalformedMore(c *C) {
  1559  	s.outs = [][]byte{
  1560  		[]byte(`InactiveEnterTimestamp=0`), // 0 is valid for InactiveEnterTimestampMonotonic
  1561  	}
  1562  	sysd := New(SystemMode, s.rep)
  1563  
  1564  	stamp, err := sysd.InactiveEnterTimestamp("bar.service")
  1565  
  1566  	c.Assert(err, ErrorMatches, `internal error: systemctl time output \(0\) is malformed`)
  1567  	c.Check(s.argses, DeepEquals, [][]string{
  1568  		{"show", "--property", "InactiveEnterTimestamp", "bar.service"},
  1569  	})
  1570  	c.Check(stamp.IsZero(), Equals, true)
  1571  }
  1572  
  1573  type systemdErrorSuite struct{}
  1574  
  1575  var _ = Suite(&systemdErrorSuite{})
  1576  
  1577  func (s *systemdErrorSuite) TestErrorStringNormalError(c *C) {
  1578  	systemctl := testutil.MockCommand(c, "systemctl", `echo "I fail"; exit 11`)
  1579  	defer systemctl.Restore()
  1580  
  1581  	_, err := Version()
  1582  	c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with exit status 11: I fail\n`)
  1583  }
  1584  
  1585  func (s *systemdErrorSuite) TestErrorStringNoOutput(c *C) {
  1586  	systemctl := testutil.MockCommand(c, "systemctl", `exit 22`)
  1587  	defer systemctl.Restore()
  1588  
  1589  	_, err := Version()
  1590  	c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with exit status 22`)
  1591  }
  1592  
  1593  func (s *systemdErrorSuite) TestErrorStringNoSystemctl(c *C) {
  1594  	oldPath := os.Getenv("PATH")
  1595  	os.Setenv("PATH", "/xxx")
  1596  	defer func() { os.Setenv("PATH", oldPath) }()
  1597  
  1598  	_, err := Version()
  1599  	c.Check(err, ErrorMatches, `systemctl command \[--version\] failed with: exec: "systemctl": executable file not found in \$PATH`)
  1600  }