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