github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/cmd/snap-preseed/main_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 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 main_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/jessevdk/go-flags"
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/asserts/assertstest"
    35  	"github.com/snapcore/snapd/cmd/snap-preseed"
    36  	"github.com/snapcore/snapd/cmd/snaplock/runinhibit"
    37  	"github.com/snapcore/snapd/dirs"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/osutil/squashfs"
    40  	apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor"
    41  	"github.com/snapcore/snapd/seed"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/testutil"
    44  	"github.com/snapcore/snapd/timings"
    45  )
    46  
    47  func Test(t *testing.T) { TestingT(t) }
    48  
    49  var _ = Suite(&startPreseedSuite{})
    50  
    51  type startPreseedSuite struct {
    52  	testutil.BaseTest
    53  }
    54  
    55  func (s *startPreseedSuite) SetUpTest(c *C) {
    56  	s.BaseTest.SetUpTest(c)
    57  	restore := squashfs.MockNeedsFuse(false)
    58  	s.BaseTest.AddCleanup(restore)
    59  }
    60  
    61  func (s *startPreseedSuite) TearDownTest(c *C) {
    62  	s.BaseTest.TearDownTest(c)
    63  	dirs.SetRootDir("")
    64  }
    65  
    66  func testParser(c *C) *flags.Parser {
    67  	parser := main.Parser()
    68  	_, err := parser.ParseArgs([]string{})
    69  	c.Assert(err, IsNil)
    70  	return parser
    71  }
    72  
    73  func mockVersionFiles(c *C, rootDir1, version1, rootDir2, version2 string) {
    74  	versions := []string{version1, version2}
    75  	for i, root := range []string{rootDir1, rootDir2} {
    76  		c.Assert(os.MkdirAll(filepath.Join(root, dirs.CoreLibExecDir), 0755), IsNil)
    77  		infoFile := filepath.Join(root, dirs.CoreLibExecDir, "info")
    78  		c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", versions[i])), 0644), IsNil)
    79  	}
    80  }
    81  
    82  func mockChrootDirs(c *C, rootDir string, apparmorDir bool) func() {
    83  	if apparmorDir {
    84  		c.Assert(os.MkdirAll(filepath.Join(rootDir, "/sys/kernel/security/apparmor"), 0755), IsNil)
    85  	}
    86  	mockMountInfo := `912 920 0:57 / ${rootDir}/proc rw,nosuid,nodev,noexec,relatime - proc proc rw
    87  914 913 0:7 / ${rootDir}/sys/kernel/security rw,nosuid,nodev,noexec,relatime master:8 - securityfs securityfs rw
    88  915 920 0:58 / ${rootDir}/dev rw,relatime - tmpfs none rw,size=492k,mode=755,uid=100000,gid=100000
    89  `
    90  	return osutil.MockMountInfo(strings.Replace(mockMountInfo, "${rootDir}", rootDir, -1))
    91  }
    92  
    93  func (s *startPreseedSuite) TestRequiresRoot(c *C) {
    94  	restore := main.MockOsGetuid(func() int {
    95  		return 1000
    96  	})
    97  	defer restore()
    98  
    99  	parser := testParser(c)
   100  	c.Check(main.Run(parser, []string{"/"}), ErrorMatches, `must be run as root`)
   101  }
   102  
   103  func (s *startPreseedSuite) TestMissingArg(c *C) {
   104  	restore := main.MockOsGetuid(func() int {
   105  		return 0
   106  	})
   107  	defer restore()
   108  
   109  	parser := testParser(c)
   110  	c.Check(main.Run(parser, nil), ErrorMatches, `need chroot path as argument`)
   111  }
   112  
   113  func (s *startPreseedSuite) TestChrootDoesntExist(c *C) {
   114  	restore := main.MockOsGetuid(func() int { return 0 })
   115  	defer restore()
   116  
   117  	parser := testParser(c)
   118  	c.Check(main.Run(parser, []string{"/non-existing-dir"}), ErrorMatches, `cannot verify "/non-existing-dir": is not a directory`)
   119  }
   120  
   121  func (s *startPreseedSuite) TestChrootValidationUnhappy(c *C) {
   122  	restore := main.MockOsGetuid(func() int { return 0 })
   123  	defer restore()
   124  
   125  	tmpDir := c.MkDir()
   126  	defer osutil.MockMountInfo("")()
   127  
   128  	parser := testParser(c)
   129  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, "cannot preseed without the following mountpoints:\n - .*/dev\n - .*/proc\n - .*/sys/kernel/security")
   130  }
   131  
   132  func (s *startPreseedSuite) TestRunPreseedMountUnhappy(c *C) {
   133  	tmpDir := c.MkDir()
   134  	dirs.SetRootDir(tmpDir)
   135  	defer mockChrootDirs(c, tmpDir, true)()
   136  
   137  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   138  	defer restoreOsGuid()
   139  
   140  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
   141  	defer restoreSyscallChroot()
   142  
   143  	mockMountCmd := testutil.MockCommand(c, "mount", `echo "something went wrong"
   144  exit 32
   145  `)
   146  	defer mockMountCmd.Restore()
   147  
   148  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   149  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   150  	defer restoreMountPath()
   151  
   152  	restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
   153  	defer restoreSystemSnapFromSeed()
   154  
   155  	parser := testParser(c)
   156  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, `cannot mount .+ at .+ in preseed mode: exit status 32\n'mount -t squashfs -o ro,x-gdu.hide,x-gvfs-hide /a/core.snap .*/target-core-mounted-here' failed with: something went wrong\n`)
   157  }
   158  
   159  func (s *startPreseedSuite) TestChrootValidationUnhappyNoApparmor(c *C) {
   160  	restore := main.MockOsGetuid(func() int { return 0 })
   161  	defer restore()
   162  
   163  	tmpDir := c.MkDir()
   164  	defer mockChrootDirs(c, tmpDir, false)()
   165  
   166  	parser := testParser(c)
   167  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, `cannot preseed without access to ".*sys/kernel/security/apparmor"`)
   168  }
   169  
   170  func (s *startPreseedSuite) TestChrootValidationAlreadyPreseeded(c *C) {
   171  	restore := main.MockOsGetuid(func() int { return 0 })
   172  	defer restore()
   173  
   174  	tmpDir := c.MkDir()
   175  	snapdDir := filepath.Dir(dirs.SnapStateFile)
   176  	c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil)
   177  	c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil)
   178  
   179  	parser := testParser(c)
   180  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, fmt.Sprintf("the system at %q appears to be preseeded, pass --reset flag to clean it up", tmpDir))
   181  }
   182  
   183  func (s *startPreseedSuite) TestChrootFailure(c *C) {
   184  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   185  	defer restoreOsGuid()
   186  
   187  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error {
   188  		return fmt.Errorf("FAIL: %s", path)
   189  	})
   190  	defer restoreSyscallChroot()
   191  
   192  	tmpDir := c.MkDir()
   193  	defer mockChrootDirs(c, tmpDir, true)()
   194  
   195  	parser := testParser(c)
   196  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, fmt.Sprintf("cannot chroot into %s: FAIL: %s", tmpDir, tmpDir))
   197  }
   198  
   199  func (s *startPreseedSuite) TestRunPreseedHappy(c *C) {
   200  	tmpDir := c.MkDir()
   201  	dirs.SetRootDir(tmpDir)
   202  	defer mockChrootDirs(c, tmpDir, true)()
   203  
   204  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   205  	defer restoreOsGuid()
   206  
   207  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
   208  	defer restoreSyscallChroot()
   209  
   210  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   211  	defer mockMountCmd.Restore()
   212  
   213  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   214  	defer mockUmountCmd.Restore()
   215  
   216  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   217  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   218  	defer restoreMountPath()
   219  
   220  	restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
   221  	defer restoreSystemSnapFromSeed()
   222  
   223  	mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), `#!/bin/sh
   224  	if [ "$SNAPD_PRESEED" != "1" ]; then
   225  		exit 1
   226  	fi
   227  `)
   228  	defer mockTargetSnapd.Restore()
   229  
   230  	mockSnapdFromDeb := testutil.MockCommand(c, filepath.Join(tmpDir, "usr/lib/snapd/snapd"), `#!/bin/sh
   231  	exit 1
   232  `)
   233  	defer mockSnapdFromDeb.Restore()
   234  
   235  	// snapd from the snap is newer than deb
   236  	mockVersionFiles(c, targetSnapdRoot, "2.44.0", tmpDir, "2.41.0")
   237  
   238  	parser := testParser(c)
   239  	c.Check(main.Run(parser, []string{tmpDir}), IsNil)
   240  
   241  	c.Assert(mockMountCmd.Calls(), HasLen, 1)
   242  	// note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test
   243  	// and mocking dirs.RootDir
   244  	c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "-o", "ro,x-gdu.hide,x-gvfs-hide", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)})
   245  
   246  	c.Assert(mockTargetSnapd.Calls(), HasLen, 1)
   247  	c.Check(mockTargetSnapd.Calls()[0], DeepEquals, []string{"snapd"})
   248  
   249  	c.Assert(mockSnapdFromDeb.Calls(), HasLen, 0)
   250  
   251  	// relative chroot path works too
   252  	tmpDirPath, relativeChroot := filepath.Split(tmpDir)
   253  	pwd, err := os.Getwd()
   254  	c.Assert(err, IsNil)
   255  	defer func() {
   256  		os.Chdir(pwd)
   257  	}()
   258  	c.Assert(os.Chdir(tmpDirPath), IsNil)
   259  	c.Check(main.Run(parser, []string{relativeChroot}), IsNil)
   260  }
   261  
   262  func (s *startPreseedSuite) TestRunPreseedHappyDebVersionIsNewer(c *C) {
   263  	tmpDir := c.MkDir()
   264  	dirs.SetRootDir(tmpDir)
   265  	defer mockChrootDirs(c, tmpDir, true)()
   266  
   267  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   268  	defer restoreOsGuid()
   269  
   270  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
   271  	defer restoreSyscallChroot()
   272  
   273  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   274  	defer mockMountCmd.Restore()
   275  
   276  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   277  	defer mockUmountCmd.Restore()
   278  
   279  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   280  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   281  	defer restoreMountPath()
   282  
   283  	restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
   284  	defer restoreSystemSnapFromSeed()
   285  
   286  	c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
   287  	mockSnapdFromSnap := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), `#!/bin/sh
   288  	exit 1
   289  `)
   290  	defer mockSnapdFromSnap.Restore()
   291  
   292  	mockSnapdFromDeb := testutil.MockCommand(c, filepath.Join(tmpDir, "usr/lib/snapd/snapd"), `#!/bin/sh
   293  	if [ "$SNAPD_PRESEED" != "1" ]; then
   294  		exit 1
   295  	fi
   296  `)
   297  	defer mockSnapdFromDeb.Restore()
   298  
   299  	// snapd from the deb is newer than snap
   300  	mockVersionFiles(c, targetSnapdRoot, "2.44.0", tmpDir, "2.45.0")
   301  
   302  	parser := testParser(c)
   303  	c.Check(main.Run(parser, []string{tmpDir}), IsNil)
   304  
   305  	c.Assert(mockMountCmd.Calls(), HasLen, 1)
   306  	// note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test
   307  	// and mocking dirs.RootDir
   308  	c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "-o", "ro,x-gdu.hide,x-gvfs-hide", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)})
   309  
   310  	c.Assert(mockSnapdFromDeb.Calls(), HasLen, 1)
   311  	c.Check(mockSnapdFromDeb.Calls()[0], DeepEquals, []string{"snapd"})
   312  	c.Assert(mockSnapdFromSnap.Calls(), HasLen, 0)
   313  }
   314  
   315  type Fake16Seed struct {
   316  	AssertsModel      *asserts.Model
   317  	Essential         []*seed.Snap
   318  	LoadMetaErr       error
   319  	LoadAssertionsErr error
   320  	UsesSnapd         bool
   321  }
   322  
   323  // Fake implementation of seed.Seed interface
   324  
   325  func mockClassicModel() *asserts.Model {
   326  	headers := map[string]interface{}{
   327  		"type":         "model",
   328  		"authority-id": "brand",
   329  		"series":       "16",
   330  		"brand-id":     "brand",
   331  		"model":        "classicbaz-3000",
   332  		"classic":      "true",
   333  		"timestamp":    "2018-01-01T08:00:00+00:00",
   334  	}
   335  	return assertstest.FakeAssertion(headers, nil).(*asserts.Model)
   336  }
   337  
   338  func (fs *Fake16Seed) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error {
   339  	return fs.LoadAssertionsErr
   340  }
   341  
   342  func (fs *Fake16Seed) Model() *asserts.Model {
   343  	return fs.AssertsModel
   344  }
   345  
   346  func (fs *Fake16Seed) Brand() (*asserts.Account, error) {
   347  	headers := map[string]interface{}{
   348  		"type":         "account",
   349  		"account-id":   "brand",
   350  		"display-name": "fake brand",
   351  		"username":     "brand",
   352  		"timestamp":    "2018-01-01T08:00:00+00:00",
   353  	}
   354  	return assertstest.FakeAssertion(headers, nil).(*asserts.Account), nil
   355  }
   356  
   357  func (fs *Fake16Seed) LoadEssentialMeta(essentialTypes []snap.Type, tm timings.Measurer) error {
   358  	panic("unexpected")
   359  }
   360  
   361  func (fs *Fake16Seed) LoadMeta(tm timings.Measurer) error {
   362  	return fs.LoadMetaErr
   363  }
   364  
   365  func (fs *Fake16Seed) UsesSnapdSnap() bool {
   366  	return fs.UsesSnapd
   367  }
   368  
   369  func (fs *Fake16Seed) EssentialSnaps() []*seed.Snap {
   370  	return fs.Essential
   371  }
   372  
   373  func (fs *Fake16Seed) ModeSnaps(mode string) ([]*seed.Snap, error) {
   374  	return nil, nil
   375  }
   376  
   377  func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) {
   378  	tmpDir := c.MkDir()
   379  
   380  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
   381  		return &Fake16Seed{
   382  			AssertsModel: mockClassicModel(),
   383  			Essential:    []*seed.Snap{{Path: "/some/path/core", SideInfo: &snap.SideInfo{RealName: "core"}}},
   384  		}, nil
   385  	})
   386  	defer restore()
   387  
   388  	path, err := main.SystemSnapFromSeed(tmpDir)
   389  	c.Assert(err, IsNil)
   390  	c.Check(path, Equals, "/some/path/core")
   391  }
   392  
   393  func (s *startPreseedSuite) TestSystemSnapFromSnapdSeed(c *C) {
   394  	tmpDir := c.MkDir()
   395  
   396  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
   397  		return &Fake16Seed{
   398  			AssertsModel: mockClassicModel(),
   399  			Essential:    []*seed.Snap{{Path: "/some/path/snapd.snap", SideInfo: &snap.SideInfo{RealName: "snapd"}}},
   400  			UsesSnapd:    true,
   401  		}, nil
   402  	})
   403  	defer restore()
   404  
   405  	path, err := main.SystemSnapFromSeed(tmpDir)
   406  	c.Assert(err, IsNil)
   407  	c.Check(path, Equals, "/some/path/snapd.snap")
   408  }
   409  
   410  func (s *startPreseedSuite) TestSystemSnapFromSeedOpenError(c *C) {
   411  	tmpDir := c.MkDir()
   412  
   413  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return nil, fmt.Errorf("fail") })
   414  	defer restore()
   415  
   416  	_, err := main.SystemSnapFromSeed(tmpDir)
   417  	c.Assert(err, ErrorMatches, "fail")
   418  }
   419  
   420  func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) {
   421  	tmpDir := c.MkDir()
   422  
   423  	fakeSeed := &Fake16Seed{}
   424  	fakeSeed.AssertsModel = mockClassicModel()
   425  
   426  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
   427  	defer restore()
   428  
   429  	fakeSeed.Essential = []*seed.Snap{{Path: "", SideInfo: &snap.SideInfo{RealName: "core"}}}
   430  	_, err := main.SystemSnapFromSeed(tmpDir)
   431  	c.Assert(err, ErrorMatches, "core snap not found")
   432  
   433  	fakeSeed.Essential = []*seed.Snap{{Path: "/some/path", SideInfo: &snap.SideInfo{RealName: "foosnap"}}}
   434  	_, err = main.SystemSnapFromSeed(tmpDir)
   435  	c.Assert(err, ErrorMatches, "core snap not found")
   436  
   437  	fakeSeed.LoadMetaErr = fmt.Errorf("load meta failed")
   438  	_, err = main.SystemSnapFromSeed(tmpDir)
   439  	c.Assert(err, ErrorMatches, "load meta failed")
   440  
   441  	fakeSeed.LoadMetaErr = nil
   442  	fakeSeed.LoadAssertionsErr = fmt.Errorf("load assertions failed")
   443  	_, err = main.SystemSnapFromSeed(tmpDir)
   444  	c.Assert(err, ErrorMatches, "load assertions failed")
   445  }
   446  
   447  func (s *startPreseedSuite) TestClassicRequired(c *C) {
   448  	tmpDir := c.MkDir()
   449  
   450  	headers := map[string]interface{}{
   451  		"type":         "model",
   452  		"authority-id": "brand",
   453  		"series":       "16",
   454  		"brand-id":     "brand",
   455  		"model":        "baz-3000",
   456  		"architecture": "armhf",
   457  		"gadget":       "brand-gadget",
   458  		"kernel":       "kernel",
   459  		"timestamp":    "2018-01-01T08:00:00+00:00",
   460  	}
   461  
   462  	fakeSeed := &Fake16Seed{}
   463  	fakeSeed.AssertsModel = assertstest.FakeAssertion(headers, nil).(*asserts.Model)
   464  
   465  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
   466  	defer restore()
   467  
   468  	_, err := main.SystemSnapFromSeed(tmpDir)
   469  	c.Assert(err, ErrorMatches, "preseeding is only supported on classic systems")
   470  }
   471  
   472  func (s *startPreseedSuite) TestRunPreseedUnsupportedVersion(c *C) {
   473  	tmpDir := c.MkDir()
   474  	dirs.SetRootDir(tmpDir)
   475  	c.Assert(os.MkdirAll(filepath.Join(tmpDir, "usr/lib/snapd/"), 0755), IsNil)
   476  	defer mockChrootDirs(c, tmpDir, true)()
   477  
   478  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   479  	defer restoreOsGuid()
   480  
   481  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
   482  	defer restoreSyscallChroot()
   483  
   484  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   485  	defer mockMountCmd.Restore()
   486  
   487  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   488  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   489  	defer restoreMountPath()
   490  
   491  	restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
   492  	defer restoreSystemSnapFromSeed()
   493  
   494  	c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
   495  	mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), "")
   496  	defer mockTargetSnapd.Restore()
   497  
   498  	infoFile := filepath.Join(targetSnapdRoot, dirs.CoreLibExecDir, "info")
   499  	c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.43.0"), 0644), IsNil)
   500  
   501  	// simulate snapd version from the deb
   502  	infoFile = filepath.Join(filepath.Join(tmpDir, dirs.CoreLibExecDir, "info"))
   503  	c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.41.0"), 0644), IsNil)
   504  
   505  	parser := testParser(c)
   506  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches,
   507  		`snapd 2.43.0 from the target system does not support preseeding, the minimum required version is 2.43.3\+`)
   508  }
   509  
   510  func (s *startPreseedSuite) TestChooseTargetSnapdVersion(c *C) {
   511  	tmpDir := c.MkDir()
   512  	dirs.SetRootDir(tmpDir)
   513  	c.Assert(os.MkdirAll(filepath.Join(tmpDir, "usr/lib/snapd/"), 0755), IsNil)
   514  
   515  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   516  	c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
   517  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   518  	defer restoreMountPath()
   519  
   520  	var versions = []struct {
   521  		fromSnap        string
   522  		fromDeb         string
   523  		expectedPath    string
   524  		expectedVersion string
   525  		expectedErr     string
   526  	}{
   527  		{
   528  			fromDeb:  "2.44.0",
   529  			fromSnap: "2.45.3+git123",
   530  			// snap version wins
   531  			expectedVersion: "2.45.3+git123",
   532  			expectedPath:    filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/snapd"),
   533  		},
   534  		{
   535  			fromDeb:  "2.44.0",
   536  			fromSnap: "2.44.0",
   537  			// snap version wins
   538  			expectedVersion: "2.44.0",
   539  			expectedPath:    filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/snapd"),
   540  		},
   541  		{
   542  			fromDeb:  "2.45.1+20.04",
   543  			fromSnap: "2.45.1",
   544  			// deb version wins
   545  			expectedVersion: "2.45.1+20.04",
   546  			expectedPath:    filepath.Join(tmpDir, "usr/lib/snapd/snapd"),
   547  		},
   548  		{
   549  			fromDeb:  "2.45.2",
   550  			fromSnap: "2.45.1",
   551  			// deb version wins
   552  			expectedVersion: "2.45.2",
   553  			expectedPath:    filepath.Join(tmpDir, "usr/lib/snapd/snapd"),
   554  		},
   555  		{
   556  			fromSnap:    "2.45.1",
   557  			expectedErr: fmt.Sprintf("cannot open snapd info file %q.*", filepath.Join(tmpDir, "usr/lib/snapd/info")),
   558  		},
   559  		{
   560  			fromDeb:     "2.45.1",
   561  			expectedErr: fmt.Sprintf("cannot open snapd info file %q.*", filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/info")),
   562  		},
   563  	}
   564  
   565  	for _, test := range versions {
   566  		infoFile := filepath.Join(tmpDir, "usr/lib/snapd/info")
   567  		os.Remove(infoFile)
   568  		if test.fromDeb != "" {
   569  			c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", test.fromDeb)), 0644), IsNil)
   570  		}
   571  		infoFile = filepath.Join(targetSnapdRoot, "usr/lib/snapd/info")
   572  		os.Remove(infoFile)
   573  		if test.fromSnap != "" {
   574  			c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", test.fromSnap)), 0644), IsNil)
   575  		}
   576  
   577  		targetSnapd, err := main.ChooseTargetSnapdVersion()
   578  		if test.expectedErr != "" {
   579  			c.Assert(err, ErrorMatches, test.expectedErr)
   580  		} else {
   581  			c.Assert(err, IsNil)
   582  			c.Assert(targetSnapd, NotNil)
   583  			path, version := main.SnapdPathAndVersion(targetSnapd)
   584  			c.Check(path, Equals, test.expectedPath)
   585  			c.Check(version, Equals, test.expectedVersion)
   586  		}
   587  	}
   588  }
   589  
   590  func (s *startPreseedSuite) TestRunPreseedAgainstFilesystemRoot(c *C) {
   591  	restore := main.MockOsGetuid(func() int { return 0 })
   592  	defer restore()
   593  
   594  	parser := testParser(c)
   595  	c.Assert(main.Run(parser, []string{"/"}), ErrorMatches, `cannot run snap-preseed against /`)
   596  }
   597  
   598  func (s *startPreseedSuite) TestReset(c *C) {
   599  	restore := main.MockOsGetuid(func() int { return 0 })
   600  	defer restore()
   601  
   602  	startDir, err := os.Getwd()
   603  	c.Assert(err, IsNil)
   604  	defer func() {
   605  		os.Chdir(startDir)
   606  	}()
   607  
   608  	for _, isRelative := range []bool{false, true} {
   609  		tmpDir := c.MkDir()
   610  		resetDirArg := tmpDir
   611  		if isRelative {
   612  			var parentDir string
   613  			parentDir, resetDirArg = filepath.Split(tmpDir)
   614  			os.Chdir(parentDir)
   615  		}
   616  
   617  		// mock some preseeding artifacts
   618  		artifacts := []struct {
   619  			path string
   620  			// if symlinkTarget is not empty, then a path -> symlinkTarget symlink
   621  			// will be created instead of a regular file.
   622  			symlinkTarget string
   623  		}{
   624  			{dirs.SnapStateFile, ""},
   625  			{dirs.SnapSystemKeyFile, ""},
   626  			{filepath.Join(dirs.SnapDesktopFilesDir, "foo.desktop"), ""},
   627  			{filepath.Join(dirs.SnapDesktopIconsDir, "foo.png"), ""},
   628  			{filepath.Join(dirs.SnapMountPolicyDir, "foo.fstab"), ""},
   629  			{filepath.Join(dirs.SnapBlobDir, "foo.snap"), ""},
   630  			{filepath.Join(dirs.SnapUdevRulesDir, "foo-snap.bar.rules"), ""},
   631  			{filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.foo.bar.conf"), ""},
   632  			{filepath.Join(dirs.SnapDBusSessionServicesDir, "org.example.Session.service"), ""},
   633  			{filepath.Join(dirs.SnapDBusSystemServicesDir, "org.example.System.service"), ""},
   634  			{filepath.Join(dirs.SnapServicesDir, "snap.foo.service"), ""},
   635  			{filepath.Join(dirs.SnapServicesDir, "snap.foo.timer"), ""},
   636  			{filepath.Join(dirs.SnapServicesDir, "snap.foo.socket"), ""},
   637  			{filepath.Join(dirs.SnapServicesDir, "snap-foo.mount"), ""},
   638  			{filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", "snap-foo.mount"), ""},
   639  			{filepath.Join(dirs.SnapDataDir, "foo", "bar"), ""},
   640  			{filepath.Join(dirs.SnapCacheDir, "foocache", "bar"), ""},
   641  			{filepath.Join(apparmor_sandbox.CacheDir, "foo", "bar"), ""},
   642  			{filepath.Join(dirs.SnapAppArmorDir, "foo"), ""},
   643  			{filepath.Join(dirs.SnapAssertsDBDir, "foo"), ""},
   644  			{filepath.Join(dirs.FeaturesDir, "foo"), ""},
   645  			{filepath.Join(dirs.SnapDeviceDir, "foo-1", "bar"), ""},
   646  			{filepath.Join(dirs.SnapCookieDir, "foo"), ""},
   647  			{filepath.Join(dirs.SnapSeqDir, "foo.json"), ""},
   648  			{filepath.Join(dirs.SnapMountDir, "foo", "bin"), ""},
   649  			{filepath.Join(dirs.SnapSeccompDir, "foo.bin"), ""},
   650  			{filepath.Join(runinhibit.InhibitDir, "foo.lock"), ""},
   651  			// bash-completion symlinks
   652  			{filepath.Join(dirs.CompletersDir, "foo.bar"), "/a/snapd/complete.sh"},
   653  			{filepath.Join(dirs.CompletersDir, "foo"), "foo.bar"},
   654  		}
   655  
   656  		for _, art := range artifacts {
   657  			fullPath := filepath.Join(tmpDir, art.path)
   658  			// create parent dir
   659  			c.Assert(os.MkdirAll(filepath.Dir(fullPath), 0755), IsNil)
   660  			if art.symlinkTarget != "" {
   661  				// note, symlinkTarget is not relative to tmpDir
   662  				c.Assert(os.Symlink(art.symlinkTarget, fullPath), IsNil)
   663  			} else {
   664  				c.Assert(ioutil.WriteFile(fullPath, nil, os.ModePerm), IsNil)
   665  			}
   666  		}
   667  
   668  		checkArtifacts := func(exists bool) {
   669  			for _, art := range artifacts {
   670  				fullPath := filepath.Join(tmpDir, art.path)
   671  				if art.symlinkTarget != "" {
   672  					c.Check(osutil.IsSymlink(fullPath), Equals, exists, Commentf("offending symlink: %s", fullPath))
   673  				} else {
   674  					c.Check(osutil.FileExists(fullPath), Equals, exists, Commentf("offending file: %s", fullPath))
   675  				}
   676  			}
   677  		}
   678  
   679  		// sanity
   680  		checkArtifacts(true)
   681  
   682  		snapdDir := filepath.Dir(dirs.SnapStateFile)
   683  		c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil)
   684  		c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil)
   685  
   686  		parser := testParser(c)
   687  		c.Assert(main.Run(parser, []string{"--reset", resetDirArg}), IsNil)
   688  
   689  		checkArtifacts(false)
   690  
   691  		// running reset again is ok
   692  		parser = testParser(c)
   693  		c.Assert(main.Run(parser, []string{"--reset", resetDirArg}), IsNil)
   694  
   695  		// reset complains if target directory doesn't exist
   696  		c.Assert(main.Run(parser, []string{"--reset", "/non/existing/chrootpath"}), ErrorMatches, `cannot reset non-existing directory "/non/existing/chrootpath"`)
   697  
   698  		// reset complains if target is not a directory
   699  		dummyFile := filepath.Join(resetDirArg, "foo")
   700  		c.Assert(ioutil.WriteFile(dummyFile, nil, os.ModePerm), IsNil)
   701  		err = main.Run(parser, []string{"--reset", dummyFile})
   702  		// the error message is always with an absolute file, so make the path
   703  		// absolute if we are running the relative test to properly match
   704  		if isRelative {
   705  			var err2 error
   706  			dummyFile, err2 = filepath.Abs(dummyFile)
   707  			c.Assert(err2, IsNil)
   708  		}
   709  		c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot reset %q, it is not a directory`, dummyFile))
   710  	}
   711  
   712  }
   713  
   714  func (s *startPreseedSuite) TestReadInfoSanity(c *C) {
   715  	var called bool
   716  	inf := &snap.Info{
   717  		BadInterfaces: make(map[string]string),
   718  		Plugs: map[string]*snap.PlugInfo{
   719  			"foo": {
   720  				Interface: "bad"},
   721  		},
   722  	}
   723  
   724  	// set a dummy sanitize method.
   725  	snap.SanitizePlugsSlots = func(*snap.Info) { called = true }
   726  
   727  	parser := testParser(c)
   728  	tmpDir := c.MkDir()
   729  	_ = main.Run(parser, []string{tmpDir})
   730  
   731  	// real sanitize method should be set after Run()
   732  	snap.SanitizePlugsSlots(inf)
   733  	c.Assert(called, Equals, false)
   734  	c.Assert(inf.BadInterfaces, HasLen, 1)
   735  }