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