github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/sandbox/cgroup/tracking_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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 cgroup_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"strings"
    27  
    28  	"github.com/godbus/dbus"
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/dbusutil"
    32  	"github.com/snapcore/snapd/dbusutil/dbustest"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/features"
    35  	"github.com/snapcore/snapd/logger"
    36  	"github.com/snapcore/snapd/sandbox/cgroup"
    37  	"github.com/snapcore/snapd/testutil"
    38  )
    39  
    40  func enableFeatures(c *C, ff ...features.SnapdFeature) {
    41  	c.Assert(os.MkdirAll(dirs.FeaturesDir, 0755), IsNil)
    42  	for _, f := range ff {
    43  		c.Assert(ioutil.WriteFile(f.ControlFile(), nil, 0755), IsNil)
    44  	}
    45  }
    46  
    47  type trackingSuite struct{}
    48  
    49  var _ = Suite(&trackingSuite{})
    50  
    51  func (s *trackingSuite) SetUpTest(c *C) {
    52  	dirs.SetRootDir(c.MkDir())
    53  }
    54  
    55  func (s *trackingSuite) TearDownTest(c *C) {
    56  	dirs.SetRootDir("")
    57  }
    58  
    59  // CreateTransientScopeForTracking is a no-op when refresh app awareness is off
    60  func (s *trackingSuite) TestCreateTransientScopeForTrackingFeatureDisabled(c *C) {
    61  	noDBus := func() (*dbus.Conn, error) {
    62  		c.Error("test sequence violated")
    63  		return nil, fmt.Errorf("dbus should not have been used")
    64  	}
    65  	restore := dbusutil.MockConnections(noDBus, noDBus)
    66  	defer restore()
    67  
    68  	c.Assert(features.RefreshAppAwareness.IsEnabled(), Equals, false)
    69  	err := cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
    70  	c.Check(err, IsNil)
    71  }
    72  
    73  // CreateTransientScopeForTracking does stuff when refresh app awareness is on
    74  func (s *trackingSuite) TestCreateTransientScopeForTrackingFeatureEnabled(c *C) {
    75  	// Pretend that refresh app awareness is enabled
    76  	enableFeatures(c, features.RefreshAppAwareness)
    77  	c.Assert(features.RefreshAppAwareness.IsEnabled(), Equals, true)
    78  	// Pretend we are a non-root user so that session bus is used.
    79  	restore := cgroup.MockOsGetuid(12345)
    80  	defer restore()
    81  	// Pretend our PID is this value.
    82  	restore = cgroup.MockOsGetpid(312123)
    83  	defer restore()
    84  	// Rig the random UUID generator to return this value.
    85  	uuid := "cc98cd01-6a25-46bd-b71b-82069b71b770"
    86  	restore = cgroup.MockRandomUUID(uuid)
    87  	defer restore()
    88  	// Replace interactions with DBus so that only session bus is available and responds with our logic.
    89  	conn, err := dbustest.Connection(func(msg *dbus.Message, n int) ([]*dbus.Message, error) {
    90  		switch n {
    91  		case 0:
    92  			return []*dbus.Message{checkAndRespondToStartTransientUnit(c, msg, "snap.pkg.app."+uuid+".scope", 312123)}, nil
    93  		}
    94  		return nil, fmt.Errorf("unexpected message #%d: %s", n, msg)
    95  	})
    96  	c.Assert(err, IsNil)
    97  	restore = dbusutil.MockOnlySessionBusAvailable(conn)
    98  	defer restore()
    99  	// Replace the cgroup analyzer function
   100  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   101  		return "/user.slice/user-12345.slice/user@12345.service/snap.pkg.app." + uuid + ".scope", nil
   102  	})
   103  	defer restore()
   104  
   105  	err = cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   106  	c.Check(err, IsNil)
   107  }
   108  
   109  func (s *trackingSuite) TestCreateTransientScopeForTrackingUnhappyNotRootGeneric(c *C) {
   110  	// Pretend that refresh app awareness is enabled
   111  	enableFeatures(c, features.RefreshAppAwareness)
   112  
   113  	// Hand out stub connections to both the system and session bus.
   114  	// Neither is really used here but they must appear to be available.
   115  	restore := dbusutil.MockConnections(dbustest.StubConnection, dbustest.StubConnection)
   116  	defer restore()
   117  
   118  	// Pretend we are a non-root user so that session bus is used.
   119  	restore = cgroup.MockOsGetuid(12345)
   120  	defer restore()
   121  	// Pretend our PID is this value.
   122  	restore = cgroup.MockOsGetpid(312123)
   123  	defer restore()
   124  
   125  	// Rig the cgroup analyzer to return an answer not related to the snap name.
   126  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   127  		return "foo", nil
   128  	})
   129  	defer restore()
   130  
   131  	// Pretend that attempting to create a transient scope fails with a canned error.
   132  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   133  		return fmt.Errorf("cannot create transient scope for testing")
   134  	})
   135  	defer restore()
   136  
   137  	// Create a transient scope and see it fail according to how doCreateTransientScope is rigged.
   138  	err := cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   139  	c.Assert(err, ErrorMatches, "cannot create transient scope for testing")
   140  
   141  	// Calling StartTransientUnit fails with org.freedesktop.DBus.UnknownMethod error.
   142  	// This is possible on old systemd or on deputy systemd.
   143  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   144  		return cgroup.ErrDBusUnknownMethod
   145  	})
   146  	defer restore()
   147  
   148  	// Attempts to create a transient scope fail with a special error
   149  	// indicating that we cannot track application process.
   150  	err = cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   151  	c.Assert(err, ErrorMatches, "cannot track application process")
   152  
   153  	// Calling StartTransientUnit fails with org.freedesktop.DBus.Spawn.ChildExited error.
   154  	// This is possible where we try to activate socket activate session bus
   155  	// but it's not available OR when we try to socket activate systemd --user.
   156  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   157  		return cgroup.ErrDBusSpawnChildExited
   158  	})
   159  	defer restore()
   160  
   161  	// Attempts to create a transient scope fail with a special error
   162  	// indicating that we cannot track application process and because we are
   163  	// not root, we do not attempt to fall back to the system bus.
   164  	err = cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   165  	c.Assert(err, ErrorMatches, "cannot track application process")
   166  }
   167  
   168  func (s *trackingSuite) TestCreateTransientScopeForTrackingUnhappyRootFallback(c *C) {
   169  	// Pretend that refresh app awareness is enabled
   170  	enableFeatures(c, features.RefreshAppAwareness)
   171  
   172  	// Hand out stub connections to both the system and session bus.
   173  	// Neither is really used here but they must appear to be available.
   174  	restore := dbusutil.MockConnections(dbustest.StubConnection, dbustest.StubConnection)
   175  	defer restore()
   176  
   177  	// Pretend we are a root user so that we attempt to use the system bus as fallback.
   178  	restore = cgroup.MockOsGetuid(0)
   179  	defer restore()
   180  	// Pretend our PID is this value.
   181  	restore = cgroup.MockOsGetpid(312123)
   182  	defer restore()
   183  
   184  	// Rig the random UUID generator to return this value.
   185  	uuid := "cc98cd01-6a25-46bd-b71b-82069b71b770"
   186  	restore = cgroup.MockRandomUUID(uuid)
   187  	defer restore()
   188  
   189  	// Calling StartTransientUnit fails on the session and then works on the system bus.
   190  	// This test emulates a root user falling back from the session bus to the system bus.
   191  	n := 0
   192  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   193  		n++
   194  		switch n {
   195  		case 1:
   196  			// On first try we fail. This is when we used the session bus/
   197  			return cgroup.ErrDBusSpawnChildExited
   198  		case 2:
   199  			// On second try we succeed.
   200  			return nil
   201  		}
   202  		panic("expected to call doCreateTransientScope at most twice")
   203  	})
   204  	defer restore()
   205  
   206  	// Rig the cgroup analyzer to pretend that we got placed into the system slice.
   207  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   208  		c.Assert(pid, Equals, 312123)
   209  		return "/system.slice/snap.pkg.app." + uuid + ".scope", nil
   210  	})
   211  	defer restore()
   212  
   213  	// Attempts to create a transient scope fail with a special error
   214  	// indicating that we cannot track application process and but because we were
   215  	// root we attempted to fall back to the system bus.
   216  	err := cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   217  	c.Assert(err, IsNil)
   218  }
   219  
   220  func (s *trackingSuite) TestCreateTransientScopeForTrackingUnhappyRootFailedFallback(c *C) {
   221  	// Pretend that refresh app awareness is enabled
   222  	enableFeatures(c, features.RefreshAppAwareness)
   223  
   224  	// Make it appear that session bus is there but system bus is not.
   225  	noSystemBus := func() (*dbus.Conn, error) {
   226  		return nil, fmt.Errorf("system bus is not available for testing")
   227  	}
   228  	restore := dbusutil.MockConnections(noSystemBus, dbustest.StubConnection)
   229  	defer restore()
   230  
   231  	// Pretend we are a root user so that we attempt to use the system bus as fallback.
   232  	restore = cgroup.MockOsGetuid(0)
   233  	defer restore()
   234  	// Pretend our PID is this value.
   235  	restore = cgroup.MockOsGetpid(312123)
   236  	defer restore()
   237  
   238  	// Rig the random UUID generator to return this value.
   239  	uuid := "cc98cd01-6a25-46bd-b71b-82069b71b770"
   240  	restore = cgroup.MockRandomUUID(uuid)
   241  	defer restore()
   242  
   243  	// Calling StartTransientUnit fails so that we try to use the system bus as fallback.
   244  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   245  		return cgroup.ErrDBusSpawnChildExited
   246  	})
   247  	defer restore()
   248  
   249  	// Rig the cgroup analyzer to return an answer not related to the snap name.
   250  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   251  		return "foo", nil
   252  	})
   253  	defer restore()
   254  
   255  	// Attempts to create a transient scope fail with a special error
   256  	// indicating that we cannot track application process.
   257  	err := cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   258  	c.Assert(err, ErrorMatches, "cannot track application process")
   259  }
   260  
   261  func (s *trackingSuite) TestCreateTransientScopeForTrackingUnhappyNoDBus(c *C) {
   262  	// Pretend that refresh app awareness is enabled
   263  	enableFeatures(c, features.RefreshAppAwareness)
   264  
   265  	// Make it appear that DBus is entirely unavailable.
   266  	noBus := func() (*dbus.Conn, error) {
   267  		return nil, fmt.Errorf("dbus is not available for testing")
   268  	}
   269  	restore := dbusutil.MockConnections(noBus, noBus)
   270  	defer restore()
   271  
   272  	// Pretend we are a root user so that we attempt to use the system bus as fallback.
   273  	restore = cgroup.MockOsGetuid(0)
   274  	defer restore()
   275  	// Pretend our PID is this value.
   276  	restore = cgroup.MockOsGetpid(312123)
   277  	defer restore()
   278  
   279  	// Rig the random UUID generator to return this value.
   280  	uuid := "cc98cd01-6a25-46bd-b71b-82069b71b770"
   281  	restore = cgroup.MockRandomUUID(uuid)
   282  	defer restore()
   283  
   284  	// Calling StartTransientUnit is not attempted without a DBus connection.
   285  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   286  		c.Error("test sequence violated")
   287  		return fmt.Errorf("test was not expected to create a transient scope")
   288  	})
   289  	defer restore()
   290  
   291  	// Disable the cgroup analyzer function as we don't expect it to be used in this test.
   292  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   293  		c.Error("test sequence violated")
   294  		return "", fmt.Errorf("test was not expected to measure process path in the tracking cgroup")
   295  	})
   296  	defer restore()
   297  
   298  	// Attempts to create a transient scope fail with a special error
   299  	// indicating that we cannot track application process.
   300  	err := cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   301  	c.Assert(err, ErrorMatches, "cannot track application process")
   302  }
   303  
   304  func (s *trackingSuite) TestCreateTransientScopeForTrackingSilentlyFails(c *C) {
   305  	// Pretend that refresh app awareness is enabled
   306  	enableFeatures(c, features.RefreshAppAwareness)
   307  
   308  	// Hand out stub connections to both the system and session bus.
   309  	// Neither is really used here but they must appear to be available.
   310  	restore := dbusutil.MockConnections(dbustest.StubConnection, dbustest.StubConnection)
   311  	defer restore()
   312  
   313  	// Pretend we are a non-root user.
   314  	restore = cgroup.MockOsGetuid(12345)
   315  	defer restore()
   316  	// Pretend our PID is this value.
   317  	restore = cgroup.MockOsGetpid(312123)
   318  	defer restore()
   319  
   320  	// Rig the random UUID generator to return this value.
   321  	uuid := "cc98cd01-6a25-46bd-b71b-82069b71b770"
   322  	restore = cgroup.MockRandomUUID(uuid)
   323  	defer restore()
   324  
   325  	// Calling StartTransientUnit succeeds but in reality does not move our
   326  	// process to the new cgroup hierarchy. This can happen when systemd
   327  	// version is < 238 and when the calling user is in a hierarchy that is
   328  	// owned by another user. One example is a user logging in remotely over
   329  	// ssh.
   330  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   331  		return nil
   332  	})
   333  	defer restore()
   334  
   335  	// Rig the cgroup analyzer to pretend that we are not placed in a snap-related slice.
   336  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   337  		c.Assert(pid, Equals, 312123)
   338  		return "/system.slice/foo.service", nil
   339  	})
   340  	defer restore()
   341  
   342  	// Attempts to create a transient scope fail with a special error
   343  	// indicating that we cannot track application process even though
   344  	// the DBus call has returned no error.
   345  	err := cgroup.CreateTransientScopeForTracking("snap.pkg.app", nil)
   346  	c.Assert(err, ErrorMatches, "cannot track application process")
   347  }
   348  
   349  func (s *trackingSuite) TestCreateTransientScopeForRootOnSystemBus(c *C) {
   350  	// Pretend that refresh app awareness is enabled
   351  	enableFeatures(c, features.RefreshAppAwareness)
   352  
   353  	// Hand out stub connections to both the system and session bus. Remember
   354  	// the identity of the system bus to that we can verify access later.
   355  	// Neither is really used here but they must appear to be available.
   356  	systemBus, err := dbustest.StubConnection()
   357  	c.Assert(err, IsNil)
   358  	restore := dbusutil.MockConnections(func() (*dbus.Conn, error) { return systemBus, nil }, dbustest.StubConnection)
   359  	defer restore()
   360  
   361  	// Pretend we are a root user. All hooks execute as root.
   362  	restore = cgroup.MockOsGetuid(0)
   363  	defer restore()
   364  
   365  	// Pretend our PID is this value.
   366  	restore = cgroup.MockOsGetpid(312123)
   367  	defer restore()
   368  
   369  	// Rig the random UUID generator to return this value.
   370  	uuid := "cc98cd01-6a25-46bd-b71b-82069b71b770"
   371  	restore = cgroup.MockRandomUUID(uuid)
   372  	defer restore()
   373  
   374  	// Pretend that attempting to create a transient scope succeeds.  Measure
   375  	// the bus used and the unit name provided by the caller.  Note that the
   376  	// call was made on the system bus, as requested by TrackingOptions below.
   377  	restore = cgroup.MockDoCreateTransientScope(func(conn *dbus.Conn, unitName string, pid int) error {
   378  		c.Assert(conn, Equals, systemBus)
   379  		c.Assert(unitName, Equals, "snap.pkg.app."+uuid+".scope")
   380  		return nil
   381  	})
   382  	defer restore()
   383  
   384  	// Rig the cgroup analyzer to indicate successful tracking.
   385  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   386  		return "snap.pkg.app." + uuid + ".scope", nil
   387  	})
   388  	defer restore()
   389  
   390  	// Create a transient scope and see it succeed.
   391  	err = cgroup.CreateTransientScopeForTracking("snap.pkg.app", &cgroup.TrackingOptions{AllowSessionBus: false})
   392  	c.Assert(err, IsNil)
   393  }
   394  
   395  func checkAndRespondToStartTransientUnit(c *C, msg *dbus.Message, scopeName string, pid int) *dbus.Message {
   396  	// XXX: Those types might live in a package somewhere
   397  	type Property struct {
   398  		Name  string
   399  		Value interface{}
   400  	}
   401  	type Unit struct {
   402  		Name  string
   403  		Props []Property
   404  	}
   405  	// Signature of StartTransientUnit, string, string, array of Property and array of Unit (see above).
   406  	requestSig := dbus.SignatureOf("", "", []Property{}, []Unit{})
   407  
   408  	c.Assert(msg.Type, Equals, dbus.TypeMethodCall)
   409  	c.Check(msg.Flags, Equals, dbus.Flags(0))
   410  	c.Check(msg.Headers, DeepEquals, map[dbus.HeaderField]dbus.Variant{
   411  		dbus.FieldDestination: dbus.MakeVariant("org.freedesktop.systemd1"),
   412  		dbus.FieldPath:        dbus.MakeVariant(dbus.ObjectPath("/org/freedesktop/systemd1")),
   413  		dbus.FieldInterface:   dbus.MakeVariant("org.freedesktop.systemd1.Manager"),
   414  		dbus.FieldMember:      dbus.MakeVariant("StartTransientUnit"),
   415  		dbus.FieldSignature:   dbus.MakeVariant(requestSig),
   416  	})
   417  	c.Check(msg.Body, DeepEquals, []interface{}{
   418  		scopeName,
   419  		"fail",
   420  		[][]interface{}{
   421  			{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})},
   422  		},
   423  		[][]interface{}{},
   424  	})
   425  
   426  	responseSig := dbus.SignatureOf(dbus.ObjectPath(""))
   427  	return &dbus.Message{
   428  		Type: dbus.TypeMethodReply,
   429  		Headers: map[dbus.HeaderField]dbus.Variant{
   430  			dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()),
   431  			dbus.FieldSender:      dbus.MakeVariant(":1"), // This does not matter.
   432  			// dbus.FieldDestination is provided automatically by DBus test helper.
   433  			dbus.FieldSignature: dbus.MakeVariant(responseSig),
   434  		},
   435  		// The object path returned in the body is not used by snap run yet.
   436  		Body: []interface{}{dbus.ObjectPath("/org/freedesktop/systemd1/job/1462")},
   437  	}
   438  }
   439  
   440  func checkAndFailToStartTransientUnit(c *C, msg *dbus.Message, errMsg string) *dbus.Message {
   441  	c.Assert(msg.Type, Equals, dbus.TypeMethodCall)
   442  	// ignore the message and just produce an error response
   443  	return &dbus.Message{
   444  		Type: dbus.TypeError,
   445  		Headers: map[dbus.HeaderField]dbus.Variant{
   446  			dbus.FieldReplySerial: dbus.MakeVariant(msg.Serial()),
   447  			dbus.FieldSender:      dbus.MakeVariant(":1"), // This does not matter.
   448  			// dbus.FieldDestination is provided automatically by DBus test helper.
   449  			dbus.FieldErrorName: dbus.MakeVariant(errMsg),
   450  		},
   451  	}
   452  }
   453  
   454  func (s *trackingSuite) TestDoCreateTransientScopeHappy(c *C) {
   455  	conn, err := dbustest.Connection(func(msg *dbus.Message, n int) ([]*dbus.Message, error) {
   456  		switch n {
   457  		case 0:
   458  			return []*dbus.Message{checkAndRespondToStartTransientUnit(c, msg, "foo.scope", 312123)}, nil
   459  		}
   460  		return nil, fmt.Errorf("unexpected message #%d: %s", n, msg)
   461  	})
   462  
   463  	c.Assert(err, IsNil)
   464  	defer conn.Close()
   465  	err = cgroup.DoCreateTransientScope(conn, "foo.scope", 312123)
   466  	c.Assert(err, IsNil)
   467  }
   468  
   469  func (s *trackingSuite) TestDoCreateTransientScopeForwardedErrors(c *C) {
   470  	// Certain errors are forwarded and handled in the logic calling into
   471  	// DoCreateTransientScope. Those are tested here.
   472  	for _, t := range []struct {
   473  		dbusError, msg string
   474  	}{
   475  		{"org.freedesktop.DBus.Error.NameHasNoOwner", "dbus name has no owner"},
   476  		{"org.freedesktop.DBus.Error.UnknownMethod", "unknown dbus object method"},
   477  		{"org.freedesktop.DBus.Error.Spawn.ChildExited", "dbus spawned child process exited"},
   478  	} {
   479  		conn, err := dbustest.Connection(func(msg *dbus.Message, n int) ([]*dbus.Message, error) {
   480  			switch n {
   481  			case 0:
   482  				return []*dbus.Message{checkAndFailToStartTransientUnit(c, msg, t.dbusError)}, nil
   483  			}
   484  			return nil, fmt.Errorf("unexpected message #%d: %s", n, msg)
   485  		})
   486  		c.Assert(err, IsNil)
   487  		defer conn.Close()
   488  		err = cgroup.DoCreateTransientScope(conn, "foo.scope", 312123)
   489  		c.Assert(strings.HasSuffix(err.Error(), fmt.Sprintf(" [%s]", t.dbusError)), Equals, true, Commentf("%q ~ %s", err, t.dbusError))
   490  		c.Check(err, ErrorMatches, t.msg+" .*")
   491  	}
   492  }
   493  
   494  func (s *trackingSuite) TestDoCreateTransientScopeClashingScopeName(c *C) {
   495  	// In case our UUID algorithm is bad and systemd reports that an unit with
   496  	// identical name already exists, we provide a special error handler for that.
   497  	errMsg := "org.freedesktop.systemd1.UnitExists"
   498  	conn, err := dbustest.Connection(func(msg *dbus.Message, n int) ([]*dbus.Message, error) {
   499  		switch n {
   500  		case 0:
   501  			return []*dbus.Message{checkAndFailToStartTransientUnit(c, msg, errMsg)}, nil
   502  		}
   503  		return nil, fmt.Errorf("unexpected message #%d: %s", n, msg)
   504  	})
   505  	c.Assert(err, IsNil)
   506  	defer conn.Close()
   507  	err = cgroup.DoCreateTransientScope(conn, "foo.scope", 312123)
   508  	c.Assert(err, ErrorMatches, "cannot create transient scope: scope .* clashed: .*")
   509  }
   510  
   511  func (s *trackingSuite) TestDoCreateTransientScopeOtherDBusErrors(c *C) {
   512  	// Other DBus errors are not special-cased and cause a generic failure handler.
   513  	errMsg := "org.example.BadHairDay"
   514  	conn, err := dbustest.Connection(func(msg *dbus.Message, n int) ([]*dbus.Message, error) {
   515  		switch n {
   516  		case 0:
   517  			return []*dbus.Message{checkAndFailToStartTransientUnit(c, msg, errMsg)}, nil
   518  		}
   519  		return nil, fmt.Errorf("unexpected message #%d: %s", n, msg)
   520  	})
   521  	c.Assert(err, IsNil)
   522  	defer conn.Close()
   523  	err = cgroup.DoCreateTransientScope(conn, "foo.scope", 312123)
   524  	c.Assert(err, ErrorMatches, `cannot create transient scope: DBus error "org.example.BadHairDay": \[\]`)
   525  }
   526  
   527  func (s *trackingSuite) TestSessionOrMaybeSystemBusTotalFailureForRoot(c *C) {
   528  	system := func() (*dbus.Conn, error) {
   529  		return nil, fmt.Errorf("system bus unavailable for testing")
   530  	}
   531  	session := func() (*dbus.Conn, error) {
   532  		return nil, fmt.Errorf("session bus unavailable for testing")
   533  	}
   534  	restore := dbusutil.MockConnections(system, session)
   535  	defer restore()
   536  	logBuf, restore := logger.MockLogger()
   537  	defer restore()
   538  	os.Setenv("SNAPD_DEBUG", "true")
   539  	defer os.Unsetenv("SNAPD_DEBUG")
   540  
   541  	uid := 0
   542  	isSession, conn, err := cgroup.SessionOrMaybeSystemBus(uid)
   543  	c.Assert(err, ErrorMatches, "system bus unavailable for testing")
   544  	c.Check(conn, IsNil)
   545  	c.Check(isSession, Equals, false)
   546  	c.Check(logBuf.String(), testutil.Contains, "DEBUG: session bus is not available: session bus unavailable for testing\n")
   547  	c.Check(logBuf.String(), testutil.Contains, "DEBUG: falling back to system bus\n")
   548  	c.Check(logBuf.String(), testutil.Contains, "DEBUG: system bus is not available: system bus unavailable for testing\n")
   549  }
   550  
   551  func (s *trackingSuite) TestSessionOrMaybeSystemBusFallbackForRoot(c *C) {
   552  	system := func() (*dbus.Conn, error) {
   553  		return dbustest.StubConnection()
   554  	}
   555  	session := func() (*dbus.Conn, error) {
   556  		return nil, fmt.Errorf("session bus unavailable for testing")
   557  	}
   558  	restore := dbusutil.MockConnections(system, session)
   559  	defer restore()
   560  	logBuf, restore := logger.MockLogger()
   561  	defer restore()
   562  	os.Setenv("SNAPD_DEBUG", "true")
   563  	defer os.Unsetenv("SNAPD_DEBUG")
   564  
   565  	uid := 0
   566  	isSession, conn, err := cgroup.SessionOrMaybeSystemBus(uid)
   567  	c.Assert(err, IsNil)
   568  	conn.Close()
   569  	c.Check(isSession, Equals, false)
   570  	c.Check(logBuf.String(), testutil.Contains, "DEBUG: session bus is not available: session bus unavailable for testing\n")
   571  	c.Check(logBuf.String(), testutil.Contains, "DEBUG: falling back to system bus\n")
   572  	c.Check(logBuf.String(), testutil.Contains, "DEBUG: using system bus now, session bus was not available\n")
   573  }
   574  
   575  func (s *trackingSuite) TestSessionOrMaybeSystemBusNonRootSessionFailure(c *C) {
   576  	system := func() (*dbus.Conn, error) {
   577  		return dbustest.StubConnection()
   578  	}
   579  	session := func() (*dbus.Conn, error) {
   580  		return nil, fmt.Errorf("session bus unavailable for testing")
   581  	}
   582  	restore := dbusutil.MockConnections(system, session)
   583  	defer restore()
   584  	logBuf, restore := logger.MockLogger()
   585  	defer restore()
   586  	os.Setenv("SNAPD_DEBUG", "true")
   587  	defer os.Unsetenv("SNAPD_DEBUG")
   588  
   589  	uid := 12345
   590  	isSession, conn, err := cgroup.SessionOrMaybeSystemBus(uid)
   591  	c.Assert(err, ErrorMatches, "session bus unavailable for testing")
   592  	c.Check(conn, IsNil)
   593  	c.Check(isSession, Equals, false)
   594  	c.Check(logBuf.String(), testutil.Contains, "DEBUG: session bus is not available: session bus unavailable for testing\n")
   595  }
   596  
   597  func (s *trackingSuite) TestConfirmSystemdServiceTrackingHappy(c *C) {
   598  	// Pretend our PID is this value.
   599  	restore := cgroup.MockOsGetpid(312123)
   600  	defer restore()
   601  	// Replace the cgroup analyzer function
   602  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   603  		c.Assert(pid, Equals, 312123)
   604  		return "/user.slice/user-12345.slice/user@12345.service/snap.pkg.app.service", nil
   605  	})
   606  	defer restore()
   607  
   608  	// With the cgroup path faked as above, we are being tracked as the systemd
   609  	// service so no error is reported.
   610  	err := cgroup.ConfirmSystemdServiceTracking("snap.pkg.app")
   611  	c.Assert(err, IsNil)
   612  }
   613  
   614  func (s *trackingSuite) TestConfirmSystemdServiceTrackingSad(c *C) {
   615  	// Pretend our PID is this value.
   616  	restore := cgroup.MockOsGetpid(312123)
   617  	defer restore()
   618  	// Replace the cgroup analyzer function
   619  	restore = cgroup.MockCgroupProcessPathInTrackingCgroup(func(pid int) (string, error) {
   620  		c.Assert(pid, Equals, 312123)
   621  		// Tracking path of a gnome terminal helper process. Meant to illustrate a tracking but not related to a snap application.
   622  		return "user.slice/user-12345.slice/user@12345.service/apps.slice/apps-org.gnome.Terminal.slice/vte-spawn-e640104a-cddf-4bd8-ba4b-2c1baf0270c3.scope", nil
   623  	})
   624  	defer restore()
   625  
   626  	// With the cgroup path faked as above, tracking is not effective.
   627  	err := cgroup.ConfirmSystemdServiceTracking("snap.pkg.app")
   628  	c.Assert(err, Equals, cgroup.ErrCannotTrackProcess)
   629  }