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