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