github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  
    29  	"testing"
    30  
    31  	"github.com/jessevdk/go-flags"
    32  	. "gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/asserts"
    35  	"github.com/snapcore/snapd/asserts/assertstest"
    36  	"github.com/snapcore/snapd/cmd/snap-preseed"
    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) TestChrootValidationUnhappyNoApparmor(c *C) {
   133  	restore := main.MockOsGetuid(func() int { return 0 })
   134  	defer restore()
   135  
   136  	tmpDir := c.MkDir()
   137  	defer mockChrootDirs(c, tmpDir, false)()
   138  
   139  	parser := testParser(c)
   140  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, `cannot preseed without access to ".*sys/kernel/security/apparmor"`)
   141  }
   142  
   143  func (s *startPreseedSuite) TestChrootValidationAlreadyPreseeded(c *C) {
   144  	restore := main.MockOsGetuid(func() int { return 0 })
   145  	defer restore()
   146  
   147  	tmpDir := c.MkDir()
   148  	snapdDir := filepath.Dir(dirs.SnapStateFile)
   149  	c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil)
   150  	c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil)
   151  
   152  	parser := testParser(c)
   153  	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))
   154  }
   155  
   156  func (s *startPreseedSuite) TestChrootFailure(c *C) {
   157  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   158  	defer restoreOsGuid()
   159  
   160  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error {
   161  		return fmt.Errorf("FAIL: %s", path)
   162  	})
   163  	defer restoreSyscallChroot()
   164  
   165  	tmpDir := c.MkDir()
   166  	defer mockChrootDirs(c, tmpDir, true)()
   167  
   168  	parser := testParser(c)
   169  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, fmt.Sprintf("cannot chroot into %s: FAIL: %s", tmpDir, tmpDir))
   170  }
   171  
   172  func (s *startPreseedSuite) TestRunPreseedHappy(c *C) {
   173  	tmpDir := c.MkDir()
   174  	dirs.SetRootDir(tmpDir)
   175  	defer mockChrootDirs(c, tmpDir, true)()
   176  
   177  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   178  	defer restoreOsGuid()
   179  
   180  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
   181  	defer restoreSyscallChroot()
   182  
   183  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   184  	defer mockMountCmd.Restore()
   185  
   186  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   187  	defer mockUmountCmd.Restore()
   188  
   189  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   190  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   191  	defer restoreMountPath()
   192  
   193  	restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
   194  	defer restoreSystemSnapFromSeed()
   195  
   196  	mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), `#!/bin/sh
   197  	if [ "$SNAPD_PRESEED" != "1" ]; then
   198  		exit 1
   199  	fi
   200  `)
   201  	defer mockTargetSnapd.Restore()
   202  
   203  	mockSnapdFromDeb := testutil.MockCommand(c, filepath.Join(tmpDir, "usr/lib/snapd/snapd"), `#!/bin/sh
   204  	exit 1
   205  `)
   206  	defer mockSnapdFromDeb.Restore()
   207  
   208  	// snapd from the snap is newer than deb
   209  	mockVersionFiles(c, targetSnapdRoot, "2.44.0", tmpDir, "2.41.0")
   210  
   211  	parser := testParser(c)
   212  	c.Check(main.Run(parser, []string{tmpDir}), IsNil)
   213  
   214  	c.Assert(mockMountCmd.Calls(), HasLen, 1)
   215  	// note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test
   216  	// and mocking dirs.RootDir
   217  	c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "-o", "ro,x-gdu.hide", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)})
   218  
   219  	c.Assert(mockTargetSnapd.Calls(), HasLen, 1)
   220  	c.Check(mockTargetSnapd.Calls()[0], DeepEquals, []string{"snapd"})
   221  
   222  	c.Assert(mockSnapdFromDeb.Calls(), HasLen, 0)
   223  
   224  	// relative chroot path works too
   225  	tmpDirPath, relativeChroot := filepath.Split(tmpDir)
   226  	pwd, err := os.Getwd()
   227  	c.Assert(err, IsNil)
   228  	defer func() {
   229  		os.Chdir(pwd)
   230  	}()
   231  	c.Assert(os.Chdir(tmpDirPath), IsNil)
   232  	c.Check(main.Run(parser, []string{relativeChroot}), IsNil)
   233  }
   234  
   235  func (s *startPreseedSuite) TestRunPreseedHappyDebVersionIsNewer(c *C) {
   236  	tmpDir := c.MkDir()
   237  	dirs.SetRootDir(tmpDir)
   238  	defer mockChrootDirs(c, tmpDir, true)()
   239  
   240  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   241  	defer restoreOsGuid()
   242  
   243  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
   244  	defer restoreSyscallChroot()
   245  
   246  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   247  	defer mockMountCmd.Restore()
   248  
   249  	mockUmountCmd := testutil.MockCommand(c, "umount", "")
   250  	defer mockUmountCmd.Restore()
   251  
   252  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   253  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   254  	defer restoreMountPath()
   255  
   256  	restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
   257  	defer restoreSystemSnapFromSeed()
   258  
   259  	c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
   260  	mockSnapdFromSnap := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), `#!/bin/sh
   261  	exit 1
   262  `)
   263  	defer mockSnapdFromSnap.Restore()
   264  
   265  	mockSnapdFromDeb := testutil.MockCommand(c, filepath.Join(tmpDir, "usr/lib/snapd/snapd"), `#!/bin/sh
   266  	if [ "$SNAPD_PRESEED" != "1" ]; then
   267  		exit 1
   268  	fi
   269  `)
   270  	defer mockSnapdFromDeb.Restore()
   271  
   272  	// snapd from the deb is newer than snap
   273  	mockVersionFiles(c, targetSnapdRoot, "2.44.0", tmpDir, "2.45.0")
   274  
   275  	parser := testParser(c)
   276  	c.Check(main.Run(parser, []string{tmpDir}), IsNil)
   277  
   278  	c.Assert(mockMountCmd.Calls(), HasLen, 1)
   279  	// note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test
   280  	// and mocking dirs.RootDir
   281  	c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "-o", "ro,x-gdu.hide", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)})
   282  
   283  	c.Assert(mockSnapdFromDeb.Calls(), HasLen, 1)
   284  	c.Check(mockSnapdFromDeb.Calls()[0], DeepEquals, []string{"snapd"})
   285  	c.Assert(mockSnapdFromSnap.Calls(), HasLen, 0)
   286  }
   287  
   288  type Fake16Seed struct {
   289  	AssertsModel      *asserts.Model
   290  	Essential         []*seed.Snap
   291  	LoadMetaErr       error
   292  	LoadAssertionsErr error
   293  	UsesSnapd         bool
   294  }
   295  
   296  // Fake implementation of seed.Seed interface
   297  
   298  func mockClassicModel() *asserts.Model {
   299  	headers := map[string]interface{}{
   300  		"type":         "model",
   301  		"authority-id": "brand",
   302  		"series":       "16",
   303  		"brand-id":     "brand",
   304  		"model":        "classicbaz-3000",
   305  		"classic":      "true",
   306  		"timestamp":    "2018-01-01T08:00:00+00:00",
   307  	}
   308  	return assertstest.FakeAssertion(headers, nil).(*asserts.Model)
   309  }
   310  
   311  func (fs *Fake16Seed) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error {
   312  	return fs.LoadAssertionsErr
   313  }
   314  
   315  func (fs *Fake16Seed) Model() *asserts.Model {
   316  	return fs.AssertsModel
   317  }
   318  
   319  func (fs *Fake16Seed) Brand() (*asserts.Account, error) {
   320  	headers := map[string]interface{}{
   321  		"type":         "account",
   322  		"account-id":   "brand",
   323  		"display-name": "fake brand",
   324  		"username":     "brand",
   325  		"timestamp":    "2018-01-01T08:00:00+00:00",
   326  	}
   327  	return assertstest.FakeAssertion(headers, nil).(*asserts.Account), nil
   328  }
   329  
   330  func (fs *Fake16Seed) LoadMeta(tm timings.Measurer) error {
   331  	return fs.LoadMetaErr
   332  }
   333  
   334  func (fs *Fake16Seed) UsesSnapdSnap() bool {
   335  	return fs.UsesSnapd
   336  }
   337  
   338  func (fs *Fake16Seed) EssentialSnaps() []*seed.Snap {
   339  	return fs.Essential
   340  }
   341  
   342  func (fs *Fake16Seed) ModeSnaps(mode string) ([]*seed.Snap, error) {
   343  	return nil, nil
   344  }
   345  
   346  func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) {
   347  	tmpDir := c.MkDir()
   348  
   349  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
   350  		return &Fake16Seed{
   351  			AssertsModel: mockClassicModel(),
   352  			Essential:    []*seed.Snap{{Path: "/some/path/core", SideInfo: &snap.SideInfo{RealName: "core"}}},
   353  		}, nil
   354  	})
   355  	defer restore()
   356  
   357  	path, err := main.SystemSnapFromSeed(tmpDir)
   358  	c.Assert(err, IsNil)
   359  	c.Check(path, Equals, "/some/path/core")
   360  }
   361  
   362  func (s *startPreseedSuite) TestSystemSnapFromSnapdSeed(c *C) {
   363  	tmpDir := c.MkDir()
   364  
   365  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
   366  		return &Fake16Seed{
   367  			AssertsModel: mockClassicModel(),
   368  			Essential:    []*seed.Snap{{Path: "/some/path/snapd.snap", SideInfo: &snap.SideInfo{RealName: "snapd"}}},
   369  			UsesSnapd:    true,
   370  		}, nil
   371  	})
   372  	defer restore()
   373  
   374  	path, err := main.SystemSnapFromSeed(tmpDir)
   375  	c.Assert(err, IsNil)
   376  	c.Check(path, Equals, "/some/path/snapd.snap")
   377  }
   378  
   379  func (s *startPreseedSuite) TestSystemSnapFromSeedOpenError(c *C) {
   380  	tmpDir := c.MkDir()
   381  
   382  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return nil, fmt.Errorf("fail") })
   383  	defer restore()
   384  
   385  	_, err := main.SystemSnapFromSeed(tmpDir)
   386  	c.Assert(err, ErrorMatches, "fail")
   387  }
   388  
   389  func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) {
   390  	tmpDir := c.MkDir()
   391  
   392  	fakeSeed := &Fake16Seed{}
   393  	fakeSeed.AssertsModel = mockClassicModel()
   394  
   395  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
   396  	defer restore()
   397  
   398  	fakeSeed.Essential = []*seed.Snap{{Path: "", SideInfo: &snap.SideInfo{RealName: "core"}}}
   399  	_, err := main.SystemSnapFromSeed(tmpDir)
   400  	c.Assert(err, ErrorMatches, "core snap not found")
   401  
   402  	fakeSeed.Essential = []*seed.Snap{{Path: "/some/path", SideInfo: &snap.SideInfo{RealName: "foosnap"}}}
   403  	_, err = main.SystemSnapFromSeed(tmpDir)
   404  	c.Assert(err, ErrorMatches, "core snap not found")
   405  
   406  	fakeSeed.LoadMetaErr = fmt.Errorf("load meta failed")
   407  	_, err = main.SystemSnapFromSeed(tmpDir)
   408  	c.Assert(err, ErrorMatches, "load meta failed")
   409  
   410  	fakeSeed.LoadMetaErr = nil
   411  	fakeSeed.LoadAssertionsErr = fmt.Errorf("load assertions failed")
   412  	_, err = main.SystemSnapFromSeed(tmpDir)
   413  	c.Assert(err, ErrorMatches, "load assertions failed")
   414  }
   415  
   416  func (s *startPreseedSuite) TestClassicRequired(c *C) {
   417  	tmpDir := c.MkDir()
   418  
   419  	headers := map[string]interface{}{
   420  		"type":         "model",
   421  		"authority-id": "brand",
   422  		"series":       "16",
   423  		"brand-id":     "brand",
   424  		"model":        "baz-3000",
   425  		"architecture": "armhf",
   426  		"gadget":       "brand-gadget",
   427  		"kernel":       "kernel",
   428  		"timestamp":    "2018-01-01T08:00:00+00:00",
   429  	}
   430  
   431  	fakeSeed := &Fake16Seed{}
   432  	fakeSeed.AssertsModel = assertstest.FakeAssertion(headers, nil).(*asserts.Model)
   433  
   434  	restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
   435  	defer restore()
   436  
   437  	_, err := main.SystemSnapFromSeed(tmpDir)
   438  	c.Assert(err, ErrorMatches, "preseeding is only supported on classic systems")
   439  }
   440  
   441  func (s *startPreseedSuite) TestRunPreseedUnsupportedVersion(c *C) {
   442  	tmpDir := c.MkDir()
   443  	dirs.SetRootDir(tmpDir)
   444  	c.Assert(os.MkdirAll(filepath.Join(tmpDir, "usr/lib/snapd/"), 0755), IsNil)
   445  	defer mockChrootDirs(c, tmpDir, true)()
   446  
   447  	restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
   448  	defer restoreOsGuid()
   449  
   450  	restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
   451  	defer restoreSyscallChroot()
   452  
   453  	mockMountCmd := testutil.MockCommand(c, "mount", "")
   454  	defer mockMountCmd.Restore()
   455  
   456  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   457  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   458  	defer restoreMountPath()
   459  
   460  	restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
   461  	defer restoreSystemSnapFromSeed()
   462  
   463  	c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
   464  	mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), "")
   465  	defer mockTargetSnapd.Restore()
   466  
   467  	infoFile := filepath.Join(targetSnapdRoot, dirs.CoreLibExecDir, "info")
   468  	c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.43.0"), 0644), IsNil)
   469  
   470  	// simulate snapd version from the deb
   471  	infoFile = filepath.Join(filepath.Join(tmpDir, dirs.CoreLibExecDir, "info"))
   472  	c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.41.0"), 0644), IsNil)
   473  
   474  	parser := testParser(c)
   475  	c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches,
   476  		`snapd 2.43.0 from the target system does not support preseeding, the minimum required version is 2.43.3\+`)
   477  }
   478  
   479  func (s *startPreseedSuite) TestChooseTargetSnapdVersion(c *C) {
   480  	tmpDir := c.MkDir()
   481  	dirs.SetRootDir(tmpDir)
   482  	c.Assert(os.MkdirAll(filepath.Join(tmpDir, "usr/lib/snapd/"), 0755), IsNil)
   483  
   484  	targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
   485  	c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
   486  	restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
   487  	defer restoreMountPath()
   488  
   489  	var versions = []struct {
   490  		fromSnap        string
   491  		fromDeb         string
   492  		expectedPath    string
   493  		expectedVersion string
   494  		expectedErr     string
   495  	}{
   496  		{
   497  			fromDeb:  "2.44.0",
   498  			fromSnap: "2.45.3+git123",
   499  			// snap version wins
   500  			expectedVersion: "2.45.3+git123",
   501  			expectedPath:    filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/snapd"),
   502  		},
   503  		{
   504  			fromDeb:  "2.44.0",
   505  			fromSnap: "2.44.0",
   506  			// snap version wins
   507  			expectedVersion: "2.44.0",
   508  			expectedPath:    filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/snapd"),
   509  		},
   510  		{
   511  			fromDeb:  "2.45.1+20.04",
   512  			fromSnap: "2.45.1",
   513  			// deb version wins
   514  			expectedVersion: "2.45.1+20.04",
   515  			expectedPath:    filepath.Join(tmpDir, "usr/lib/snapd/snapd"),
   516  		},
   517  		{
   518  			fromDeb:  "2.45.2",
   519  			fromSnap: "2.45.1",
   520  			// deb version wins
   521  			expectedVersion: "2.45.2",
   522  			expectedPath:    filepath.Join(tmpDir, "usr/lib/snapd/snapd"),
   523  		},
   524  		{
   525  			fromSnap:    "2.45.1",
   526  			expectedErr: fmt.Sprintf("cannot open snapd info file %q.*", filepath.Join(tmpDir, "usr/lib/snapd/info")),
   527  		},
   528  		{
   529  			fromDeb:     "2.45.1",
   530  			expectedErr: fmt.Sprintf("cannot open snapd info file %q.*", filepath.Join(tmpDir, "target-core-mounted-here/usr/lib/snapd/info")),
   531  		},
   532  	}
   533  
   534  	for _, test := range versions {
   535  		infoFile := filepath.Join(tmpDir, "usr/lib/snapd/info")
   536  		os.Remove(infoFile)
   537  		if test.fromDeb != "" {
   538  			c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", test.fromDeb)), 0644), IsNil)
   539  		}
   540  		infoFile = filepath.Join(targetSnapdRoot, "usr/lib/snapd/info")
   541  		os.Remove(infoFile)
   542  		if test.fromSnap != "" {
   543  			c.Assert(ioutil.WriteFile(infoFile, []byte(fmt.Sprintf("VERSION=%s", test.fromSnap)), 0644), IsNil)
   544  		}
   545  
   546  		targetSnapd, err := main.ChooseTargetSnapdVersion()
   547  		if test.expectedErr != "" {
   548  			c.Assert(err, ErrorMatches, test.expectedErr)
   549  		} else {
   550  			c.Assert(err, IsNil)
   551  			c.Assert(targetSnapd, NotNil)
   552  			path, version := main.SnapdPathAndVersion(targetSnapd)
   553  			c.Check(path, Equals, test.expectedPath)
   554  			c.Check(version, Equals, test.expectedVersion)
   555  		}
   556  	}
   557  }
   558  
   559  func (s *startPreseedSuite) TestRunPreseedAgainstFilesystemRoot(c *C) {
   560  	restore := main.MockOsGetuid(func() int { return 0 })
   561  	defer restore()
   562  
   563  	parser := testParser(c)
   564  	c.Assert(main.Run(parser, []string{"/"}), ErrorMatches, `cannot run snap-preseed against /`)
   565  }
   566  
   567  func (s *startPreseedSuite) TestReset(c *C) {
   568  	restore := main.MockOsGetuid(func() int { return 0 })
   569  	defer restore()
   570  
   571  	startDir, err := os.Getwd()
   572  	c.Assert(err, IsNil)
   573  	defer func() {
   574  		os.Chdir(startDir)
   575  	}()
   576  
   577  	for _, isRelative := range []bool{false, true} {
   578  		tmpDir := c.MkDir()
   579  		resetDirArg := tmpDir
   580  		if isRelative {
   581  			var parentDir string
   582  			parentDir, resetDirArg = filepath.Split(tmpDir)
   583  			os.Chdir(parentDir)
   584  		}
   585  
   586  		// mock some preseeding artifacts
   587  		artifacts := []struct {
   588  			path string
   589  			// if symlinkTarget is not empty, then a path -> symlinkTarget symlink
   590  			// will be created instead of a regular file.
   591  			symlinkTarget string
   592  		}{
   593  			{dirs.SnapStateFile, ""},
   594  			{dirs.SnapSystemKeyFile, ""},
   595  			{filepath.Join(dirs.SnapDesktopFilesDir, "foo.desktop"), ""},
   596  			{filepath.Join(dirs.SnapDesktopIconsDir, "foo.png"), ""},
   597  			{filepath.Join(dirs.SnapMountPolicyDir, "foo.fstab"), ""},
   598  			{filepath.Join(dirs.SnapBlobDir, "foo.snap"), ""},
   599  			{filepath.Join(dirs.SnapUdevRulesDir, "foo-snap.bar.rules"), ""},
   600  			{filepath.Join(dirs.SnapDBusSystemPolicyDir, "snap.foo.bar.conf"), ""},
   601  			{filepath.Join(dirs.SnapServicesDir, "snap.foo.service"), ""},
   602  			{filepath.Join(dirs.SnapServicesDir, "snap.foo.timer"), ""},
   603  			{filepath.Join(dirs.SnapServicesDir, "snap.foo.socket"), ""},
   604  			{filepath.Join(dirs.SnapServicesDir, "snap-foo.mount"), ""},
   605  			{filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", "snap-foo.mount"), ""},
   606  			{filepath.Join(dirs.SnapDataDir, "foo", "bar"), ""},
   607  			{filepath.Join(dirs.SnapCacheDir, "foocache", "bar"), ""},
   608  			{filepath.Join(apparmor_sandbox.CacheDir, "foo", "bar"), ""},
   609  			{filepath.Join(dirs.SnapAppArmorDir, "foo"), ""},
   610  			{filepath.Join(dirs.SnapAssertsDBDir, "foo"), ""},
   611  			{filepath.Join(dirs.FeaturesDir, "foo"), ""},
   612  			{filepath.Join(dirs.SnapDeviceDir, "foo-1", "bar"), ""},
   613  			{filepath.Join(dirs.SnapCookieDir, "foo"), ""},
   614  			{filepath.Join(dirs.SnapSeqDir, "foo.json"), ""},
   615  			{filepath.Join(dirs.SnapMountDir, "foo", "bin"), ""},
   616  			{filepath.Join(dirs.SnapSeccompDir, "foo.bin"), ""},
   617  			// bash-completion symlinks
   618  			{filepath.Join(dirs.CompletersDir, "foo.bar"), "/a/snapd/complete.sh"},
   619  			{filepath.Join(dirs.CompletersDir, "foo"), "foo.bar"},
   620  		}
   621  
   622  		for _, art := range artifacts {
   623  			fullPath := filepath.Join(tmpDir, art.path)
   624  			// create parent dir
   625  			c.Assert(os.MkdirAll(filepath.Dir(fullPath), 0755), IsNil)
   626  			if art.symlinkTarget != "" {
   627  				// note, symlinkTarget is not relative to tmpDir
   628  				c.Assert(os.Symlink(art.symlinkTarget, fullPath), IsNil)
   629  			} else {
   630  				c.Assert(ioutil.WriteFile(fullPath, nil, os.ModePerm), IsNil)
   631  			}
   632  		}
   633  
   634  		checkArtifacts := func(exists bool) {
   635  			for _, art := range artifacts {
   636  				fullPath := filepath.Join(tmpDir, art.path)
   637  				if art.symlinkTarget != "" {
   638  					c.Check(osutil.IsSymlink(fullPath), Equals, exists, Commentf("offending symlink: %s", fullPath))
   639  				} else {
   640  					c.Check(osutil.FileExists(fullPath), Equals, exists, Commentf("offending file: %s", fullPath))
   641  				}
   642  			}
   643  		}
   644  
   645  		// sanity
   646  		checkArtifacts(true)
   647  
   648  		snapdDir := filepath.Dir(dirs.SnapStateFile)
   649  		c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil)
   650  		c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil)
   651  
   652  		parser := testParser(c)
   653  		c.Assert(main.Run(parser, []string{"--reset", resetDirArg}), IsNil)
   654  
   655  		checkArtifacts(false)
   656  
   657  		// running reset again is ok
   658  		parser = testParser(c)
   659  		c.Assert(main.Run(parser, []string{"--reset", resetDirArg}), IsNil)
   660  
   661  		// reset complains if target directory doesn't exist
   662  		c.Assert(main.Run(parser, []string{"--reset", "/non/existing/chrootpath"}), ErrorMatches, `cannot reset non-existing directory "/non/existing/chrootpath"`)
   663  
   664  		// reset complains if target is not a directory
   665  		dummyFile := filepath.Join(resetDirArg, "foo")
   666  		c.Assert(ioutil.WriteFile(dummyFile, nil, os.ModePerm), IsNil)
   667  		err = main.Run(parser, []string{"--reset", dummyFile})
   668  		// the error message is always with an absolute file, so make the path
   669  		// absolute if we are running the relative test to properly match
   670  		if isRelative {
   671  			var err2 error
   672  			dummyFile, err2 = filepath.Abs(dummyFile)
   673  			c.Assert(err2, IsNil)
   674  		}
   675  		c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot reset %q, it is not a directory`, dummyFile))
   676  	}
   677  
   678  }