github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapshotstate/backend/backend_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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  	"archive/zip"
    24  	"bytes"
    25  	"context"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"os/exec"
    32  	"os/user"
    33  	"path/filepath"
    34  	"sort"
    35  	"strings"
    36  	"testing"
    37  
    38  	"gopkg.in/check.v1"
    39  
    40  	"github.com/snapcore/snapd/client"
    41  	"github.com/snapcore/snapd/dirs"
    42  	"github.com/snapcore/snapd/logger"
    43  	"github.com/snapcore/snapd/osutil/sys"
    44  	"github.com/snapcore/snapd/overlord/snapshotstate/backend"
    45  	"github.com/snapcore/snapd/snap"
    46  )
    47  
    48  type snapshotSuite struct {
    49  	root      string
    50  	restore   []func()
    51  	tarPath   string
    52  	isTesting bool
    53  }
    54  
    55  // silly wrappers to get better failure messages
    56  type isTestingSuite struct{ snapshotSuite }
    57  type noTestingSuite struct{ snapshotSuite }
    58  
    59  var _ = check.Suite(&isTestingSuite{snapshotSuite{isTesting: true}})
    60  var _ = check.Suite(&noTestingSuite{snapshotSuite{isTesting: false}})
    61  
    62  // tie gocheck into testing
    63  func TestSnapshot(t *testing.T) { check.TestingT(t) }
    64  
    65  type tableT struct {
    66  	dir     string
    67  	name    string
    68  	content string
    69  }
    70  
    71  func table(si snap.PlaceInfo, homeDir string) []tableT {
    72  	return []tableT{
    73  		{
    74  			dir:     si.DataDir(),
    75  			name:    "foo",
    76  			content: "versioned system canary\n",
    77  		}, {
    78  			dir:     si.CommonDataDir(),
    79  			name:    "bar",
    80  			content: "common system canary\n",
    81  		}, {
    82  			dir:     si.UserDataDir(homeDir),
    83  			name:    "ufoo",
    84  			content: "versioned user canary\n",
    85  		}, {
    86  			dir:     si.UserCommonDataDir(homeDir),
    87  			name:    "ubar",
    88  			content: "common user canary\n",
    89  		},
    90  	}
    91  }
    92  
    93  func (s *snapshotSuite) SetUpTest(c *check.C) {
    94  	s.root = c.MkDir()
    95  
    96  	dirs.SetRootDir(s.root)
    97  
    98  	si := snap.MinimalPlaceInfo("hello-snap", snap.R(42))
    99  
   100  	for _, t := range table(si, filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   101  		c.Check(os.MkdirAll(t.dir, 0755), check.IsNil)
   102  		c.Check(ioutil.WriteFile(filepath.Join(t.dir, t.name), []byte(t.content), 0644), check.IsNil)
   103  	}
   104  
   105  	cur, err := user.Current()
   106  	c.Assert(err, check.IsNil)
   107  
   108  	s.restore = append(s.restore, backend.MockUserLookup(func(username string) (*user.User, error) {
   109  		if username != "snapuser" {
   110  			return nil, user.UnknownUserError(username)
   111  		}
   112  		rv := *cur
   113  		rv.Username = username
   114  		rv.HomeDir = filepath.Join(dirs.GlobalRootDir, "home/snapuser")
   115  		return &rv, nil
   116  	}),
   117  		backend.MockIsTesting(s.isTesting),
   118  	)
   119  
   120  	s.tarPath, err = exec.LookPath("tar")
   121  	c.Assert(err, check.IsNil)
   122  }
   123  
   124  func (s *snapshotSuite) TearDownTest(c *check.C) {
   125  	dirs.SetRootDir("")
   126  	for _, restore := range s.restore {
   127  		restore()
   128  	}
   129  }
   130  
   131  func hashkeys(snapshot *client.Snapshot) (keys []string) {
   132  	for k := range snapshot.SHA3_384 {
   133  		keys = append(keys, k)
   134  	}
   135  	sort.Strings(keys)
   136  
   137  	return keys
   138  }
   139  
   140  func (s *snapshotSuite) TestIterBailsIfContextDone(c *check.C) {
   141  	ctx, cancel := context.WithCancel(context.Background())
   142  	cancel()
   143  	triedToOpenDir := false
   144  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   145  		triedToOpenDir = true
   146  		return nil, nil // deal with it
   147  	})()
   148  
   149  	err := backend.Iter(ctx, nil)
   150  	c.Check(err, check.Equals, context.Canceled)
   151  	c.Check(triedToOpenDir, check.Equals, false)
   152  }
   153  
   154  func (s *snapshotSuite) TestIterBailsIfContextDoneMidway(c *check.C) {
   155  	ctx, cancel := context.WithCancel(context.Background())
   156  	triedToOpenDir := false
   157  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   158  		triedToOpenDir = true
   159  		return os.Open(os.DevNull)
   160  	})()
   161  	readNames := 0
   162  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   163  		readNames++
   164  		cancel()
   165  		return []string{"hello"}, nil
   166  	})()
   167  	triedToOpenSnapshot := false
   168  	defer backend.MockOpen(func(string) (*backend.Reader, error) {
   169  		triedToOpenSnapshot = true
   170  		return nil, nil
   171  	})()
   172  
   173  	err := backend.Iter(ctx, nil)
   174  	c.Check(err, check.Equals, context.Canceled)
   175  	c.Check(triedToOpenDir, check.Equals, true)
   176  	// bails as soon as
   177  	c.Check(readNames, check.Equals, 1)
   178  	c.Check(triedToOpenSnapshot, check.Equals, false)
   179  }
   180  
   181  func (s *snapshotSuite) TestIterReturnsOkIfSnapshotsDirNonexistent(c *check.C) {
   182  	triedToOpenDir := false
   183  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   184  		triedToOpenDir = true
   185  		return nil, os.ErrNotExist
   186  	})()
   187  
   188  	err := backend.Iter(context.Background(), nil)
   189  	c.Check(err, check.IsNil)
   190  	c.Check(triedToOpenDir, check.Equals, true)
   191  }
   192  
   193  func (s *snapshotSuite) TestIterBailsIfSnapshotsDirFails(c *check.C) {
   194  	triedToOpenDir := false
   195  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   196  		triedToOpenDir = true
   197  		return nil, os.ErrInvalid
   198  	})()
   199  
   200  	err := backend.Iter(context.Background(), nil)
   201  	c.Check(err, check.ErrorMatches, "cannot open snapshots directory: invalid argument")
   202  	c.Check(triedToOpenDir, check.Equals, true)
   203  }
   204  
   205  func (s *snapshotSuite) TestIterWarnsOnOpenErrorIfSnapshotNil(c *check.C) {
   206  	logbuf, restore := logger.MockLogger()
   207  	defer restore()
   208  	triedToOpenDir := false
   209  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   210  		triedToOpenDir = true
   211  		return new(os.File), nil
   212  	})()
   213  	readNames := 0
   214  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   215  		readNames++
   216  		if readNames > 1 {
   217  			return nil, io.EOF
   218  		}
   219  		return []string{"hello"}, nil
   220  	})()
   221  	triedToOpenSnapshot := false
   222  	defer backend.MockOpen(func(string) (*backend.Reader, error) {
   223  		triedToOpenSnapshot = true
   224  		return nil, os.ErrInvalid
   225  	})()
   226  
   227  	calledF := false
   228  	f := func(snapshot *backend.Reader) error {
   229  		calledF = true
   230  		return nil
   231  	}
   232  
   233  	err := backend.Iter(context.Background(), f)
   234  	// snapshot open errors are not failures:
   235  	c.Check(err, check.IsNil)
   236  	c.Check(triedToOpenDir, check.Equals, true)
   237  	c.Check(readNames, check.Equals, 2)
   238  	c.Check(triedToOpenSnapshot, check.Equals, true)
   239  	c.Check(logbuf.String(), check.Matches, `(?m).* Cannot open snapshot "hello": invalid argument.`)
   240  	c.Check(calledF, check.Equals, false)
   241  }
   242  
   243  func (s *snapshotSuite) TestIterCallsFuncIfSnapshotNotNil(c *check.C) {
   244  	logbuf, restore := logger.MockLogger()
   245  	defer restore()
   246  	triedToOpenDir := false
   247  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   248  		triedToOpenDir = true
   249  		return new(os.File), nil
   250  	})()
   251  	readNames := 0
   252  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   253  		readNames++
   254  		if readNames > 1 {
   255  			return nil, io.EOF
   256  		}
   257  		return []string{"hello"}, nil
   258  	})()
   259  	triedToOpenSnapshot := false
   260  	defer backend.MockOpen(func(string) (*backend.Reader, error) {
   261  		triedToOpenSnapshot = true
   262  		// NOTE non-nil reader, and error, returned
   263  		r := backend.Reader{}
   264  		r.Broken = "xyzzy"
   265  		return &r, os.ErrInvalid
   266  	})()
   267  
   268  	calledF := false
   269  	f := func(snapshot *backend.Reader) error {
   270  		c.Check(snapshot.Broken, check.Equals, "xyzzy")
   271  		calledF = true
   272  		return nil
   273  	}
   274  
   275  	err := backend.Iter(context.Background(), f)
   276  	// snapshot open errors are not failures:
   277  	c.Check(err, check.IsNil)
   278  	c.Check(triedToOpenDir, check.Equals, true)
   279  	c.Check(readNames, check.Equals, 2)
   280  	c.Check(triedToOpenSnapshot, check.Equals, true)
   281  	c.Check(logbuf.String(), check.Equals, "")
   282  	c.Check(calledF, check.Equals, true)
   283  }
   284  
   285  func (s *snapshotSuite) TestIterReportsCloseError(c *check.C) {
   286  	logbuf, restore := logger.MockLogger()
   287  	defer restore()
   288  	triedToOpenDir := false
   289  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   290  		triedToOpenDir = true
   291  		return new(os.File), nil
   292  	})()
   293  	readNames := 0
   294  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   295  		readNames++
   296  		if readNames > 1 {
   297  			return nil, io.EOF
   298  		}
   299  		return []string{"hello"}, nil
   300  	})()
   301  	triedToOpenSnapshot := false
   302  	defer backend.MockOpen(func(string) (*backend.Reader, error) {
   303  		triedToOpenSnapshot = true
   304  		r := backend.Reader{}
   305  		r.SetID = 42
   306  		return &r, nil
   307  	})()
   308  
   309  	calledF := false
   310  	f := func(snapshot *backend.Reader) error {
   311  		c.Check(snapshot.SetID, check.Equals, uint64(42))
   312  		calledF = true
   313  		return nil
   314  	}
   315  
   316  	err := backend.Iter(context.Background(), f)
   317  	// snapshot close errors _are_ failures (because they're completely unexpected):
   318  	c.Check(err, check.Equals, os.ErrInvalid)
   319  	c.Check(triedToOpenDir, check.Equals, true)
   320  	c.Check(readNames, check.Equals, 1) // never gets to read another one
   321  	c.Check(triedToOpenSnapshot, check.Equals, true)
   322  	c.Check(logbuf.String(), check.Equals, "")
   323  	c.Check(calledF, check.Equals, true)
   324  }
   325  
   326  func (s *snapshotSuite) TestList(c *check.C) {
   327  	logbuf, restore := logger.MockLogger()
   328  	defer restore()
   329  	defer backend.MockOsOpen(func(string) (*os.File, error) { return new(os.File), nil })()
   330  	readNames := 0
   331  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   332  		readNames++
   333  		if readNames > 4 {
   334  			return nil, io.EOF
   335  		}
   336  		return []string{
   337  			fmt.Sprintf("%d_foo", readNames),
   338  			fmt.Sprintf("%d_bar", readNames),
   339  			fmt.Sprintf("%d_baz", readNames),
   340  		}, nil
   341  	})()
   342  	defer backend.MockOpen(func(fn string) (*backend.Reader, error) {
   343  		var id uint64
   344  		var snapname string
   345  		fn = filepath.Base(fn)
   346  		_, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname)
   347  		c.Assert(err, check.IsNil, check.Commentf(fn))
   348  		f, err := os.Open(os.DevNull)
   349  		c.Assert(err, check.IsNil, check.Commentf(fn))
   350  		return &backend.Reader{
   351  			File: f,
   352  			Snapshot: client.Snapshot{
   353  				SetID:    id,
   354  				Snap:     snapname,
   355  				SnapID:   "id-for-" + snapname,
   356  				Version:  "v1.0-" + snapname,
   357  				Revision: snap.R(int(id)),
   358  			},
   359  		}, nil
   360  	})()
   361  
   362  	type tableT struct {
   363  		setID     uint64
   364  		snapnames []string
   365  		numSets   int
   366  		numShots  int
   367  		predicate func(*client.Snapshot) bool
   368  	}
   369  	table := []tableT{
   370  		{0, nil, 4, 12, nil},
   371  		{0, []string{"foo"}, 4, 4, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" }},
   372  		{1, nil, 1, 3, func(snapshot *client.Snapshot) bool { return snapshot.SetID == 1 }},
   373  		{2, []string{"bar"}, 1, 1, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "bar" && snapshot.SetID == 2 }},
   374  		{0, []string{"foo", "bar"}, 4, 8, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" || snapshot.Snap == "bar" }},
   375  	}
   376  
   377  	for i, t := range table {
   378  		comm := check.Commentf("%d: %d/%v", i, t.setID, t.snapnames)
   379  		// reset
   380  		readNames = 0
   381  		logbuf.Reset()
   382  
   383  		sets, err := backend.List(context.Background(), t.setID, t.snapnames)
   384  		c.Check(err, check.IsNil, comm)
   385  		c.Check(readNames, check.Equals, 5, comm)
   386  		c.Check(logbuf.String(), check.Equals, "", comm)
   387  		c.Check(sets, check.HasLen, t.numSets, comm)
   388  		nShots := 0
   389  		fnTpl := filepath.Join(dirs.SnapshotsDir, "%d_%s_%s_%s.zip")
   390  		for j, ss := range sets {
   391  			for k, snapshot := range ss.Snapshots {
   392  				comm := check.Commentf("%d: %d/%v #%d/%d", i, t.setID, t.snapnames, j, k)
   393  				if t.predicate != nil {
   394  					c.Check(t.predicate(snapshot), check.Equals, true, comm)
   395  				}
   396  				nShots++
   397  				fn := fmt.Sprintf(fnTpl, snapshot.SetID, snapshot.Snap, snapshot.Version, snapshot.Revision)
   398  				c.Check(backend.Filename(snapshot), check.Equals, fn, comm)
   399  				c.Check(snapshot.SnapID, check.Equals, "id-for-"+snapshot.Snap)
   400  			}
   401  		}
   402  		c.Check(nShots, check.Equals, t.numShots)
   403  	}
   404  }
   405  
   406  func (s *snapshotSuite) TestAddDirToZipBails(c *check.C) {
   407  	snapshot := &client.Snapshot{SetID: 42, Snap: "a-snap"}
   408  	buf, restore := logger.MockLogger()
   409  	defer restore()
   410  	// note as the zip is nil this would panic if it didn't bail
   411  	c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", filepath.Join(s.root, "nonexistent")), check.IsNil)
   412  	// no log for the non-existent case
   413  	c.Check(buf.String(), check.Equals, "")
   414  	buf.Reset()
   415  	c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", "/etc/passwd"), check.IsNil)
   416  	c.Check(buf.String(), check.Matches, "(?m).* is not a directory.")
   417  }
   418  
   419  func (s *snapshotSuite) TestAddDirToZipTarFails(c *check.C) {
   420  	d := filepath.Join(s.root, "foo")
   421  	c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil)
   422  	c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil)
   423  
   424  	ctx, cancel := context.WithCancel(context.Background())
   425  	cancel()
   426  
   427  	var buf bytes.Buffer
   428  	z := zip.NewWriter(&buf)
   429  	c.Assert(backend.AddDirToZip(ctx, nil, z, "", "an/entry", d), check.ErrorMatches, ".* context canceled")
   430  }
   431  
   432  func (s *snapshotSuite) TestAddDirToZip(c *check.C) {
   433  	d := filepath.Join(s.root, "foo")
   434  	c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil)
   435  	c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil)
   436  	c.Assert(ioutil.WriteFile(filepath.Join(d, "bar", "baz"), []byte("hello\n"), 0644), check.IsNil)
   437  
   438  	var buf bytes.Buffer
   439  	z := zip.NewWriter(&buf)
   440  	snapshot := &client.Snapshot{
   441  		SHA3_384: map[string]string{},
   442  	}
   443  	c.Assert(backend.AddDirToZip(context.Background(), snapshot, z, "", "an/entry", d), check.IsNil)
   444  	z.Close() // write out the central directory
   445  
   446  	c.Check(snapshot.SHA3_384, check.HasLen, 1)
   447  	c.Check(snapshot.SHA3_384["an/entry"], check.HasLen, 96)
   448  	c.Check(snapshot.Size > 0, check.Equals, true) // actual size most likely system-dependent
   449  	br := bytes.NewReader(buf.Bytes())
   450  	r, err := zip.NewReader(br, int64(br.Len()))
   451  	c.Assert(err, check.IsNil)
   452  	c.Check(r.File, check.HasLen, 1)
   453  	c.Check(r.File[0].Name, check.Equals, "an/entry")
   454  }
   455  
   456  func (s *snapshotSuite) TestHappyRoundtrip(c *check.C) {
   457  	s.testHappyRoundtrip(c, "marker", false)
   458  }
   459  
   460  func (s *snapshotSuite) TestHappyRoundtripAutomaticSnapshot(c *check.C) {
   461  	s.testHappyRoundtrip(c, "marker", true)
   462  }
   463  
   464  func (s *snapshotSuite) TestHappyRoundtripNoCommon(c *check.C) {
   465  	for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   466  		if _, d := filepath.Split(t.dir); d == "common" {
   467  			c.Assert(os.RemoveAll(t.dir), check.IsNil)
   468  		}
   469  	}
   470  	s.testHappyRoundtrip(c, "marker", false)
   471  }
   472  
   473  func (s *snapshotSuite) TestHappyRoundtripNoRev(c *check.C) {
   474  	for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   475  		if _, d := filepath.Split(t.dir); d == "42" {
   476  			c.Assert(os.RemoveAll(t.dir), check.IsNil)
   477  		}
   478  	}
   479  	s.testHappyRoundtrip(c, "../common/marker", false)
   480  }
   481  
   482  func (s *snapshotSuite) testHappyRoundtrip(c *check.C, marker string, auto bool) {
   483  	if os.Geteuid() == 0 {
   484  		c.Skip("this test cannot run as root (runuser will fail)")
   485  	}
   486  	logger.SimpleSetup()
   487  
   488  	epoch := snap.E("42*")
   489  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   490  	cfg := map[string]interface{}{"some-setting": false}
   491  	shID := uint64(12)
   492  
   493  	shw, err := backend.Save(context.TODO(), shID, info, cfg, []string{"snapuser"}, &backend.Flags{Auto: auto})
   494  	c.Assert(err, check.IsNil)
   495  	c.Check(shw.SetID, check.Equals, shID)
   496  	c.Check(shw.Snap, check.Equals, info.InstanceName())
   497  	c.Check(shw.SnapID, check.Equals, info.SnapID)
   498  	c.Check(shw.Version, check.Equals, info.Version)
   499  	c.Check(shw.Epoch, check.DeepEquals, epoch)
   500  	c.Check(shw.Revision, check.Equals, info.Revision)
   501  	c.Check(shw.Conf, check.DeepEquals, cfg)
   502  	c.Check(shw.Auto, check.Equals, auto)
   503  	c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   504  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
   505  
   506  	shs, err := backend.List(context.TODO(), 0, nil)
   507  	c.Assert(err, check.IsNil)
   508  	c.Assert(shs, check.HasLen, 1)
   509  	c.Assert(shs[0].Snapshots, check.HasLen, 1)
   510  
   511  	shr, err := backend.Open(backend.Filename(shw))
   512  	c.Assert(err, check.IsNil)
   513  	defer shr.Close()
   514  
   515  	for label, sh := range map[string]*client.Snapshot{"open": &shr.Snapshot, "list": shs[0].Snapshots[0]} {
   516  		comm := check.Commentf("%q", label)
   517  		c.Check(sh.SetID, check.Equals, shID, comm)
   518  		c.Check(sh.Snap, check.Equals, info.InstanceName(), comm)
   519  		c.Check(sh.SnapID, check.Equals, info.SnapID, comm)
   520  		c.Check(sh.Version, check.Equals, info.Version, comm)
   521  		c.Check(sh.Epoch, check.DeepEquals, epoch)
   522  		c.Check(sh.Revision, check.Equals, info.Revision, comm)
   523  		c.Check(sh.Conf, check.DeepEquals, cfg, comm)
   524  		c.Check(sh.SHA3_384, check.DeepEquals, shw.SHA3_384, comm)
   525  		c.Check(sh.Auto, check.Equals, auto)
   526  	}
   527  	c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   528  	c.Check(shr.Check(context.TODO(), nil), check.IsNil)
   529  
   530  	newroot := c.MkDir()
   531  	c.Assert(os.MkdirAll(filepath.Join(newroot, "home/snapuser"), 0755), check.IsNil)
   532  	dirs.SetRootDir(newroot)
   533  
   534  	var diff = func() *exec.Cmd {
   535  		cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot)
   536  		// cmd.Stdout = os.Stdout
   537  		// cmd.Stderr = os.Stderr
   538  		return cmd
   539  	}
   540  
   541  	for i := 0; i < 3; i++ {
   542  		comm := check.Commentf("%d", i)
   543  		// sanity check
   544  		c.Check(diff().Run(), check.NotNil, comm)
   545  
   546  		// restore leaves things like they were (again and again)
   547  		rs, err := shr.Restore(context.TODO(), snap.R(0), nil, logger.Debugf)
   548  		c.Assert(err, check.IsNil, comm)
   549  		rs.Cleanup()
   550  		c.Check(diff().Run(), check.IsNil, comm)
   551  
   552  		// dirty it -> no longer like it was
   553  		c.Check(ioutil.WriteFile(filepath.Join(info.DataDir(), marker), []byte("scribble\n"), 0644), check.IsNil, comm)
   554  	}
   555  }
   556  
   557  func (s *snapshotSuite) TestRestoreRoundtripDifferentRevision(c *check.C) {
   558  	if os.Geteuid() == 0 {
   559  		c.Skip("this test cannot run as root (runuser will fail)")
   560  	}
   561  	logger.SimpleSetup()
   562  
   563  	epoch := snap.E("42*")
   564  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   565  	shID := uint64(12)
   566  
   567  	shw, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"}, nil)
   568  	c.Assert(err, check.IsNil)
   569  	c.Check(shw.Revision, check.Equals, info.Revision)
   570  
   571  	shr, err := backend.Open(backend.Filename(shw))
   572  	c.Assert(err, check.IsNil)
   573  	defer shr.Close()
   574  
   575  	c.Check(shr.Revision, check.Equals, info.Revision)
   576  	c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   577  
   578  	// move the expected data to its expected place
   579  	for _, dir := range []string{
   580  		filepath.Join(s.root, "home", "snapuser", "snap", "hello-snap"),
   581  		filepath.Join(dirs.SnapDataDir, "hello-snap"),
   582  	} {
   583  		c.Check(os.Rename(filepath.Join(dir, "42"), filepath.Join(dir, "17")), check.IsNil)
   584  	}
   585  
   586  	newroot := c.MkDir()
   587  	c.Assert(os.MkdirAll(filepath.Join(newroot, "home", "snapuser"), 0755), check.IsNil)
   588  	dirs.SetRootDir(newroot)
   589  
   590  	var diff = func() *exec.Cmd {
   591  		cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot)
   592  		// cmd.Stdout = os.Stdout
   593  		// cmd.Stderr = os.Stderr
   594  		return cmd
   595  	}
   596  
   597  	// sanity check
   598  	c.Check(diff().Run(), check.NotNil)
   599  
   600  	// restore leaves things like they were, but in the new dir
   601  	rs, err := shr.Restore(context.TODO(), snap.R("17"), nil, logger.Debugf)
   602  	c.Assert(err, check.IsNil)
   603  	rs.Cleanup()
   604  	c.Check(diff().Run(), check.IsNil)
   605  }
   606  
   607  func (s *snapshotSuite) TestPickUserWrapperRunuser(c *check.C) {
   608  	n := 0
   609  	defer backend.MockExecLookPath(func(s string) (string, error) {
   610  		n++
   611  		if s != "runuser" {
   612  			c.Fatalf(`expected to get "runuser", got %q`, s)
   613  		}
   614  		return "/sbin/runuser", nil
   615  	})()
   616  
   617  	c.Check(backend.PickUserWrapper(), check.Equals, "/sbin/runuser")
   618  	c.Check(n, check.Equals, 1)
   619  }
   620  
   621  func (s *snapshotSuite) TestPickUserWrapperSudo(c *check.C) {
   622  	n := 0
   623  	defer backend.MockExecLookPath(func(s string) (string, error) {
   624  		n++
   625  		if n == 1 {
   626  			if s != "runuser" {
   627  				c.Fatalf(`expected to get "runuser" first, got %q`, s)
   628  			}
   629  			return "", errors.New("no such thing")
   630  		}
   631  		if s != "sudo" {
   632  			c.Fatalf(`expected to get "sudo" next, got %q`, s)
   633  		}
   634  		return "/usr/bin/sudo", nil
   635  	})()
   636  
   637  	c.Check(backend.PickUserWrapper(), check.Equals, "/usr/bin/sudo")
   638  	c.Check(n, check.Equals, 2)
   639  }
   640  
   641  func (s *snapshotSuite) TestPickUserWrapperNothing(c *check.C) {
   642  	n := 0
   643  	defer backend.MockExecLookPath(func(s string) (string, error) {
   644  		n++
   645  		return "", errors.New("no such thing")
   646  	})()
   647  
   648  	c.Check(backend.PickUserWrapper(), check.Equals, "")
   649  	c.Check(n, check.Equals, 2)
   650  }
   651  
   652  func (s *snapshotSuite) TestMaybeRunuserHappyRunuser(c *check.C) {
   653  	uid := sys.UserID(0)
   654  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   655  	defer backend.SetUserWrapper("/sbin/runuser")()
   656  	logbuf, restore := logger.MockLogger()
   657  	defer restore()
   658  
   659  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   660  		Path: "/sbin/runuser",
   661  		Args: []string{"/sbin/runuser", "-u", "test", "--", "tar", "--bar"},
   662  	})
   663  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   664  		Path: s.tarPath,
   665  		Args: []string{"tar", "--bar"},
   666  	})
   667  	uid = 42
   668  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   669  		Path: s.tarPath,
   670  		Args: []string{"tar", "--bar"},
   671  	})
   672  	c.Check(logbuf.String(), check.Equals, "")
   673  }
   674  
   675  func (s *snapshotSuite) TestMaybeRunuserHappySudo(c *check.C) {
   676  	uid := sys.UserID(0)
   677  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   678  	defer backend.SetUserWrapper("/usr/bin/sudo")()
   679  	logbuf, restore := logger.MockLogger()
   680  	defer restore()
   681  
   682  	cmd := backend.TarAsUser("test", "--bar")
   683  	c.Check(cmd, check.DeepEquals, &exec.Cmd{
   684  		Path: "/usr/bin/sudo",
   685  		Args: []string{"/usr/bin/sudo", "-u", "test", "--", "tar", "--bar"},
   686  	})
   687  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   688  		Path: s.tarPath,
   689  		Args: []string{"tar", "--bar"},
   690  	})
   691  	uid = 42
   692  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   693  		Path: s.tarPath,
   694  		Args: []string{"tar", "--bar"},
   695  	})
   696  	c.Check(logbuf.String(), check.Equals, "")
   697  }
   698  
   699  func (s *snapshotSuite) TestMaybeRunuserNoHappy(c *check.C) {
   700  	uid := sys.UserID(0)
   701  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   702  	defer backend.SetUserWrapper("")()
   703  	logbuf, restore := logger.MockLogger()
   704  	defer restore()
   705  
   706  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   707  		Path: s.tarPath,
   708  		Args: []string{"tar", "--bar"},
   709  	})
   710  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   711  		Path: s.tarPath,
   712  		Args: []string{"tar", "--bar"},
   713  	})
   714  	uid = 42
   715  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   716  		Path: s.tarPath,
   717  		Args: []string{"tar", "--bar"},
   718  	})
   719  	c.Check(strings.TrimSpace(logbuf.String()), check.Matches, ".* No user wrapper found.*")
   720  }