github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/overlord/snapstate/backend/link_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2016 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 backend_test
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"strings"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/boot"
    34  	"github.com/snapcore/snapd/boot/boottest"
    35  	"github.com/snapcore/snapd/bootloader"
    36  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    37  	"github.com/snapcore/snapd/cmd/snaplock/runinhibit"
    38  	"github.com/snapcore/snapd/dirs"
    39  	"github.com/snapcore/snapd/gadget/quantity"
    40  	"github.com/snapcore/snapd/osutil"
    41  	"github.com/snapcore/snapd/progress"
    42  	"github.com/snapcore/snapd/release"
    43  	"github.com/snapcore/snapd/snap"
    44  	"github.com/snapcore/snapd/snap/quota"
    45  	"github.com/snapcore/snapd/snap/snaptest"
    46  	"github.com/snapcore/snapd/systemd"
    47  	"github.com/snapcore/snapd/testutil"
    48  	"github.com/snapcore/snapd/timings"
    49  	"github.com/snapcore/snapd/wrappers"
    50  
    51  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    52  )
    53  
    54  type linkSuiteCommon struct {
    55  	testutil.BaseTest
    56  
    57  	be backend.Backend
    58  
    59  	perfTimings *timings.Timings
    60  }
    61  
    62  func (s *linkSuiteCommon) SetUpTest(c *C) {
    63  	s.BaseTest.SetUpTest(c)
    64  	dirs.SetRootDir(c.MkDir())
    65  	s.AddCleanup(func() { dirs.SetRootDir("") })
    66  
    67  	s.perfTimings = timings.New(nil)
    68  	restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
    69  		return []byte("ActiveState=inactive\n"), nil
    70  	})
    71  	s.AddCleanup(restore)
    72  }
    73  
    74  type linkSuite struct {
    75  	linkSuiteCommon
    76  }
    77  
    78  var _ = Suite(&linkSuite{})
    79  
    80  func (s *linkSuite) TestLinkDoUndoGenerateWrappers(c *C) {
    81  	const yaml = `name: hello
    82  version: 1.0
    83  environment:
    84   KEY: value
    85  
    86  slots:
    87    system-slot:
    88      interface: dbus
    89      bus: system
    90      name: org.example.System
    91    session-slot:
    92      interface: dbus
    93      bus: session
    94      name: org.example.Session
    95  
    96  apps:
    97   bin:
    98     command: bin
    99   svc:
   100     command: svc
   101     daemon: simple
   102   dbus-system:
   103     daemon: simple
   104     activates-on: [system-slot]
   105   dbus-session:
   106     daemon: simple
   107     daemon-scope: user
   108     activates-on: [session-slot]
   109  `
   110  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   111  
   112  	_, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   113  	c.Assert(err, IsNil)
   114  
   115  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   116  	c.Assert(err, IsNil)
   117  	c.Assert(l, HasLen, 1)
   118  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   119  	c.Assert(err, IsNil)
   120  	c.Assert(l, HasLen, 2)
   121  	l, err = filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "*.service"))
   122  	c.Assert(err, IsNil)
   123  	c.Assert(l, HasLen, 1)
   124  	l, err = filepath.Glob(filepath.Join(dirs.SnapDBusSystemServicesDir, "*.service"))
   125  	c.Assert(err, IsNil)
   126  	c.Assert(l, HasLen, 1)
   127  	l, err = filepath.Glob(filepath.Join(dirs.SnapDBusSessionServicesDir, "*.service"))
   128  	c.Assert(err, IsNil)
   129  	c.Assert(l, HasLen, 1)
   130  
   131  	// undo will remove
   132  	err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null)
   133  	c.Assert(err, IsNil)
   134  
   135  	l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   136  	c.Assert(err, IsNil)
   137  	c.Assert(l, HasLen, 0)
   138  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   139  	c.Assert(err, IsNil)
   140  	c.Assert(l, HasLen, 0)
   141  	l, err = filepath.Glob(filepath.Join(dirs.SnapUserServicesDir, "*.service"))
   142  	c.Assert(err, IsNil)
   143  	c.Assert(l, HasLen, 0)
   144  	l, err = filepath.Glob(filepath.Join(dirs.SnapDBusSystemServicesDir, "*.service"))
   145  	c.Assert(err, IsNil)
   146  	c.Assert(l, HasLen, 0)
   147  	l, err = filepath.Glob(filepath.Join(dirs.SnapDBusSessionServicesDir, "*.service"))
   148  	c.Assert(err, IsNil)
   149  	c.Assert(l, HasLen, 0)
   150  }
   151  
   152  func (s *linkSuite) TestLinkDoUndoCurrentSymlink(c *C) {
   153  	const yaml = `name: hello
   154  version: 1.0
   155  `
   156  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   157  
   158  	reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   159  	c.Assert(err, IsNil)
   160  
   161  	c.Check(reboot, Equals, false)
   162  
   163  	mountDir := info.MountDir()
   164  	dataDir := info.DataDir()
   165  	currentActiveSymlink := filepath.Join(mountDir, "..", "current")
   166  	currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink)
   167  	c.Assert(err, IsNil)
   168  	c.Assert(currentActiveDir, Equals, mountDir)
   169  
   170  	currentDataSymlink := filepath.Join(dataDir, "..", "current")
   171  	currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink)
   172  	c.Assert(err, IsNil)
   173  	c.Assert(currentDataDir, Equals, dataDir)
   174  
   175  	// undo will remove the symlinks
   176  	err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null)
   177  	c.Assert(err, IsNil)
   178  
   179  	c.Check(osutil.FileExists(currentActiveSymlink), Equals, false)
   180  	c.Check(osutil.FileExists(currentDataSymlink), Equals, false)
   181  
   182  }
   183  
   184  func (s *linkSuite) TestLinkSetNextBoot(c *C) {
   185  	coreDev := boottest.MockDevice("base")
   186  
   187  	bl := boottest.MockUC16Bootenv(bootloadertest.Mock("mock", c.MkDir()))
   188  	bootloader.Force(bl)
   189  	defer bootloader.Force(nil)
   190  	bl.SetBootBase("base_1.snap")
   191  
   192  	const yaml = `name: base
   193  version: 1.0
   194  type: base
   195  `
   196  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   197  
   198  	reboot, err := s.be.LinkSnap(info, coreDev, backend.LinkContext{}, s.perfTimings)
   199  	c.Assert(err, IsNil)
   200  	c.Check(reboot, Equals, true)
   201  }
   202  
   203  func (s *linkSuite) TestLinkDoIdempotent(c *C) {
   204  	// make sure that a retry wouldn't stumble on partial work
   205  
   206  	const yaml = `name: hello
   207  version: 1.0
   208  environment:
   209   KEY: value
   210  apps:
   211   bin:
   212     command: bin
   213   svc:
   214     command: svc
   215     daemon: simple
   216  `
   217  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   218  
   219  	_, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   220  	c.Assert(err, IsNil)
   221  
   222  	_, err = s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   223  	c.Assert(err, IsNil)
   224  
   225  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   226  	c.Assert(err, IsNil)
   227  	c.Assert(l, HasLen, 1)
   228  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   229  	c.Assert(err, IsNil)
   230  	c.Assert(l, HasLen, 1)
   231  
   232  	mountDir := info.MountDir()
   233  	dataDir := info.DataDir()
   234  	currentActiveSymlink := filepath.Join(mountDir, "..", "current")
   235  	currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink)
   236  	c.Assert(err, IsNil)
   237  	c.Assert(currentActiveDir, Equals, mountDir)
   238  
   239  	currentDataSymlink := filepath.Join(dataDir, "..", "current")
   240  	currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink)
   241  	c.Assert(err, IsNil)
   242  	c.Assert(currentDataDir, Equals, dataDir)
   243  
   244  	c.Check(filepath.Join(runinhibit.InhibitDir, "hello.lock"), testutil.FileAbsent)
   245  }
   246  
   247  func (s *linkSuite) TestLinkUndoIdempotent(c *C) {
   248  	// make sure that a retry wouldn't stumble on partial work
   249  
   250  	const yaml = `name: hello
   251  version: 1.0
   252  apps:
   253   bin:
   254     command: bin
   255   svc:
   256     command: svc
   257     daemon: simple
   258  `
   259  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   260  
   261  	_, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   262  	c.Assert(err, IsNil)
   263  
   264  	err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null)
   265  	c.Assert(err, IsNil)
   266  
   267  	err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null)
   268  	c.Assert(err, IsNil)
   269  
   270  	// no wrappers
   271  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   272  	c.Assert(err, IsNil)
   273  	c.Assert(l, HasLen, 0)
   274  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   275  	c.Assert(err, IsNil)
   276  	c.Assert(l, HasLen, 0)
   277  
   278  	// no symlinks
   279  	currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current")
   280  	currentDataSymlink := filepath.Join(info.DataDir(), "..", "current")
   281  	c.Check(osutil.FileExists(currentActiveSymlink), Equals, false)
   282  	c.Check(osutil.FileExists(currentDataSymlink), Equals, false)
   283  
   284  	// no inhibition lock
   285  	c.Check(filepath.Join(runinhibit.InhibitDir, "hello.lock"), testutil.FileAbsent)
   286  }
   287  
   288  func (s *linkSuite) TestLinkFailsForUnsetRevision(c *C) {
   289  	info := &snap.Info{
   290  		SuggestedName: "foo",
   291  	}
   292  	_, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   293  	c.Assert(err, ErrorMatches, `cannot link snap "foo" with unset revision`)
   294  }
   295  
   296  func mockSnapdSnapForLink(c *C) (snapdSnap *snap.Info, units [][]string) {
   297  	const yaml = `name: snapd
   298  version: 1.0
   299  type: snapd
   300  `
   301  	snapdUnits := [][]string{
   302  		// system services
   303  		{"lib/systemd/system/snapd.service", "[Unit]\n[Service]\nExecStart=/usr/lib/snapd/snapd\n# X-Snapd-Snap: do-not-start"},
   304  		{"lib/systemd/system/snapd.socket", "[Unit]\n[Socket]\nListenStream=/run/snapd.socket"},
   305  		{"lib/systemd/system/snapd.snap-repair.timer", "[Unit]\n[Timer]\nOnCalendar=*-*-* 5,11,17,23:00"},
   306  		// user services
   307  		{"usr/lib/systemd/user/snapd.session-agent.service", "[Unit]\n[Service]\nExecStart=/usr/bin/snap session-agent"},
   308  		{"usr/lib/systemd/user/snapd.session-agent.socket", "[Unit]\n[Socket]\nListenStream=%t/snap-session.socket"},
   309  	}
   310  	otherFiles := [][]string{
   311  		// D-Bus activation files
   312  		{"usr/share/dbus-1/services/io.snapcraft.Launcher.service", "[D-BUS Service]\nName=io.snapcraft.Launcher"},
   313  		{"usr/share/dbus-1/services/io.snapcraft.Settings.service", "[D-BUS Service]\nName=io.snapcraft.Settings"},
   314  		{"usr/share/dbus-1/services/io.snapcraft.SessionAgent.service", "[D-BUS Service]\nName=io.snapcraft.SessionAgent"},
   315  	}
   316  	info := snaptest.MockSnapWithFiles(c, yaml, &snap.SideInfo{Revision: snap.R(11)}, append(snapdUnits, otherFiles...))
   317  	return info, snapdUnits
   318  }
   319  
   320  func (s *linkSuite) TestLinkSnapdSnapOnCore(c *C) {
   321  	restore := release.MockOnClassic(false)
   322  	defer restore()
   323  
   324  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   325  	c.Assert(err, IsNil)
   326  	err = os.MkdirAll(dirs.SnapUserServicesDir, 0755)
   327  	c.Assert(err, IsNil)
   328  
   329  	info, _ := mockSnapdSnapForLink(c)
   330  
   331  	reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   332  	c.Assert(err, IsNil)
   333  	c.Assert(reboot, Equals, false)
   334  
   335  	// system services
   336  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), testutil.FileContains,
   337  		fmt.Sprintf("[Service]\nExecStart=%s/usr/lib/snapd/snapd\n", info.MountDir()))
   338  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.socket"), testutil.FileEquals,
   339  		"[Unit]\n[Socket]\nListenStream=/run/snapd.socket")
   340  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), testutil.FileEquals,
   341  		"[Unit]\n[Timer]\nOnCalendar=*-*-* 5,11,17,23:00")
   342  	// user services
   343  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), testutil.FileContains,
   344  		fmt.Sprintf("[Service]\nExecStart=%s/usr/bin/snap session-agent", info.MountDir()))
   345  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.socket"), testutil.FileEquals,
   346  		"[Unit]\n[Socket]\nListenStream=%t/snap-session.socket")
   347  	// auxiliary mount unit
   348  	mountUnit := fmt.Sprintf(`[Unit]
   349  Description=Make the snapd snap tooling available for the system
   350  Before=snapd.service
   351  
   352  [Mount]
   353  What=%s/usr/lib/snapd
   354  Where=/usr/lib/snapd
   355  Type=none
   356  Options=bind
   357  
   358  [Install]
   359  WantedBy=snapd.service
   360  `, info.MountDir())
   361  	c.Check(filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), testutil.FileEquals, mountUnit)
   362  	// D-Bus service activation files for snap userd
   363  	c.Check(filepath.Join(dirs.SnapDBusSessionServicesDir, "io.snapcraft.Launcher.service"), testutil.FileEquals,
   364  		"[D-BUS Service]\nName=io.snapcraft.Launcher")
   365  	c.Check(filepath.Join(dirs.SnapDBusSessionServicesDir, "io.snapcraft.Settings.service"), testutil.FileEquals,
   366  		"[D-BUS Service]\nName=io.snapcraft.Settings")
   367  	c.Check(filepath.Join(dirs.SnapDBusSessionServicesDir, "io.snapcraft.SessionAgent.service"), testutil.FileEquals,
   368  		"[D-BUS Service]\nName=io.snapcraft.SessionAgent")
   369  }
   370  
   371  type linkCleanupSuite struct {
   372  	linkSuiteCommon
   373  	info *snap.Info
   374  }
   375  
   376  var _ = Suite(&linkCleanupSuite{})
   377  
   378  func (s *linkCleanupSuite) SetUpTest(c *C) {
   379  	s.linkSuiteCommon.SetUpTest(c)
   380  
   381  	const yaml = `name: hello
   382  version: 1.0
   383  environment:
   384   KEY: value
   385  
   386  slots:
   387    system-slot:
   388      interface: dbus
   389      bus: system
   390      name: org.example.System
   391    session-slot:
   392      interface: dbus
   393      bus: session
   394      name: org.example.Session
   395  
   396  apps:
   397   foo:
   398     command: foo
   399   bar:
   400     command: bar
   401   svc:
   402     command: svc
   403     daemon: simple
   404   dbus-system:
   405     daemon: simple
   406     activates-on: [system-slot]
   407   dbus-session:
   408     daemon: simple
   409     daemon-scope: user
   410     activates-on: [session-slot]
   411  `
   412  	cmd := testutil.MockCommand(c, "update-desktop-database", "")
   413  	s.AddCleanup(cmd.Restore)
   414  
   415  	s.info = snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   416  
   417  	guiDir := filepath.Join(s.info.MountDir(), "meta", "gui")
   418  	c.Assert(os.MkdirAll(guiDir, 0755), IsNil)
   419  	c.Assert(ioutil.WriteFile(filepath.Join(guiDir, "bin.desktop"), []byte(`
   420  [Desktop Entry]
   421  Name=bin
   422  Icon=${SNAP}/bin.png
   423  Exec=bin
   424  `), 0644), IsNil)
   425  
   426  	// sanity checks
   427  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir, dirs.SnapDBusSystemServicesDir, dirs.SnapDBusSessionServicesDir} {
   428  		os.MkdirAll(d, 0755)
   429  		l, err := filepath.Glob(filepath.Join(d, "*"))
   430  		c.Assert(err, IsNil, Commentf(d))
   431  		c.Assert(l, HasLen, 0, Commentf(d))
   432  	}
   433  }
   434  
   435  func (s *linkCleanupSuite) testLinkCleanupDirOnFail(c *C, dir string) {
   436  	c.Assert(os.Chmod(dir, 0555), IsNil)
   437  	defer os.Chmod(dir, 0755)
   438  
   439  	_, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings)
   440  	c.Assert(err, NotNil)
   441  	_, isPathError := err.(*os.PathError)
   442  	_, isLinkError := err.(*os.LinkError)
   443  	c.Assert(isPathError || isLinkError, Equals, true, Commentf("%#v", err))
   444  
   445  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir, dirs.SnapDBusSystemServicesDir, dirs.SnapDBusSessionServicesDir} {
   446  		l, err := filepath.Glob(filepath.Join(d, "*"))
   447  		c.Check(err, IsNil, Commentf(d))
   448  		c.Check(l, HasLen, 0, Commentf(d))
   449  	}
   450  }
   451  
   452  func (s *linkCleanupSuite) TestLinkCleanupOnDesktopFail(c *C) {
   453  	s.testLinkCleanupDirOnFail(c, dirs.SnapDesktopFilesDir)
   454  }
   455  
   456  func (s *linkCleanupSuite) TestLinkCleanupOnBinariesFail(c *C) {
   457  	// this one is the trivial case _as the code stands today_,
   458  	// but nothing guarantees that ordering.
   459  	s.testLinkCleanupDirOnFail(c, dirs.SnapBinariesDir)
   460  }
   461  
   462  func (s *linkCleanupSuite) TestLinkCleanupOnServicesFail(c *C) {
   463  	s.testLinkCleanupDirOnFail(c, dirs.SnapServicesDir)
   464  }
   465  
   466  func (s *linkCleanupSuite) TestLinkCleanupOnMountDirFail(c *C) {
   467  	s.testLinkCleanupDirOnFail(c, filepath.Dir(s.info.MountDir()))
   468  }
   469  
   470  func (s *linkCleanupSuite) TestLinkCleanupOnDBusSystemFail(c *C) {
   471  	s.testLinkCleanupDirOnFail(c, dirs.SnapDBusSystemServicesDir)
   472  }
   473  
   474  func (s *linkCleanupSuite) TestLinkCleanupOnDBusSessionFail(c *C) {
   475  	s.testLinkCleanupDirOnFail(c, dirs.SnapDBusSessionServicesDir)
   476  }
   477  
   478  func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) {
   479  	r := systemd.MockSystemctl(func(...string) ([]byte, error) {
   480  		return nil, errors.New("ouchie")
   481  	})
   482  	defer r()
   483  
   484  	_, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings)
   485  	c.Assert(err, ErrorMatches, "ouchie")
   486  
   487  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
   488  		l, err := filepath.Glob(filepath.Join(d, "*"))
   489  		c.Check(err, IsNil, Commentf(d))
   490  		c.Check(l, HasLen, 0, Commentf(d))
   491  	}
   492  }
   493  
   494  func (s *linkCleanupSuite) TestLinkCleansUpDataDirAndSymlinksOnSymlinkFail(c *C) {
   495  	// sanity check
   496  	c.Assert(s.info.DataDir(), testutil.FileAbsent)
   497  
   498  	// the mountdir symlink is currently the last thing in LinkSnap that can
   499  	// make it fail, creating a symlink requires write permissions
   500  	d := filepath.Dir(s.info.MountDir())
   501  	c.Assert(os.Chmod(d, 0555), IsNil)
   502  	defer os.Chmod(d, 0755)
   503  
   504  	_, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings)
   505  	c.Assert(err, ErrorMatches, `(?i).*symlink.*permission denied.*`)
   506  
   507  	c.Check(s.info.DataDir(), testutil.FileAbsent)
   508  	c.Check(filepath.Join(s.info.DataDir(), "..", "current"), testutil.FileAbsent)
   509  	c.Check(filepath.Join(s.info.MountDir(), "..", "current"), testutil.FileAbsent)
   510  }
   511  
   512  func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesClassic(c *C) {
   513  	current := filepath.Join(s.info.MountDir(), "..", "current")
   514  
   515  	for _, dev := range []boot.Device{mockDev, mockClassicDev} {
   516  		var updateFontconfigCaches int
   517  		restore := backend.MockUpdateFontconfigCaches(func() error {
   518  			c.Assert(osutil.FileExists(current), Equals, false)
   519  			updateFontconfigCaches += 1
   520  			return nil
   521  		})
   522  		defer restore()
   523  
   524  		_, err := s.be.LinkSnap(s.info, dev, backend.LinkContext{}, s.perfTimings)
   525  		c.Assert(err, IsNil)
   526  		if dev.Classic() {
   527  			c.Assert(updateFontconfigCaches, Equals, 1)
   528  		} else {
   529  			c.Assert(updateFontconfigCaches, Equals, 0)
   530  		}
   531  		c.Assert(os.Remove(current), IsNil)
   532  	}
   533  }
   534  
   535  func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesCallsFromNewCurrent(c *C) {
   536  	const yaml = `name: core
   537  version: 1.0
   538  type: os
   539  `
   540  	// old version is 'current'
   541  	infoOld := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   542  	mountDirOld := infoOld.MountDir()
   543  	err := os.Symlink(filepath.Base(mountDirOld), filepath.Join(mountDirOld, "..", "current"))
   544  	c.Assert(err, IsNil)
   545  
   546  	oldCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v6"), "")
   547  	oldCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v7"), "")
   548  
   549  	infoNew := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(12)})
   550  	mountDirNew := infoNew.MountDir()
   551  
   552  	newCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v6"), "")
   553  	newCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v7"), "")
   554  
   555  	// provide our own mock, osutil.CommandFromCore expects an ELF binary
   556  	restore := backend.MockCommandFromSystemSnap(func(name string, args ...string) (*exec.Cmd, error) {
   557  		cmd := filepath.Join(dirs.SnapMountDir, "core", "current", name)
   558  		c.Logf("command from core: %v", cmd)
   559  		return exec.Command(cmd, args...), nil
   560  	})
   561  	defer restore()
   562  
   563  	_, err = s.be.LinkSnap(infoNew, mockClassicDev, backend.LinkContext{}, s.perfTimings)
   564  	c.Assert(err, IsNil)
   565  
   566  	c.Check(oldCmdV6.Calls(), HasLen, 0)
   567  	c.Check(oldCmdV7.Calls(), HasLen, 0)
   568  
   569  	c.Check(newCmdV6.Calls(), DeepEquals, [][]string{
   570  		{"fc-cache-v6", "--system-only"},
   571  	})
   572  	c.Check(newCmdV7.Calls(), DeepEquals, [][]string{
   573  		{"fc-cache-v7", "--system-only"},
   574  	})
   575  }
   576  
   577  func (s *linkCleanupSuite) testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c *C, firstInstall bool) {
   578  	dirs.SetRootDir(c.MkDir())
   579  	defer dirs.SetRootDir("")
   580  
   581  	info, _ := mockSnapdSnapForLink(c)
   582  
   583  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   584  	c.Assert(err, IsNil)
   585  	err = os.MkdirAll(dirs.SnapUserServicesDir, 0755)
   586  	c.Assert(err, IsNil)
   587  
   588  	// make snap mount dir non-writable, triggers error updating the current symlink
   589  	snapdSnapDir := filepath.Dir(info.MountDir())
   590  
   591  	if firstInstall {
   592  		err := os.Remove(filepath.Join(snapdSnapDir, "1234"))
   593  		c.Assert(err == nil || os.IsNotExist(err), Equals, true, Commentf("err: %v, err"))
   594  	} else {
   595  		err := os.Mkdir(filepath.Join(snapdSnapDir, "1234"), 0755)
   596  		c.Assert(err, IsNil)
   597  	}
   598  
   599  	// triggers permission denied error when symlink is manipulated
   600  	err = os.Chmod(snapdSnapDir, 0555)
   601  	c.Assert(err, IsNil)
   602  	defer os.Chmod(snapdSnapDir, 0755)
   603  
   604  	linkCtx := backend.LinkContext{
   605  		FirstInstall: firstInstall,
   606  	}
   607  	reboot, err := s.be.LinkSnap(info, mockDev, linkCtx, s.perfTimings)
   608  	c.Assert(err, ErrorMatches, fmt.Sprintf("symlink %s /.*/snapd/current: permission denied", info.Revision))
   609  	c.Assert(reboot, Equals, false)
   610  
   611  	checker := testutil.FilePresent
   612  	if firstInstall {
   613  		checker = testutil.FileAbsent
   614  	}
   615  
   616  	// system services
   617  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), checker)
   618  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.socket"), checker)
   619  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), checker)
   620  	// user services
   621  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), checker)
   622  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.socket"), checker)
   623  	c.Check(filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), checker)
   624  	c.Check(filepath.Join(runinhibit.InhibitDir, "snapd.lock"), testutil.FileAbsent)
   625  
   626  	// D-Bus service activation
   627  	c.Check(filepath.Join(dirs.SnapDBusSessionServicesDir, "io.snapcraft.Launcher.service"), checker)
   628  	c.Check(filepath.Join(dirs.SnapDBusSessionServicesDir, "io.snapcraft.Settings.service"), checker)
   629  	c.Check(filepath.Join(dirs.SnapDBusSessionServicesDir, "io.snapcraft.SessionAgent.service"), checker)
   630  }
   631  
   632  func (s *linkCleanupSuite) TestLinkCleanupFailedSnapdSnapFirstInstallOnCore(c *C) {
   633  	// test failure mode when snapd is first installed, its units were
   634  	// correctly written and corresponding services were started, but
   635  	// current symlink failed
   636  	restore := release.MockOnClassic(false)
   637  	defer restore()
   638  	s.testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c, true)
   639  }
   640  
   641  func (s *linkCleanupSuite) TestLinkCleanupFailedSnapdSnapNonFirstInstallOnCore(c *C) {
   642  	// test failure mode when a new revision of snapd is installed, its was
   643  	// units were correctly written and corresponding services were started,
   644  	// but current symlink failed
   645  	restore := release.MockOnClassic(false)
   646  	defer restore()
   647  	s.testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c, false)
   648  }
   649  
   650  type snapdOnCoreUnlinkSuite struct {
   651  	linkSuiteCommon
   652  }
   653  
   654  var _ = Suite(&snapdOnCoreUnlinkSuite{})
   655  
   656  func (s *snapdOnCoreUnlinkSuite) TestUndoGeneratedWrappers(c *C) {
   657  	restore := release.MockOnClassic(false)
   658  	defer restore()
   659  	restore = release.MockReleaseInfo(&release.OS{ID: "ubuntu"})
   660  	defer restore()
   661  
   662  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   663  	c.Assert(err, IsNil)
   664  	err = os.MkdirAll(dirs.SnapUserServicesDir, 0755)
   665  	c.Assert(err, IsNil)
   666  
   667  	info, snapdUnits := mockSnapdSnapForLink(c)
   668  	// all generated untis
   669  	generatedSnapdUnits := append(snapdUnits,
   670  		[]string{"usr-lib-snapd.mount", "mount unit"})
   671  
   672  	toEtcUnitPath := func(p string) string {
   673  		if strings.HasPrefix(p, "usr/lib/systemd/user") {
   674  			return filepath.Join(dirs.SnapUserServicesDir, filepath.Base(p))
   675  		}
   676  		return filepath.Join(dirs.SnapServicesDir, filepath.Base(p))
   677  	}
   678  
   679  	reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   680  	c.Assert(err, IsNil)
   681  	c.Assert(reboot, Equals, false)
   682  
   683  	// sanity checks
   684  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), testutil.FileContains,
   685  		fmt.Sprintf("[Service]\nExecStart=%s/usr/lib/snapd/snapd\n", info.MountDir()))
   686  	// expecting all generated untis to be present
   687  	for _, entry := range generatedSnapdUnits {
   688  		c.Check(toEtcUnitPath(entry[0]), testutil.FilePresent)
   689  	}
   690  	// linked snaps do not have a run inhibition lock
   691  	c.Check(filepath.Join(runinhibit.InhibitDir, "snapd.lock"), testutil.FileAbsent)
   692  
   693  	linkCtx := backend.LinkContext{
   694  		FirstInstall:   true,
   695  		RunInhibitHint: runinhibit.HintInhibitedForRefresh,
   696  	}
   697  	err = s.be.UnlinkSnap(info, linkCtx, nil)
   698  	c.Assert(err, IsNil)
   699  
   700  	// generated wrappers should be gone now
   701  	for _, entry := range generatedSnapdUnits {
   702  		c.Check(toEtcUnitPath(entry[0]), testutil.FileAbsent)
   703  	}
   704  	// unlinked snaps have a run inhibition lock
   705  	c.Check(filepath.Join(runinhibit.InhibitDir, "snapd.lock"), testutil.FilePresent)
   706  
   707  	// unlink is idempotent
   708  	err = s.be.UnlinkSnap(info, linkCtx, nil)
   709  	c.Assert(err, IsNil)
   710  	c.Check(filepath.Join(runinhibit.InhibitDir, "snapd.lock"), testutil.FilePresent)
   711  }
   712  
   713  func (s *snapdOnCoreUnlinkSuite) TestUnlinkNonFirstSnapdOnCoreDoesNothing(c *C) {
   714  	restore := release.MockOnClassic(false)
   715  	defer restore()
   716  
   717  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   718  	c.Assert(err, IsNil)
   719  	err = os.MkdirAll(dirs.SnapUserServicesDir, 0755)
   720  	c.Assert(err, IsNil)
   721  
   722  	info, _ := mockSnapdSnapForLink(c)
   723  
   724  	units := [][]string{
   725  		{filepath.Join(dirs.SnapServicesDir, "snapd.service"), "precious"},
   726  		{filepath.Join(dirs.SnapServicesDir, "snapd.socket"), "precious"},
   727  		{filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), "precious"},
   728  		{filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), "precious"},
   729  		{filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), "precious"},
   730  		{filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agentsocket"), "precious"},
   731  	}
   732  	// content list uses absolute paths already
   733  	snaptest.PopulateDir("/", units)
   734  	linkCtx := backend.LinkContext{
   735  		FirstInstall:   false,
   736  		RunInhibitHint: runinhibit.HintInhibitedForRefresh,
   737  	}
   738  	err = s.be.UnlinkSnap(info, linkCtx, nil)
   739  	c.Assert(err, IsNil)
   740  	for _, unit := range units {
   741  		c.Check(unit[0], testutil.FileEquals, "precious")
   742  	}
   743  
   744  	// unlinked snaps have a run inhibition lock. XXX: the specific inhibition hint can change.
   745  	c.Check(filepath.Join(runinhibit.InhibitDir, "snapd.lock"), testutil.FilePresent)
   746  	c.Check(filepath.Join(runinhibit.InhibitDir, "snapd.lock"), testutil.FileEquals, "refresh")
   747  }
   748  
   749  func (s *linkSuite) TestLinkOptRequiresTooling(c *C) {
   750  	const yaml = `name: hello
   751  version: 1.0
   752  
   753  apps:
   754   svc:
   755     command: svc
   756     daemon: simple
   757  `
   758  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   759  
   760  	linkCtxWithTooling := backend.LinkContext{
   761  		RequireMountedSnapdSnap: true,
   762  	}
   763  	_, err := s.be.LinkSnap(info, mockDev, linkCtxWithTooling, s.perfTimings)
   764  	c.Assert(err, IsNil)
   765  	c.Assert(filepath.Join(dirs.SnapServicesDir, "snap.hello.svc.service"), testutil.FileContains,
   766  		`Wants=usr-lib-snapd.mount
   767  After=usr-lib-snapd.mount`)
   768  
   769  	// remove it now
   770  	err = s.be.UnlinkSnap(info, linkCtxWithTooling, nil)
   771  	c.Assert(err, IsNil)
   772  	c.Assert(filepath.Join(dirs.SnapServicesDir, "snap.hello.svc.service"), testutil.FileAbsent)
   773  
   774  	linkCtxNoTooling := backend.LinkContext{
   775  		RequireMountedSnapdSnap: false,
   776  	}
   777  	_, err = s.be.LinkSnap(info, mockDev, linkCtxNoTooling, s.perfTimings)
   778  	c.Assert(err, IsNil)
   779  	c.Assert(filepath.Join(dirs.SnapServicesDir, "snap.hello.svc.service"), Not(testutil.FileContains), `usr-lib-snapd.mount`)
   780  }
   781  
   782  func (s *linkSuite) TestLinkOptHasQuotaGroup(c *C) {
   783  	const yaml = `name: hello
   784  version: 1.0
   785  
   786  apps:
   787   svc:
   788     command: svc
   789     daemon: simple
   790  `
   791  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   792  
   793  	grp, err := quota.NewGroup("foogroup", quantity.SizeMiB)
   794  	c.Assert(err, IsNil)
   795  
   796  	linkCtxWithGroup := backend.LinkContext{
   797  		ServiceOptions: &wrappers.SnapServiceOptions{
   798  			QuotaGroup: grp,
   799  		},
   800  	}
   801  	_, err = s.be.LinkSnap(info, mockDev, linkCtxWithGroup, s.perfTimings)
   802  	c.Assert(err, IsNil)
   803  	c.Assert(filepath.Join(dirs.SnapServicesDir, "snap.hello.svc.service"), testutil.FileContains,
   804  		"\nSlice=snap.foogroup.slice\n")
   805  }