github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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/dirs"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/progress"
    40  	"github.com/snapcore/snapd/release"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/snaptest"
    43  	"github.com/snapcore/snapd/systemd"
    44  	"github.com/snapcore/snapd/testutil"
    45  	"github.com/snapcore/snapd/timings"
    46  
    47  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    48  )
    49  
    50  type linkSuiteCommon struct {
    51  	testutil.BaseTest
    52  
    53  	be backend.Backend
    54  
    55  	perfTimings *timings.Timings
    56  }
    57  
    58  func (s *linkSuiteCommon) SetUpTest(c *C) {
    59  	s.BaseTest.SetUpTest(c)
    60  	dirs.SetRootDir(c.MkDir())
    61  	s.AddCleanup(func() { dirs.SetRootDir("") })
    62  
    63  	s.perfTimings = timings.New(nil)
    64  	restore := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
    65  		return []byte("ActiveState=inactive\n"), nil
    66  	})
    67  	s.AddCleanup(restore)
    68  }
    69  
    70  type linkSuite struct {
    71  	linkSuiteCommon
    72  }
    73  
    74  var _ = Suite(&linkSuite{})
    75  
    76  func (s *linkSuite) TestLinkSnapGivesLastActiveDisabledServicesToWrappers(c *C) {
    77  	const yaml = `name: hello
    78  version: 1.0
    79  environment:
    80   KEY: value
    81  
    82  apps:
    83   bin:
    84     command: bin
    85     daemon: simple
    86   svc:
    87     command: svc
    88     daemon: simple
    89  `
    90  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
    91  
    92  	svcsDisabled := []string{}
    93  	r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
    94  		// drop --root from the cmd
    95  		if len(cmd) >= 3 && cmd[0] == "--root" {
    96  			cmd = cmd[2:]
    97  		}
    98  		// if it's an enable, save the service name to check later
    99  		if len(cmd) >= 2 && cmd[0] == "enable" {
   100  			svcsDisabled = append(svcsDisabled, cmd[1])
   101  		}
   102  		return nil, nil
   103  	})
   104  	defer r()
   105  
   106  	linkCtx := backend.LinkContext{
   107  		PrevDisabledServices: []string{"svc"},
   108  	}
   109  	_, err := s.be.LinkSnap(info, mockDev, linkCtx, s.perfTimings)
   110  	c.Assert(err, IsNil)
   111  
   112  	c.Assert(svcsDisabled, DeepEquals, []string{"snap.hello.bin.service"})
   113  }
   114  
   115  func (s *linkSuite) TestLinkDoUndoGenerateWrappers(c *C) {
   116  	const yaml = `name: hello
   117  version: 1.0
   118  environment:
   119   KEY: value
   120  
   121  apps:
   122   bin:
   123     command: bin
   124   svc:
   125     command: svc
   126     daemon: simple
   127  `
   128  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   129  
   130  	_, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   131  	c.Assert(err, IsNil)
   132  
   133  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   134  	c.Assert(err, IsNil)
   135  	c.Assert(l, HasLen, 1)
   136  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   137  	c.Assert(err, IsNil)
   138  	c.Assert(l, HasLen, 1)
   139  
   140  	// undo will remove
   141  	err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null)
   142  	c.Assert(err, IsNil)
   143  
   144  	l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   145  	c.Assert(err, IsNil)
   146  	c.Assert(l, HasLen, 0)
   147  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.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  
   245  func (s *linkSuite) TestLinkUndoIdempotent(c *C) {
   246  	// make sure that a retry wouldn't stumble on partial work
   247  
   248  	const yaml = `name: hello
   249  version: 1.0
   250  apps:
   251   bin:
   252     command: bin
   253   svc:
   254     command: svc
   255     daemon: simple
   256  `
   257  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   258  
   259  	_, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   260  	c.Assert(err, IsNil)
   261  
   262  	err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null)
   263  	c.Assert(err, IsNil)
   264  
   265  	err = s.be.UnlinkSnap(info, backend.LinkContext{}, progress.Null)
   266  	c.Assert(err, IsNil)
   267  
   268  	// no wrappers
   269  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   270  	c.Assert(err, IsNil)
   271  	c.Assert(l, HasLen, 0)
   272  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   273  	c.Assert(err, IsNil)
   274  	c.Assert(l, HasLen, 0)
   275  
   276  	// no symlinks
   277  	currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current")
   278  	currentDataSymlink := filepath.Join(info.DataDir(), "..", "current")
   279  	c.Check(osutil.FileExists(currentActiveSymlink), Equals, false)
   280  	c.Check(osutil.FileExists(currentDataSymlink), Equals, false)
   281  }
   282  
   283  func (s *linkSuite) TestLinkFailsForUnsetRevision(c *C) {
   284  	info := &snap.Info{
   285  		SuggestedName: "foo",
   286  	}
   287  	_, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   288  	c.Assert(err, ErrorMatches, `cannot link snap "foo" with unset revision`)
   289  }
   290  
   291  func mockSnapdSnapForLink(c *C) (snapdSnap *snap.Info, units [][]string) {
   292  	const yaml = `name: snapd
   293  version: 1.0
   294  type: snapd
   295  `
   296  	snapdUnits := [][]string{
   297  		// system services
   298  		{"lib/systemd/system/snapd.service", "[Unit]\n[Service]\nExecStart=/usr/lib/snapd/snapd\n# X-Snapd-Snap: do-not-start"},
   299  		{"lib/systemd/system/snapd.socket", "[Unit]\n[Socket]\nListenStream=/run/snapd.socket"},
   300  		{"lib/systemd/system/snapd.snap-repair.timer", "[Unit]\n[Timer]\nOnCalendar=*-*-* 5,11,17,23:00"},
   301  		// user services
   302  		{"usr/lib/systemd/user/snapd.session-agent.service", "[Unit]\n[Service]\nExecStart=/usr/bin/snap session-agent"},
   303  		{"usr/lib/systemd/user/snapd.session-agent.socket", "[Unit]\n[Socket]\nListenStream=%t/snap-session.socket"},
   304  	}
   305  	info := snaptest.MockSnapWithFiles(c, yaml, &snap.SideInfo{Revision: snap.R(11)}, snapdUnits)
   306  	return info, snapdUnits
   307  }
   308  
   309  func (s *linkSuite) TestLinkSnapdSnapOnCore(c *C) {
   310  	restore := release.MockOnClassic(false)
   311  	defer restore()
   312  
   313  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   314  	c.Assert(err, IsNil)
   315  	err = os.MkdirAll(dirs.SnapUserServicesDir, 0755)
   316  	c.Assert(err, IsNil)
   317  
   318  	info, _ := mockSnapdSnapForLink(c)
   319  
   320  	reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   321  	c.Assert(err, IsNil)
   322  	c.Assert(reboot, Equals, false)
   323  
   324  	// system services
   325  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), testutil.FileContains,
   326  		fmt.Sprintf("[Service]\nExecStart=%s/usr/lib/snapd/snapd\n", info.MountDir()))
   327  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.socket"), testutil.FileEquals,
   328  		"[Unit]\n[Socket]\nListenStream=/run/snapd.socket")
   329  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), testutil.FileEquals,
   330  		"[Unit]\n[Timer]\nOnCalendar=*-*-* 5,11,17,23:00")
   331  	// user services
   332  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), testutil.FileContains,
   333  		fmt.Sprintf("[Service]\nExecStart=%s/usr/bin/snap session-agent", info.MountDir()))
   334  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.socket"), testutil.FileEquals,
   335  		"[Unit]\n[Socket]\nListenStream=%t/snap-session.socket")
   336  	// auxiliary mount unit
   337  	mountUnit := fmt.Sprintf(`[Unit]
   338  Description=Make the snapd snap tooling available for the system
   339  Before=snapd.service
   340  
   341  [Mount]
   342  What=%s/usr/lib/snapd
   343  Where=/usr/lib/snapd
   344  Type=none
   345  Options=bind
   346  
   347  [Install]
   348  WantedBy=snapd.service
   349  `, info.MountDir())
   350  	c.Check(filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), testutil.FileEquals, mountUnit)
   351  }
   352  
   353  type linkCleanupSuite struct {
   354  	linkSuiteCommon
   355  	info *snap.Info
   356  }
   357  
   358  var _ = Suite(&linkCleanupSuite{})
   359  
   360  func (s *linkCleanupSuite) SetUpTest(c *C) {
   361  	s.linkSuiteCommon.SetUpTest(c)
   362  
   363  	const yaml = `name: hello
   364  version: 1.0
   365  environment:
   366   KEY: value
   367  
   368  apps:
   369   foo:
   370     command: foo
   371   bar:
   372     command: bar
   373   svc:
   374     command: svc
   375     daemon: simple
   376  `
   377  	cmd := testutil.MockCommand(c, "update-desktop-database", "")
   378  	s.AddCleanup(cmd.Restore)
   379  
   380  	s.info = snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   381  
   382  	guiDir := filepath.Join(s.info.MountDir(), "meta", "gui")
   383  	c.Assert(os.MkdirAll(guiDir, 0755), IsNil)
   384  	c.Assert(ioutil.WriteFile(filepath.Join(guiDir, "bin.desktop"), []byte(`
   385  [Desktop Entry]
   386  Name=bin
   387  Icon=${SNAP}/bin.png
   388  Exec=bin
   389  `), 0644), IsNil)
   390  
   391  	// sanity checks
   392  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
   393  		os.MkdirAll(d, 0755)
   394  		l, err := filepath.Glob(filepath.Join(d, "*"))
   395  		c.Assert(err, IsNil, Commentf(d))
   396  		c.Assert(l, HasLen, 0, Commentf(d))
   397  	}
   398  }
   399  
   400  func (s *linkCleanupSuite) testLinkCleanupDirOnFail(c *C, dir string) {
   401  	c.Assert(os.Chmod(dir, 0555), IsNil)
   402  	defer os.Chmod(dir, 0755)
   403  
   404  	_, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings)
   405  	c.Assert(err, NotNil)
   406  	_, isPathError := err.(*os.PathError)
   407  	_, isLinkError := err.(*os.LinkError)
   408  	c.Assert(isPathError || isLinkError, Equals, true, Commentf("%#v", err))
   409  
   410  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
   411  		l, err := filepath.Glob(filepath.Join(d, "*"))
   412  		c.Check(err, IsNil, Commentf(d))
   413  		c.Check(l, HasLen, 0, Commentf(d))
   414  	}
   415  }
   416  
   417  func (s *linkCleanupSuite) TestLinkCleanupOnDesktopFail(c *C) {
   418  	s.testLinkCleanupDirOnFail(c, dirs.SnapDesktopFilesDir)
   419  }
   420  
   421  func (s *linkCleanupSuite) TestLinkCleanupOnBinariesFail(c *C) {
   422  	// this one is the trivial case _as the code stands today_,
   423  	// but nothing guarantees that ordering.
   424  	s.testLinkCleanupDirOnFail(c, dirs.SnapBinariesDir)
   425  }
   426  
   427  func (s *linkCleanupSuite) TestLinkCleanupOnServicesFail(c *C) {
   428  	s.testLinkCleanupDirOnFail(c, dirs.SnapServicesDir)
   429  }
   430  
   431  func (s *linkCleanupSuite) TestLinkCleanupOnMountDirFail(c *C) {
   432  	s.testLinkCleanupDirOnFail(c, filepath.Dir(s.info.MountDir()))
   433  }
   434  
   435  func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) {
   436  	r := systemd.MockSystemctl(func(...string) ([]byte, error) {
   437  		return nil, errors.New("ouchie")
   438  	})
   439  	defer r()
   440  
   441  	_, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings)
   442  	c.Assert(err, ErrorMatches, "ouchie")
   443  
   444  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
   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) TestLinkCleansUpDataDirAndSymlinksOnSymlinkFail(c *C) {
   452  	// sanity check
   453  	c.Assert(s.info.DataDir(), testutil.FileAbsent)
   454  
   455  	// the mountdir symlink is currently the last thing in LinkSnap that can
   456  	// make it fail, creating a symlink requires write permissions
   457  	d := filepath.Dir(s.info.MountDir())
   458  	c.Assert(os.Chmod(d, 0555), IsNil)
   459  	defer os.Chmod(d, 0755)
   460  
   461  	_, err := s.be.LinkSnap(s.info, mockDev, backend.LinkContext{}, s.perfTimings)
   462  	c.Assert(err, ErrorMatches, `(?i).*symlink.*permission denied.*`)
   463  
   464  	c.Check(s.info.DataDir(), testutil.FileAbsent)
   465  	c.Check(filepath.Join(s.info.DataDir(), "..", "current"), testutil.FileAbsent)
   466  	c.Check(filepath.Join(s.info.MountDir(), "..", "current"), testutil.FileAbsent)
   467  }
   468  
   469  func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesClassic(c *C) {
   470  	current := filepath.Join(s.info.MountDir(), "..", "current")
   471  
   472  	for _, dev := range []boot.Device{mockDev, mockClassicDev} {
   473  		var updateFontconfigCaches int
   474  		restore := backend.MockUpdateFontconfigCaches(func() error {
   475  			c.Assert(osutil.FileExists(current), Equals, false)
   476  			updateFontconfigCaches += 1
   477  			return nil
   478  		})
   479  		defer restore()
   480  
   481  		_, err := s.be.LinkSnap(s.info, dev, backend.LinkContext{}, s.perfTimings)
   482  		c.Assert(err, IsNil)
   483  		if dev.Classic() {
   484  			c.Assert(updateFontconfigCaches, Equals, 1)
   485  		} else {
   486  			c.Assert(updateFontconfigCaches, Equals, 0)
   487  		}
   488  		c.Assert(os.Remove(current), IsNil)
   489  	}
   490  }
   491  
   492  func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesCallsFromNewCurrent(c *C) {
   493  	const yaml = `name: core
   494  version: 1.0
   495  type: os
   496  `
   497  	// old version is 'current'
   498  	infoOld := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   499  	mountDirOld := infoOld.MountDir()
   500  	err := os.Symlink(filepath.Base(mountDirOld), filepath.Join(mountDirOld, "..", "current"))
   501  	c.Assert(err, IsNil)
   502  
   503  	oldCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v6"), "")
   504  	oldCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v7"), "")
   505  
   506  	infoNew := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(12)})
   507  	mountDirNew := infoNew.MountDir()
   508  
   509  	newCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v6"), "")
   510  	newCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v7"), "")
   511  
   512  	// provide our own mock, osutil.CommandFromCore expects an ELF binary
   513  	restore := backend.MockCommandFromSystemSnap(func(name string, args ...string) (*exec.Cmd, error) {
   514  		cmd := filepath.Join(dirs.SnapMountDir, "core", "current", name)
   515  		c.Logf("command from core: %v", cmd)
   516  		return exec.Command(cmd, args...), nil
   517  	})
   518  	defer restore()
   519  
   520  	_, err = s.be.LinkSnap(infoNew, mockClassicDev, backend.LinkContext{}, s.perfTimings)
   521  	c.Assert(err, IsNil)
   522  
   523  	c.Check(oldCmdV6.Calls(), HasLen, 0)
   524  	c.Check(oldCmdV7.Calls(), HasLen, 0)
   525  
   526  	c.Check(newCmdV6.Calls(), DeepEquals, [][]string{
   527  		{"fc-cache-v6", "--system-only"},
   528  	})
   529  	c.Check(newCmdV7.Calls(), DeepEquals, [][]string{
   530  		{"fc-cache-v7", "--system-only"},
   531  	})
   532  }
   533  
   534  func (s *linkCleanupSuite) testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c *C, firstInstall bool) {
   535  	dirs.SetRootDir(c.MkDir())
   536  	defer dirs.SetRootDir("")
   537  
   538  	info, _ := mockSnapdSnapForLink(c)
   539  
   540  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   541  	c.Assert(err, IsNil)
   542  	err = os.MkdirAll(dirs.SnapUserServicesDir, 0755)
   543  	c.Assert(err, IsNil)
   544  
   545  	// make snap mount dir non-writable, triggers error updating the current symlink
   546  	snapdSnapDir := filepath.Dir(info.MountDir())
   547  
   548  	if firstInstall {
   549  		err := os.Remove(filepath.Join(snapdSnapDir, "1234"))
   550  		c.Assert(err == nil || os.IsNotExist(err), Equals, true, Commentf("err: %v, err"))
   551  	} else {
   552  		err := os.Mkdir(filepath.Join(snapdSnapDir, "1234"), 0755)
   553  		c.Assert(err, IsNil)
   554  	}
   555  
   556  	// triggers permission denied error when symlink is manipulated
   557  	err = os.Chmod(snapdSnapDir, 0555)
   558  	c.Assert(err, IsNil)
   559  	defer os.Chmod(snapdSnapDir, 0755)
   560  
   561  	linkCtx := backend.LinkContext{
   562  		FirstInstall: firstInstall,
   563  	}
   564  	reboot, err := s.be.LinkSnap(info, mockDev, linkCtx, s.perfTimings)
   565  	c.Assert(err, ErrorMatches, fmt.Sprintf("symlink %s /.*/snapd/current: permission denied", info.Revision))
   566  	c.Assert(reboot, Equals, false)
   567  
   568  	checker := testutil.FilePresent
   569  	if firstInstall {
   570  		checker = testutil.FileAbsent
   571  	}
   572  
   573  	// system services
   574  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), checker)
   575  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.socket"), checker)
   576  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), checker)
   577  	// user services
   578  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), checker)
   579  	c.Check(filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.socket"), checker)
   580  	c.Check(filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), checker)
   581  }
   582  
   583  func (s *linkCleanupSuite) TestLinkCleanupFailedSnapdSnapFirstInstallOnCore(c *C) {
   584  	// test failure mode when snapd is first installed, its units were
   585  	// correctly written and corresponding services were started, but
   586  	// current symlink failed
   587  	restore := release.MockOnClassic(false)
   588  	defer restore()
   589  	s.testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c, true)
   590  }
   591  
   592  func (s *linkCleanupSuite) TestLinkCleanupFailedSnapdSnapNonFirstInstallOnCore(c *C) {
   593  	// test failure mode when a new revision of snapd is installed, its was
   594  	// units were correctly written and corresponding services were started,
   595  	// but current symlink failed
   596  	restore := release.MockOnClassic(false)
   597  	defer restore()
   598  	s.testLinkCleanupFailedSnapdSnapOnCorePastWrappers(c, false)
   599  }
   600  
   601  type snapdOnCoreUnlinkSuite struct {
   602  	linkSuiteCommon
   603  }
   604  
   605  var _ = Suite(&snapdOnCoreUnlinkSuite{})
   606  
   607  func (s *snapdOnCoreUnlinkSuite) TestUndoGeneratedWrappers(c *C) {
   608  	restore := release.MockOnClassic(false)
   609  	defer restore()
   610  	restore = release.MockReleaseInfo(&release.OS{ID: "ubuntu"})
   611  	defer restore()
   612  
   613  	err := os.MkdirAll(dirs.SnapServicesDir, 0755)
   614  	c.Assert(err, IsNil)
   615  	err = os.MkdirAll(dirs.SnapUserServicesDir, 0755)
   616  	c.Assert(err, IsNil)
   617  
   618  	info, snapdUnits := mockSnapdSnapForLink(c)
   619  	// all generated untis
   620  	generatedSnapdUnits := append(snapdUnits,
   621  		[]string{"usr-lib-snapd.mount", "mount unit"})
   622  
   623  	toEtcUnitPath := func(p string) string {
   624  		if strings.HasPrefix(p, "usr/lib/systemd/user") {
   625  			return filepath.Join(dirs.SnapUserServicesDir, filepath.Base(p))
   626  		}
   627  		return filepath.Join(dirs.SnapServicesDir, filepath.Base(p))
   628  	}
   629  
   630  	reboot, err := s.be.LinkSnap(info, mockDev, backend.LinkContext{}, s.perfTimings)
   631  	c.Assert(err, IsNil)
   632  	c.Assert(reboot, Equals, false)
   633  
   634  	// sanity checks
   635  	c.Check(filepath.Join(dirs.SnapServicesDir, "snapd.service"), testutil.FileContains,
   636  		fmt.Sprintf("[Service]\nExecStart=%s/usr/lib/snapd/snapd\n", info.MountDir()))
   637  	// expecting all generated untis to be present
   638  	for _, entry := range generatedSnapdUnits {
   639  		c.Check(toEtcUnitPath(entry[0]), testutil.FilePresent)
   640  	}
   641  
   642  	linkCtx := backend.LinkContext{
   643  		FirstInstall: true,
   644  	}
   645  	err = s.be.UnlinkSnap(info, linkCtx, nil)
   646  	c.Assert(err, IsNil)
   647  
   648  	// generated wrappers should be gone now
   649  	for _, entry := range generatedSnapdUnits {
   650  		c.Check(toEtcUnitPath(entry[0]), testutil.FileAbsent)
   651  	}
   652  
   653  	// unlink is idempotent
   654  	err = s.be.UnlinkSnap(info, linkCtx, nil)
   655  	c.Assert(err, IsNil)
   656  }
   657  
   658  func (s *snapdOnCoreUnlinkSuite) TestUnlinkNonFirstSnapdOnCoreDoesNothing(c *C) {
   659  	restore := release.MockOnClassic(false)
   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, _ := mockSnapdSnapForLink(c)
   668  
   669  	units := [][]string{
   670  		{filepath.Join(dirs.SnapServicesDir, "snapd.service"), "precious"},
   671  		{filepath.Join(dirs.SnapServicesDir, "snapd.socket"), "precious"},
   672  		{filepath.Join(dirs.SnapServicesDir, "snapd.snap-repair.timer"), "precious"},
   673  		{filepath.Join(dirs.SnapServicesDir, "usr-lib-snapd.mount"), "precious"},
   674  		{filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agent.service"), "precious"},
   675  		{filepath.Join(dirs.SnapUserServicesDir, "snapd.session-agentsocket"), "precious"},
   676  	}
   677  	// content list uses absolute paths already
   678  	snaptest.PopulateDir("/", units)
   679  	err = s.be.UnlinkSnap(info, backend.LinkContext{FirstInstall: false}, nil)
   680  	c.Assert(err, IsNil)
   681  	for _, unit := range units {
   682  		c.Check(unit[0], testutil.FileEquals, "precious")
   683  	}
   684  }