github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/osutil"
    33  	"github.com/snapcore/snapd/progress"
    34  	"github.com/snapcore/snapd/release"
    35  	"github.com/snapcore/snapd/snap"
    36  	"github.com/snapcore/snapd/snap/snaptest"
    37  	"github.com/snapcore/snapd/systemd"
    38  	"github.com/snapcore/snapd/testutil"
    39  	"github.com/snapcore/snapd/timings"
    40  
    41  	"github.com/snapcore/snapd/overlord/snapstate/backend"
    42  )
    43  
    44  type linkSuite struct {
    45  	be backend.Backend
    46  
    47  	systemctlRestorer func()
    48  
    49  	perfTimings *timings.Timings
    50  }
    51  
    52  var _ = Suite(&linkSuite{})
    53  
    54  func (s *linkSuite) SetUpTest(c *C) {
    55  	dirs.SetRootDir(c.MkDir())
    56  
    57  	s.perfTimings = timings.New(nil)
    58  	s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
    59  		return []byte("ActiveState=inactive\n"), nil
    60  	})
    61  }
    62  
    63  func (s *linkSuite) TearDownTest(c *C) {
    64  	dirs.SetRootDir("")
    65  	s.systemctlRestorer()
    66  }
    67  
    68  func (s *linkSuite) TestLinkDoUndoGenerateWrappers(c *C) {
    69  	const yaml = `name: hello
    70  version: 1.0
    71  environment:
    72   KEY: value
    73  
    74  apps:
    75   bin:
    76     command: bin
    77   svc:
    78     command: svc
    79     daemon: simple
    80  `
    81  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
    82  
    83  	err := s.be.LinkSnap(info, nil, s.perfTimings)
    84  	c.Assert(err, IsNil)
    85  
    86  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
    87  	c.Assert(err, IsNil)
    88  	c.Assert(l, HasLen, 1)
    89  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
    90  	c.Assert(err, IsNil)
    91  	c.Assert(l, HasLen, 1)
    92  
    93  	// undo will remove
    94  	err = s.be.UnlinkSnap(info, progress.Null)
    95  	c.Assert(err, IsNil)
    96  
    97  	l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
    98  	c.Assert(err, IsNil)
    99  	c.Assert(l, HasLen, 0)
   100  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   101  	c.Assert(err, IsNil)
   102  	c.Assert(l, HasLen, 0)
   103  }
   104  
   105  func (s *linkSuite) TestLinkDoUndoCurrentSymlink(c *C) {
   106  	const yaml = `name: hello
   107  version: 1.0
   108  `
   109  	const contents = ""
   110  
   111  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   112  
   113  	err := s.be.LinkSnap(info, nil, s.perfTimings)
   114  	c.Assert(err, IsNil)
   115  
   116  	mountDir := info.MountDir()
   117  	dataDir := info.DataDir()
   118  	currentActiveSymlink := filepath.Join(mountDir, "..", "current")
   119  	currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink)
   120  	c.Assert(err, IsNil)
   121  	c.Assert(currentActiveDir, Equals, mountDir)
   122  
   123  	currentDataSymlink := filepath.Join(dataDir, "..", "current")
   124  	currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink)
   125  	c.Assert(err, IsNil)
   126  	c.Assert(currentDataDir, Equals, dataDir)
   127  
   128  	// undo will remove the symlinks
   129  	err = s.be.UnlinkSnap(info, progress.Null)
   130  	c.Assert(err, IsNil)
   131  
   132  	c.Check(osutil.FileExists(currentActiveSymlink), Equals, false)
   133  	c.Check(osutil.FileExists(currentDataSymlink), Equals, false)
   134  
   135  }
   136  
   137  func (s *linkSuite) TestLinkDoIdempotent(c *C) {
   138  	// make sure that a retry wouldn't stumble on partial work
   139  
   140  	const yaml = `name: hello
   141  version: 1.0
   142  environment:
   143   KEY: value
   144  apps:
   145   bin:
   146     command: bin
   147   svc:
   148     command: svc
   149     daemon: simple
   150  `
   151  	const contents = ""
   152  
   153  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   154  
   155  	err := s.be.LinkSnap(info, nil, s.perfTimings)
   156  	c.Assert(err, IsNil)
   157  
   158  	err = s.be.LinkSnap(info, nil, s.perfTimings)
   159  	c.Assert(err, IsNil)
   160  
   161  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   162  	c.Assert(err, IsNil)
   163  	c.Assert(l, HasLen, 1)
   164  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   165  	c.Assert(err, IsNil)
   166  	c.Assert(l, HasLen, 1)
   167  
   168  	mountDir := info.MountDir()
   169  	dataDir := info.DataDir()
   170  	currentActiveSymlink := filepath.Join(mountDir, "..", "current")
   171  	currentActiveDir, err := filepath.EvalSymlinks(currentActiveSymlink)
   172  	c.Assert(err, IsNil)
   173  	c.Assert(currentActiveDir, Equals, mountDir)
   174  
   175  	currentDataSymlink := filepath.Join(dataDir, "..", "current")
   176  	currentDataDir, err := filepath.EvalSymlinks(currentDataSymlink)
   177  	c.Assert(err, IsNil)
   178  	c.Assert(currentDataDir, Equals, dataDir)
   179  }
   180  
   181  func (s *linkSuite) TestLinkUndoIdempotent(c *C) {
   182  	// make sure that a retry wouldn't stumble on partial work
   183  
   184  	const yaml = `name: hello
   185  version: 1.0
   186  apps:
   187   bin:
   188     command: bin
   189   svc:
   190     command: svc
   191     daemon: simple
   192  `
   193  	const contents = ""
   194  
   195  	info := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   196  
   197  	err := s.be.LinkSnap(info, nil, s.perfTimings)
   198  	c.Assert(err, IsNil)
   199  
   200  	err = s.be.UnlinkSnap(info, progress.Null)
   201  	c.Assert(err, IsNil)
   202  
   203  	err = s.be.UnlinkSnap(info, progress.Null)
   204  	c.Assert(err, IsNil)
   205  
   206  	// no wrappers
   207  	l, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
   208  	c.Assert(err, IsNil)
   209  	c.Assert(l, HasLen, 0)
   210  	l, err = filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.service"))
   211  	c.Assert(err, IsNil)
   212  	c.Assert(l, HasLen, 0)
   213  
   214  	// no symlinks
   215  	currentActiveSymlink := filepath.Join(info.MountDir(), "..", "current")
   216  	currentDataSymlink := filepath.Join(info.DataDir(), "..", "current")
   217  	c.Check(osutil.FileExists(currentActiveSymlink), Equals, false)
   218  	c.Check(osutil.FileExists(currentDataSymlink), Equals, false)
   219  }
   220  
   221  func (s *linkSuite) TestLinkFailsForUnsetRevision(c *C) {
   222  	info := &snap.Info{
   223  		SuggestedName: "foo",
   224  	}
   225  	err := s.be.LinkSnap(info, nil, s.perfTimings)
   226  	c.Assert(err, ErrorMatches, `cannot link snap "foo" with unset revision`)
   227  }
   228  
   229  type linkCleanupSuite struct {
   230  	linkSuite
   231  	info *snap.Info
   232  }
   233  
   234  var _ = Suite(&linkCleanupSuite{})
   235  
   236  func (s *linkCleanupSuite) SetUpTest(c *C) {
   237  	s.linkSuite.SetUpTest(c)
   238  
   239  	const yaml = `name: hello
   240  version: 1.0
   241  environment:
   242   KEY: value
   243  
   244  apps:
   245   foo:
   246     command: foo
   247   bar:
   248     command: bar
   249   svc:
   250     command: svc
   251     daemon: simple
   252  `
   253  	s.info = snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   254  
   255  	guiDir := filepath.Join(s.info.MountDir(), "meta", "gui")
   256  	c.Assert(os.MkdirAll(guiDir, 0755), IsNil)
   257  	c.Assert(ioutil.WriteFile(filepath.Join(guiDir, "bin.desktop"), []byte(`
   258  [Desktop Entry]
   259  Name=bin
   260  Icon=${SNAP}/bin.png
   261  Exec=bin
   262  `), 0644), IsNil)
   263  
   264  	r := systemd.MockSystemctl(func(...string) ([]byte, error) {
   265  		return nil, nil
   266  	})
   267  	defer r()
   268  
   269  	// sanity checks
   270  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
   271  		os.MkdirAll(d, 0755)
   272  		l, err := filepath.Glob(filepath.Join(d, "*"))
   273  		c.Assert(err, IsNil, Commentf(d))
   274  		c.Assert(l, HasLen, 0, Commentf(d))
   275  	}
   276  }
   277  
   278  func (s *linkCleanupSuite) testLinkCleanupDirOnFail(c *C, dir string) {
   279  	c.Assert(os.Chmod(dir, 0), IsNil)
   280  	defer os.Chmod(dir, 0755)
   281  
   282  	err := s.be.LinkSnap(s.info, nil, s.perfTimings)
   283  	c.Assert(err, NotNil)
   284  	_, isPathError := err.(*os.PathError)
   285  	_, isLinkError := err.(*os.LinkError)
   286  	c.Assert(isPathError || isLinkError, Equals, true, Commentf("%T", err))
   287  
   288  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
   289  		l, err := filepath.Glob(filepath.Join(d, "*"))
   290  		c.Check(err, IsNil, Commentf(d))
   291  		c.Check(l, HasLen, 0, Commentf(d))
   292  	}
   293  }
   294  
   295  func (s *linkCleanupSuite) TestLinkCleanupOnDesktopFail(c *C) {
   296  	s.testLinkCleanupDirOnFail(c, dirs.SnapDesktopFilesDir)
   297  }
   298  
   299  func (s *linkCleanupSuite) TestLinkCleanupOnBinariesFail(c *C) {
   300  	// this one is the trivial case _as the code stands today_,
   301  	// but nothing guarantees that ordering.
   302  	s.testLinkCleanupDirOnFail(c, dirs.SnapBinariesDir)
   303  }
   304  
   305  func (s *linkCleanupSuite) TestLinkCleanupOnServicesFail(c *C) {
   306  	s.testLinkCleanupDirOnFail(c, dirs.SnapServicesDir)
   307  }
   308  
   309  func (s *linkCleanupSuite) TestLinkCleanupOnMountDirFail(c *C) {
   310  	s.testLinkCleanupDirOnFail(c, filepath.Dir(s.info.MountDir()))
   311  }
   312  
   313  func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) {
   314  	r := systemd.MockSystemctl(func(...string) ([]byte, error) {
   315  		return nil, errors.New("ouchie")
   316  	})
   317  	defer r()
   318  
   319  	err := s.be.LinkSnap(s.info, nil, s.perfTimings)
   320  	c.Assert(err, ErrorMatches, "ouchie")
   321  
   322  	for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
   323  		l, err := filepath.Glob(filepath.Join(d, "*"))
   324  		c.Check(err, IsNil, Commentf(d))
   325  		c.Check(l, HasLen, 0, Commentf(d))
   326  	}
   327  }
   328  
   329  func (s *linkCleanupSuite) TestLinkCleansUpDataDirAndSymlinksOnSymlinkFail(c *C) {
   330  	// sanity check
   331  	c.Assert(s.info.DataDir(), testutil.FileAbsent)
   332  
   333  	// the mountdir symlink is currently the last thing in
   334  	// LinkSnap that can make it fail
   335  	d := filepath.Dir(s.info.MountDir())
   336  	c.Assert(os.Chmod(d, 0), IsNil)
   337  	defer os.Chmod(d, 0755)
   338  
   339  	err := s.be.LinkSnap(s.info, nil, s.perfTimings)
   340  	c.Assert(err, ErrorMatches, `(?i).*symlink.*permission denied.*`)
   341  
   342  	c.Check(s.info.DataDir(), testutil.FileAbsent)
   343  	c.Check(filepath.Join(s.info.DataDir(), "..", "current"), testutil.FileAbsent)
   344  	c.Check(filepath.Join(s.info.MountDir(), "..", "current"), testutil.FileAbsent)
   345  }
   346  
   347  func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesClassic(c *C) {
   348  	current := filepath.Join(s.info.MountDir(), "..", "current")
   349  
   350  	for _, onClassic := range []bool{false, true} {
   351  		restore := release.MockOnClassic(onClassic)
   352  		defer restore()
   353  
   354  		var updateFontconfigCaches int
   355  		restore = backend.MockUpdateFontconfigCaches(func() error {
   356  			c.Assert(osutil.FileExists(current), Equals, false)
   357  			updateFontconfigCaches += 1
   358  			return nil
   359  		})
   360  		defer restore()
   361  
   362  		err := s.be.LinkSnap(s.info, nil, s.perfTimings)
   363  		c.Assert(err, IsNil)
   364  		if onClassic {
   365  			c.Assert(updateFontconfigCaches, Equals, 1)
   366  		} else {
   367  			c.Assert(updateFontconfigCaches, Equals, 0)
   368  		}
   369  		c.Assert(os.Remove(current), IsNil)
   370  	}
   371  }
   372  
   373  func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesCallsFromNewCurrent(c *C) {
   374  	restore := release.MockOnClassic(true)
   375  	defer restore()
   376  
   377  	const yaml = `name: core
   378  version: 1.0
   379  type: os
   380  `
   381  	// old version is 'current'
   382  	infoOld := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)})
   383  	mountDirOld := infoOld.MountDir()
   384  	err := os.Symlink(filepath.Base(mountDirOld), filepath.Join(mountDirOld, "..", "current"))
   385  	c.Assert(err, IsNil)
   386  
   387  	err = os.MkdirAll(filepath.Join(mountDirOld, "bin"), 0755)
   388  	c.Assert(err, IsNil)
   389  
   390  	oldCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v6"), "")
   391  	oldCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v7"), "")
   392  
   393  	infoNew := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(12)})
   394  	mountDirNew := infoNew.MountDir()
   395  
   396  	err = os.MkdirAll(filepath.Join(mountDirNew, "bin"), 0755)
   397  	c.Assert(err, IsNil)
   398  
   399  	newCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v6"), "")
   400  	newCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v7"), "")
   401  
   402  	// provide our own mock, osutil.CommandFromCore expects an ELF binary
   403  	restore = backend.MockCommandFromSystemSnap(func(name string, args ...string) (*exec.Cmd, error) {
   404  		cmd := filepath.Join(dirs.SnapMountDir, "core", "current", name)
   405  		c.Logf("command from core: %v", cmd)
   406  		return exec.Command(cmd, args...), nil
   407  	})
   408  	defer restore()
   409  
   410  	err = s.be.LinkSnap(infoNew, nil, s.perfTimings)
   411  	c.Assert(err, IsNil)
   412  
   413  	c.Check(oldCmdV6.Calls(), HasLen, 0)
   414  	c.Check(oldCmdV7.Calls(), HasLen, 0)
   415  
   416  	c.Check(newCmdV6.Calls(), HasLen, 1)
   417  	c.Check(newCmdV7.Calls(), HasLen, 1)
   418  }