github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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) TestLastSnapshotID(c *check.C) {
   142  	// LastSnapshotSetID is happy without any snapshots
   143  	setID, err := backend.LastSnapshotSetID()
   144  	c.Assert(err, check.IsNil)
   145  	c.Check(setID, check.Equals, uint64(0))
   146  
   147  	// create snapshots dir and dummy snapshots
   148  	os.MkdirAll(dirs.SnapshotsDir, os.ModePerm)
   149  	for _, name := range []string{
   150  		"9_some-snap-1.zip", "1234_not-a-snapshot", "12_other-snap.zip", "3_foo.zip",
   151  	} {
   152  		c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, name), []byte{}, 0644), check.IsNil)
   153  	}
   154  	setID, err = backend.LastSnapshotSetID()
   155  	c.Assert(err, check.IsNil)
   156  	c.Check(setID, check.Equals, uint64(12))
   157  }
   158  
   159  func (s *snapshotSuite) TestLastSnapshotIDErrorOnDirNames(c *check.C) {
   160  	// we need snapshots dir, otherwise LastSnapshotSetID exits early.
   161  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, os.ModePerm), check.IsNil)
   162  
   163  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   164  		return nil, fmt.Errorf("fail")
   165  	})()
   166  	setID, err := backend.LastSnapshotSetID()
   167  	c.Assert(err, check.ErrorMatches, "fail")
   168  	c.Check(setID, check.Equals, uint64(0))
   169  }
   170  
   171  func (s *snapshotSuite) TestIsSnapshotFilename(c *check.C) {
   172  	tests := []struct {
   173  		name  string
   174  		valid bool
   175  		setID uint64
   176  	}{
   177  		{"1_foo.zip", true, 1},
   178  		{"14_hello-world_6.4_29.zip", true, 14},
   179  		{"1_.zip", false, 0},
   180  		{"1_foo.zip.bak", false, 0},
   181  		{"foo_1_foo.zip", false, 0},
   182  		{"foo_bar_baz.zip", false, 0},
   183  		{"", false, 0},
   184  		{"1_", false, 0},
   185  	}
   186  
   187  	for _, t := range tests {
   188  		ok, setID := backend.IsSnapshotFilename(t.name)
   189  		c.Check(ok, check.Equals, t.valid, check.Commentf("fail: %s", t.name))
   190  		c.Check(setID, check.Equals, t.setID, check.Commentf("fail: %s", t.name))
   191  	}
   192  }
   193  
   194  func (s *snapshotSuite) TestIterBailsIfContextDone(c *check.C) {
   195  	ctx, cancel := context.WithCancel(context.Background())
   196  	cancel()
   197  	triedToOpenDir := false
   198  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   199  		triedToOpenDir = true
   200  		return nil, nil // deal with it
   201  	})()
   202  
   203  	err := backend.Iter(ctx, nil)
   204  	c.Check(err, check.Equals, context.Canceled)
   205  	c.Check(triedToOpenDir, check.Equals, false)
   206  }
   207  
   208  func (s *snapshotSuite) TestIterBailsIfContextDoneMidway(c *check.C) {
   209  	ctx, cancel := context.WithCancel(context.Background())
   210  	triedToOpenDir := false
   211  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   212  		triedToOpenDir = true
   213  		return os.Open(os.DevNull)
   214  	})()
   215  	readNames := 0
   216  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   217  		readNames++
   218  		cancel()
   219  		return []string{"hello"}, nil
   220  	})()
   221  	triedToOpenSnapshot := false
   222  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   223  		triedToOpenSnapshot = true
   224  		return nil, nil
   225  	})()
   226  
   227  	err := backend.Iter(ctx, nil)
   228  	c.Check(err, check.Equals, context.Canceled)
   229  	c.Check(triedToOpenDir, check.Equals, true)
   230  	// bails as soon as
   231  	c.Check(readNames, check.Equals, 1)
   232  	c.Check(triedToOpenSnapshot, check.Equals, false)
   233  }
   234  
   235  func (s *snapshotSuite) TestIterReturnsOkIfSnapshotsDirNonexistent(c *check.C) {
   236  	triedToOpenDir := false
   237  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   238  		triedToOpenDir = true
   239  		return nil, os.ErrNotExist
   240  	})()
   241  
   242  	err := backend.Iter(context.Background(), nil)
   243  	c.Check(err, check.IsNil)
   244  	c.Check(triedToOpenDir, check.Equals, true)
   245  }
   246  
   247  func (s *snapshotSuite) TestIterBailsIfSnapshotsDirFails(c *check.C) {
   248  	triedToOpenDir := false
   249  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   250  		triedToOpenDir = true
   251  		return nil, os.ErrInvalid
   252  	})()
   253  
   254  	err := backend.Iter(context.Background(), nil)
   255  	c.Check(err, check.ErrorMatches, "cannot open snapshots directory: invalid argument")
   256  	c.Check(triedToOpenDir, check.Equals, true)
   257  }
   258  
   259  func (s *snapshotSuite) TestIterWarnsOnOpenErrorIfSnapshotNil(c *check.C) {
   260  	logbuf, restore := logger.MockLogger()
   261  	defer restore()
   262  	triedToOpenDir := false
   263  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   264  		triedToOpenDir = true
   265  		return new(os.File), nil
   266  	})()
   267  	readNames := 0
   268  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   269  		readNames++
   270  		if readNames > 1 {
   271  			return nil, io.EOF
   272  		}
   273  		return []string{"1_hello.zip"}, nil
   274  	})()
   275  	triedToOpenSnapshot := false
   276  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   277  		triedToOpenSnapshot = true
   278  		return nil, os.ErrInvalid
   279  	})()
   280  
   281  	calledF := false
   282  	f := func(snapshot *backend.Reader) error {
   283  		calledF = true
   284  		return nil
   285  	}
   286  
   287  	err := backend.Iter(context.Background(), f)
   288  	// snapshot open errors are not failures:
   289  	c.Check(err, check.IsNil)
   290  	c.Check(triedToOpenDir, check.Equals, true)
   291  	c.Check(readNames, check.Equals, 2)
   292  	c.Check(triedToOpenSnapshot, check.Equals, true)
   293  	c.Check(logbuf.String(), check.Matches, `(?m).* Cannot open snapshot "1_hello.zip": invalid argument.`)
   294  	c.Check(calledF, check.Equals, false)
   295  }
   296  
   297  func (s *snapshotSuite) TestIterCallsFuncIfSnapshotNotNil(c *check.C) {
   298  	logbuf, restore := logger.MockLogger()
   299  	defer restore()
   300  	triedToOpenDir := false
   301  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   302  		triedToOpenDir = true
   303  		return new(os.File), nil
   304  	})()
   305  	readNames := 0
   306  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   307  		readNames++
   308  		if readNames > 1 {
   309  			return nil, io.EOF
   310  		}
   311  		return []string{"1_hello.zip"}, nil
   312  	})()
   313  	triedToOpenSnapshot := false
   314  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   315  		triedToOpenSnapshot = true
   316  		// NOTE non-nil reader, and error, returned
   317  		r := backend.Reader{}
   318  		r.SetID = 1
   319  		r.Broken = "xyzzy"
   320  		return &r, os.ErrInvalid
   321  	})()
   322  
   323  	calledF := false
   324  	f := func(snapshot *backend.Reader) error {
   325  		c.Check(snapshot.Broken, check.Equals, "xyzzy")
   326  		calledF = true
   327  		return nil
   328  	}
   329  
   330  	err := backend.Iter(context.Background(), f)
   331  	// snapshot open errors are not failures:
   332  	c.Check(err, check.IsNil)
   333  	c.Check(triedToOpenDir, check.Equals, true)
   334  	c.Check(readNames, check.Equals, 2)
   335  	c.Check(triedToOpenSnapshot, check.Equals, true)
   336  	c.Check(logbuf.String(), check.Equals, "")
   337  	c.Check(calledF, check.Equals, true)
   338  }
   339  
   340  func (s *snapshotSuite) TestIterReportsCloseError(c *check.C) {
   341  	logbuf, restore := logger.MockLogger()
   342  	defer restore()
   343  	triedToOpenDir := false
   344  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   345  		triedToOpenDir = true
   346  		return new(os.File), nil
   347  	})()
   348  	readNames := 0
   349  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   350  		readNames++
   351  		if readNames > 1 {
   352  			return nil, io.EOF
   353  		}
   354  		return []string{"42_hello.zip"}, nil
   355  	})()
   356  	triedToOpenSnapshot := false
   357  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   358  		triedToOpenSnapshot = true
   359  		r := backend.Reader{}
   360  		r.SetID = 42
   361  		return &r, nil
   362  	})()
   363  
   364  	calledF := false
   365  	f := func(snapshot *backend.Reader) error {
   366  		c.Check(snapshot.SetID, check.Equals, uint64(42))
   367  		calledF = true
   368  		return nil
   369  	}
   370  
   371  	err := backend.Iter(context.Background(), f)
   372  	// snapshot close errors _are_ failures (because they're completely unexpected):
   373  	c.Check(err, check.Equals, os.ErrInvalid)
   374  	c.Check(triedToOpenDir, check.Equals, true)
   375  	c.Check(readNames, check.Equals, 1) // never gets to read another one
   376  	c.Check(triedToOpenSnapshot, check.Equals, true)
   377  	c.Check(logbuf.String(), check.Equals, "")
   378  	c.Check(calledF, check.Equals, true)
   379  }
   380  
   381  func readerForFilename(fname string, c *check.C) *backend.Reader {
   382  	var snapname string
   383  	var id uint64
   384  	fn := strings.TrimSuffix(filepath.Base(fname), ".zip")
   385  	_, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname)
   386  	c.Assert(err, check.IsNil, check.Commentf(fn))
   387  	f, err := os.Open(os.DevNull)
   388  	c.Assert(err, check.IsNil, check.Commentf(fn))
   389  	return &backend.Reader{
   390  		File: f,
   391  		Snapshot: client.Snapshot{
   392  			SetID: id,
   393  			Snap:  snapname,
   394  		},
   395  	}
   396  }
   397  
   398  func (s *snapshotSuite) TestIterIgnoresSnapshotsWithInvalidNames(c *check.C) {
   399  	logbuf, restore := logger.MockLogger()
   400  	defer restore()
   401  
   402  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   403  		return new(os.File), nil
   404  	})()
   405  	readNames := 0
   406  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   407  		readNames++
   408  		if readNames > 1 {
   409  			return nil, io.EOF
   410  		}
   411  		return []string{
   412  			"_foo.zip",
   413  			"43_bar.zip",
   414  			"foo_bar.zip",
   415  			"bar.",
   416  		}, nil
   417  	})()
   418  	defer backend.MockOpen(func(fname string, setID uint64) (*backend.Reader, error) {
   419  		return readerForFilename(fname, c), nil
   420  	})()
   421  
   422  	var calledF int
   423  	f := func(snapshot *backend.Reader) error {
   424  		calledF++
   425  		c.Check(snapshot.SetID, check.Equals, uint64(43))
   426  		return nil
   427  	}
   428  
   429  	err := backend.Iter(context.Background(), f)
   430  	c.Check(err, check.IsNil)
   431  	c.Check(logbuf.String(), check.Equals, "")
   432  	c.Check(calledF, check.Equals, 1)
   433  }
   434  
   435  func (s *snapshotSuite) TestIterSetIDoverride(c *check.C) {
   436  	if os.Geteuid() == 0 {
   437  		c.Skip("this test cannot run as root (runuser will fail)")
   438  	}
   439  	logger.SimpleSetup()
   440  
   441  	epoch := snap.E("42*")
   442  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   443  	cfg := map[string]interface{}{"some-setting": false}
   444  
   445  	shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"}, &backend.Flags{})
   446  	c.Assert(err, check.IsNil)
   447  	c.Check(shw.SetID, check.Equals, uint64(12))
   448  
   449  	snapshotPath := filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")
   450  	c.Check(backend.Filename(shw), check.Equals, snapshotPath)
   451  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
   452  
   453  	// rename the snapshot, verify that set id from the filename is used by the reader.
   454  	c.Assert(os.Rename(snapshotPath, filepath.Join(dirs.SnapshotsDir, "33_hello.zip")), check.IsNil)
   455  
   456  	var calledF int
   457  	f := func(snapshot *backend.Reader) error {
   458  		calledF++
   459  		c.Check(snapshot.SetID, check.Equals, uint64(uint(33)))
   460  		c.Check(snapshot.Snap, check.Equals, "hello-snap")
   461  		return nil
   462  	}
   463  
   464  	c.Assert(backend.Iter(context.Background(), f), check.IsNil)
   465  	c.Check(calledF, check.Equals, 1)
   466  }
   467  
   468  func (s *snapshotSuite) TestList(c *check.C) {
   469  	logbuf, restore := logger.MockLogger()
   470  	defer restore()
   471  	defer backend.MockOsOpen(func(string) (*os.File, error) { return new(os.File), nil })()
   472  
   473  	readNames := 0
   474  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   475  		readNames++
   476  		if readNames > 4 {
   477  			return nil, io.EOF
   478  		}
   479  		return []string{
   480  			fmt.Sprintf("%d_foo.zip", readNames),
   481  			fmt.Sprintf("%d_bar.zip", readNames),
   482  			fmt.Sprintf("%d_baz.zip", readNames),
   483  		}, nil
   484  	})()
   485  	defer backend.MockOpen(func(fn string, setID uint64) (*backend.Reader, error) {
   486  		var id uint64
   487  		var snapname string
   488  		c.Assert(strings.HasSuffix(fn, ".zip"), check.Equals, true)
   489  		fn = strings.TrimSuffix(filepath.Base(fn), ".zip")
   490  		_, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname)
   491  		c.Assert(err, check.IsNil, check.Commentf(fn))
   492  		f, err := os.Open(os.DevNull)
   493  		c.Assert(err, check.IsNil, check.Commentf(fn))
   494  		return &backend.Reader{
   495  			File: f,
   496  			Snapshot: client.Snapshot{
   497  				SetID:    id,
   498  				Snap:     snapname,
   499  				SnapID:   "id-for-" + snapname,
   500  				Version:  "v1.0-" + snapname,
   501  				Revision: snap.R(int(id)),
   502  			},
   503  		}, nil
   504  	})()
   505  
   506  	type tableT struct {
   507  		setID     uint64
   508  		snapnames []string
   509  		numSets   int
   510  		numShots  int
   511  		predicate func(*client.Snapshot) bool
   512  	}
   513  	table := []tableT{
   514  		{0, nil, 4, 12, nil},
   515  		{0, []string{"foo"}, 4, 4, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" }},
   516  		{1, nil, 1, 3, func(snapshot *client.Snapshot) bool { return snapshot.SetID == 1 }},
   517  		{2, []string{"bar"}, 1, 1, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "bar" && snapshot.SetID == 2 }},
   518  		{0, []string{"foo", "bar"}, 4, 8, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" || snapshot.Snap == "bar" }},
   519  	}
   520  
   521  	for i, t := range table {
   522  		comm := check.Commentf("%d: %d/%v", i, t.setID, t.snapnames)
   523  		// reset
   524  		readNames = 0
   525  		logbuf.Reset()
   526  
   527  		sets, err := backend.List(context.Background(), t.setID, t.snapnames)
   528  		c.Check(err, check.IsNil, comm)
   529  		c.Check(readNames, check.Equals, 5, comm)
   530  		c.Check(logbuf.String(), check.Equals, "", comm)
   531  		c.Check(sets, check.HasLen, t.numSets, comm)
   532  		nShots := 0
   533  		fnTpl := filepath.Join(dirs.SnapshotsDir, "%d_%s_%s_%s.zip")
   534  		for j, ss := range sets {
   535  			for k, snapshot := range ss.Snapshots {
   536  				comm := check.Commentf("%d: %d/%v #%d/%d", i, t.setID, t.snapnames, j, k)
   537  				if t.predicate != nil {
   538  					c.Check(t.predicate(snapshot), check.Equals, true, comm)
   539  				}
   540  				nShots++
   541  				fn := fmt.Sprintf(fnTpl, snapshot.SetID, snapshot.Snap, snapshot.Version, snapshot.Revision)
   542  				c.Check(backend.Filename(snapshot), check.Equals, fn, comm)
   543  				c.Check(snapshot.SnapID, check.Equals, "id-for-"+snapshot.Snap)
   544  			}
   545  		}
   546  		c.Check(nShots, check.Equals, t.numShots)
   547  	}
   548  }
   549  
   550  func (s *snapshotSuite) TestAddDirToZipBails(c *check.C) {
   551  	snapshot := &client.Snapshot{SetID: 42, Snap: "a-snap"}
   552  	buf, restore := logger.MockLogger()
   553  	defer restore()
   554  	// note as the zip is nil this would panic if it didn't bail
   555  	c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", filepath.Join(s.root, "nonexistent")), check.IsNil)
   556  	// no log for the non-existent case
   557  	c.Check(buf.String(), check.Equals, "")
   558  	buf.Reset()
   559  	c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", "/etc/passwd"), check.IsNil)
   560  	c.Check(buf.String(), check.Matches, "(?m).* is not a directory.")
   561  }
   562  
   563  func (s *snapshotSuite) TestAddDirToZipTarFails(c *check.C) {
   564  	d := filepath.Join(s.root, "foo")
   565  	c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil)
   566  	c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil)
   567  
   568  	ctx, cancel := context.WithCancel(context.Background())
   569  	cancel()
   570  
   571  	var buf bytes.Buffer
   572  	z := zip.NewWriter(&buf)
   573  	c.Assert(backend.AddDirToZip(ctx, nil, z, "", "an/entry", d), check.ErrorMatches, ".* context canceled")
   574  }
   575  
   576  func (s *snapshotSuite) TestAddDirToZip(c *check.C) {
   577  	d := filepath.Join(s.root, "foo")
   578  	c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil)
   579  	c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil)
   580  	c.Assert(ioutil.WriteFile(filepath.Join(d, "bar", "baz"), []byte("hello\n"), 0644), check.IsNil)
   581  
   582  	var buf bytes.Buffer
   583  	z := zip.NewWriter(&buf)
   584  	snapshot := &client.Snapshot{
   585  		SHA3_384: map[string]string{},
   586  	}
   587  	c.Assert(backend.AddDirToZip(context.Background(), snapshot, z, "", "an/entry", d), check.IsNil)
   588  	z.Close() // write out the central directory
   589  
   590  	c.Check(snapshot.SHA3_384, check.HasLen, 1)
   591  	c.Check(snapshot.SHA3_384["an/entry"], check.HasLen, 96)
   592  	c.Check(snapshot.Size > 0, check.Equals, true) // actual size most likely system-dependent
   593  	br := bytes.NewReader(buf.Bytes())
   594  	r, err := zip.NewReader(br, int64(br.Len()))
   595  	c.Assert(err, check.IsNil)
   596  	c.Check(r.File, check.HasLen, 1)
   597  	c.Check(r.File[0].Name, check.Equals, "an/entry")
   598  }
   599  
   600  func (s *snapshotSuite) TestHappyRoundtrip(c *check.C) {
   601  	s.testHappyRoundtrip(c, "marker", false)
   602  }
   603  
   604  func (s *snapshotSuite) TestHappyRoundtripAutomaticSnapshot(c *check.C) {
   605  	s.testHappyRoundtrip(c, "marker", true)
   606  }
   607  
   608  func (s *snapshotSuite) TestHappyRoundtripNoCommon(c *check.C) {
   609  	for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   610  		if _, d := filepath.Split(t.dir); d == "common" {
   611  			c.Assert(os.RemoveAll(t.dir), check.IsNil)
   612  		}
   613  	}
   614  	s.testHappyRoundtrip(c, "marker", false)
   615  }
   616  
   617  func (s *snapshotSuite) TestHappyRoundtripNoRev(c *check.C) {
   618  	for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   619  		if _, d := filepath.Split(t.dir); d == "42" {
   620  			c.Assert(os.RemoveAll(t.dir), check.IsNil)
   621  		}
   622  	}
   623  	s.testHappyRoundtrip(c, "../common/marker", false)
   624  }
   625  
   626  func (s *snapshotSuite) testHappyRoundtrip(c *check.C, marker string, auto bool) {
   627  	if os.Geteuid() == 0 {
   628  		c.Skip("this test cannot run as root (runuser will fail)")
   629  	}
   630  	logger.SimpleSetup()
   631  
   632  	epoch := snap.E("42*")
   633  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   634  	cfg := map[string]interface{}{"some-setting": false}
   635  	shID := uint64(12)
   636  
   637  	shw, err := backend.Save(context.TODO(), shID, info, cfg, []string{"snapuser"}, &backend.Flags{Auto: auto})
   638  	c.Assert(err, check.IsNil)
   639  	c.Check(shw.SetID, check.Equals, shID)
   640  	c.Check(shw.Snap, check.Equals, info.InstanceName())
   641  	c.Check(shw.SnapID, check.Equals, info.SnapID)
   642  	c.Check(shw.Version, check.Equals, info.Version)
   643  	c.Check(shw.Epoch, check.DeepEquals, epoch)
   644  	c.Check(shw.Revision, check.Equals, info.Revision)
   645  	c.Check(shw.Conf, check.DeepEquals, cfg)
   646  	c.Check(shw.Auto, check.Equals, auto)
   647  	c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   648  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
   649  
   650  	shs, err := backend.List(context.TODO(), 0, nil)
   651  	c.Assert(err, check.IsNil)
   652  	c.Assert(shs, check.HasLen, 1)
   653  	c.Assert(shs[0].Snapshots, check.HasLen, 1)
   654  
   655  	shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID)
   656  	c.Assert(err, check.IsNil)
   657  	defer shr.Close()
   658  
   659  	for label, sh := range map[string]*client.Snapshot{"open": &shr.Snapshot, "list": shs[0].Snapshots[0]} {
   660  		comm := check.Commentf("%q", label)
   661  		c.Check(sh.SetID, check.Equals, shID, comm)
   662  		c.Check(sh.Snap, check.Equals, info.InstanceName(), comm)
   663  		c.Check(sh.SnapID, check.Equals, info.SnapID, comm)
   664  		c.Check(sh.Version, check.Equals, info.Version, comm)
   665  		c.Check(sh.Epoch, check.DeepEquals, epoch)
   666  		c.Check(sh.Revision, check.Equals, info.Revision, comm)
   667  		c.Check(sh.Conf, check.DeepEquals, cfg, comm)
   668  		c.Check(sh.SHA3_384, check.DeepEquals, shw.SHA3_384, comm)
   669  		c.Check(sh.Auto, check.Equals, auto)
   670  	}
   671  	c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   672  	c.Check(shr.Check(context.TODO(), nil), check.IsNil)
   673  
   674  	newroot := c.MkDir()
   675  	c.Assert(os.MkdirAll(filepath.Join(newroot, "home/snapuser"), 0755), check.IsNil)
   676  	dirs.SetRootDir(newroot)
   677  
   678  	var diff = func() *exec.Cmd {
   679  		cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot)
   680  		// cmd.Stdout = os.Stdout
   681  		// cmd.Stderr = os.Stderr
   682  		return cmd
   683  	}
   684  
   685  	for i := 0; i < 3; i++ {
   686  		comm := check.Commentf("%d", i)
   687  		// sanity check
   688  		c.Check(diff().Run(), check.NotNil, comm)
   689  
   690  		// restore leaves things like they were (again and again)
   691  		rs, err := shr.Restore(context.TODO(), snap.R(0), nil, logger.Debugf)
   692  		c.Assert(err, check.IsNil, comm)
   693  		rs.Cleanup()
   694  		c.Check(diff().Run(), check.IsNil, comm)
   695  
   696  		// dirty it -> no longer like it was
   697  		c.Check(ioutil.WriteFile(filepath.Join(info.DataDir(), marker), []byte("scribble\n"), 0644), check.IsNil, comm)
   698  	}
   699  }
   700  
   701  func (s *snapshotSuite) TestOpenSetIDoverride(c *check.C) {
   702  	if os.Geteuid() == 0 {
   703  		c.Skip("this test cannot run as root (runuser will fail)")
   704  	}
   705  	logger.SimpleSetup()
   706  
   707  	epoch := snap.E("42*")
   708  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   709  	cfg := map[string]interface{}{"some-setting": false}
   710  
   711  	shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"}, &backend.Flags{})
   712  	c.Assert(err, check.IsNil)
   713  	c.Check(shw.SetID, check.Equals, uint64(12))
   714  
   715  	c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   716  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
   717  
   718  	shr, err := backend.Open(backend.Filename(shw), 99)
   719  	c.Assert(err, check.IsNil)
   720  	defer shr.Close()
   721  
   722  	c.Check(shr.SetID, check.Equals, uint64(99))
   723  }
   724  
   725  func (s *snapshotSuite) TestRestoreRoundtripDifferentRevision(c *check.C) {
   726  	if os.Geteuid() == 0 {
   727  		c.Skip("this test cannot run as root (runuser will fail)")
   728  	}
   729  	logger.SimpleSetup()
   730  
   731  	epoch := snap.E("42*")
   732  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   733  	shID := uint64(12)
   734  
   735  	shw, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"}, nil)
   736  	c.Assert(err, check.IsNil)
   737  	c.Check(shw.Revision, check.Equals, info.Revision)
   738  
   739  	shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID)
   740  	c.Assert(err, check.IsNil)
   741  	defer shr.Close()
   742  
   743  	c.Check(shr.Revision, check.Equals, info.Revision)
   744  	c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   745  
   746  	// move the expected data to its expected place
   747  	for _, dir := range []string{
   748  		filepath.Join(s.root, "home", "snapuser", "snap", "hello-snap"),
   749  		filepath.Join(dirs.SnapDataDir, "hello-snap"),
   750  	} {
   751  		c.Check(os.Rename(filepath.Join(dir, "42"), filepath.Join(dir, "17")), check.IsNil)
   752  	}
   753  
   754  	newroot := c.MkDir()
   755  	c.Assert(os.MkdirAll(filepath.Join(newroot, "home", "snapuser"), 0755), check.IsNil)
   756  	dirs.SetRootDir(newroot)
   757  
   758  	var diff = func() *exec.Cmd {
   759  		cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot)
   760  		// cmd.Stdout = os.Stdout
   761  		// cmd.Stderr = os.Stderr
   762  		return cmd
   763  	}
   764  
   765  	// sanity check
   766  	c.Check(diff().Run(), check.NotNil)
   767  
   768  	// restore leaves things like they were, but in the new dir
   769  	rs, err := shr.Restore(context.TODO(), snap.R("17"), nil, logger.Debugf)
   770  	c.Assert(err, check.IsNil)
   771  	rs.Cleanup()
   772  	c.Check(diff().Run(), check.IsNil)
   773  }
   774  
   775  func (s *snapshotSuite) TestPickUserWrapperRunuser(c *check.C) {
   776  	n := 0
   777  	defer backend.MockExecLookPath(func(s string) (string, error) {
   778  		n++
   779  		if s != "runuser" {
   780  			c.Fatalf(`expected to get "runuser", got %q`, s)
   781  		}
   782  		return "/sbin/runuser", nil
   783  	})()
   784  
   785  	c.Check(backend.PickUserWrapper(), check.Equals, "/sbin/runuser")
   786  	c.Check(n, check.Equals, 1)
   787  }
   788  
   789  func (s *snapshotSuite) TestPickUserWrapperSudo(c *check.C) {
   790  	n := 0
   791  	defer backend.MockExecLookPath(func(s string) (string, error) {
   792  		n++
   793  		if n == 1 {
   794  			if s != "runuser" {
   795  				c.Fatalf(`expected to get "runuser" first, got %q`, s)
   796  			}
   797  			return "", errors.New("no such thing")
   798  		}
   799  		if s != "sudo" {
   800  			c.Fatalf(`expected to get "sudo" next, got %q`, s)
   801  		}
   802  		return "/usr/bin/sudo", nil
   803  	})()
   804  
   805  	c.Check(backend.PickUserWrapper(), check.Equals, "/usr/bin/sudo")
   806  	c.Check(n, check.Equals, 2)
   807  }
   808  
   809  func (s *snapshotSuite) TestPickUserWrapperNothing(c *check.C) {
   810  	n := 0
   811  	defer backend.MockExecLookPath(func(s string) (string, error) {
   812  		n++
   813  		return "", errors.New("no such thing")
   814  	})()
   815  
   816  	c.Check(backend.PickUserWrapper(), check.Equals, "")
   817  	c.Check(n, check.Equals, 2)
   818  }
   819  
   820  func (s *snapshotSuite) TestMaybeRunuserHappyRunuser(c *check.C) {
   821  	uid := sys.UserID(0)
   822  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   823  	defer backend.SetUserWrapper("/sbin/runuser")()
   824  	logbuf, restore := logger.MockLogger()
   825  	defer restore()
   826  
   827  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   828  		Path: "/sbin/runuser",
   829  		Args: []string{"/sbin/runuser", "-u", "test", "--", "tar", "--bar"},
   830  	})
   831  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   832  		Path: s.tarPath,
   833  		Args: []string{"tar", "--bar"},
   834  	})
   835  	uid = 42
   836  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   837  		Path: s.tarPath,
   838  		Args: []string{"tar", "--bar"},
   839  	})
   840  	c.Check(logbuf.String(), check.Equals, "")
   841  }
   842  
   843  func (s *snapshotSuite) TestMaybeRunuserHappySudo(c *check.C) {
   844  	uid := sys.UserID(0)
   845  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   846  	defer backend.SetUserWrapper("/usr/bin/sudo")()
   847  	logbuf, restore := logger.MockLogger()
   848  	defer restore()
   849  
   850  	cmd := backend.TarAsUser("test", "--bar")
   851  	c.Check(cmd, check.DeepEquals, &exec.Cmd{
   852  		Path: "/usr/bin/sudo",
   853  		Args: []string{"/usr/bin/sudo", "-u", "test", "--", "tar", "--bar"},
   854  	})
   855  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   856  		Path: s.tarPath,
   857  		Args: []string{"tar", "--bar"},
   858  	})
   859  	uid = 42
   860  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   861  		Path: s.tarPath,
   862  		Args: []string{"tar", "--bar"},
   863  	})
   864  	c.Check(logbuf.String(), check.Equals, "")
   865  }
   866  
   867  func (s *snapshotSuite) TestMaybeRunuserNoHappy(c *check.C) {
   868  	uid := sys.UserID(0)
   869  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   870  	defer backend.SetUserWrapper("")()
   871  	logbuf, restore := logger.MockLogger()
   872  	defer restore()
   873  
   874  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   875  		Path: s.tarPath,
   876  		Args: []string{"tar", "--bar"},
   877  	})
   878  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   879  		Path: s.tarPath,
   880  		Args: []string{"tar", "--bar"},
   881  	})
   882  	uid = 42
   883  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   884  		Path: s.tarPath,
   885  		Args: []string{"tar", "--bar"},
   886  	})
   887  	c.Check(strings.TrimSpace(logbuf.String()), check.Matches, ".* No user wrapper found.*")
   888  }
   889  
   890  func (s *snapshotSuite) TestEstimateSnapshotSize(c *check.C) {
   891  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
   892  		return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil
   893  	})
   894  	defer restore()
   895  
   896  	var info = &snap.Info{
   897  		SuggestedName: "foo",
   898  		SideInfo: snap.SideInfo{
   899  			Revision: snap.R(7),
   900  		},
   901  	}
   902  
   903  	snapData := []string{
   904  		"/var/snap/foo/7/somedatadir",
   905  		"/var/snap/foo/7/otherdata",
   906  		"/var/snap/foo/7",
   907  		"/var/snap/foo/common",
   908  		"/var/snap/foo/common/a",
   909  		"/home/user1/snap/foo/7/somedata",
   910  		"/home/user1/snap/foo/common",
   911  	}
   912  	var data []byte
   913  	var expected int
   914  	for _, d := range snapData {
   915  		data = append(data, 0)
   916  		expected += len(data)
   917  		c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil)
   918  		c.Assert(ioutil.WriteFile(filepath.Join(s.root, d, "somfile"), data, 0644), check.IsNil)
   919  	}
   920  
   921  	sz, err := backend.EstimateSnapshotSize(info, nil)
   922  	c.Assert(err, check.IsNil)
   923  	c.Check(sz, check.Equals, uint64(expected))
   924  }
   925  
   926  func (s *snapshotSuite) TestEstimateSnapshotSizeEmpty(c *check.C) {
   927  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
   928  		return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil
   929  	})
   930  	defer restore()
   931  
   932  	var info = &snap.Info{
   933  		SuggestedName: "foo",
   934  		SideInfo: snap.SideInfo{
   935  			Revision: snap.R(7),
   936  		},
   937  	}
   938  
   939  	snapData := []string{
   940  		"/var/snap/foo/common",
   941  		"/var/snap/foo/7",
   942  		"/home/user1/snap/foo/7",
   943  		"/home/user1/snap/foo/common",
   944  	}
   945  	for _, d := range snapData {
   946  		c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil)
   947  	}
   948  
   949  	sz, err := backend.EstimateSnapshotSize(info, nil)
   950  	c.Assert(err, check.IsNil)
   951  	c.Check(sz, check.Equals, uint64(0))
   952  }
   953  
   954  func (s *snapshotSuite) TestEstimateSnapshotPassesUsernames(c *check.C) {
   955  	var gotUsernames []string
   956  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
   957  		gotUsernames = usernames
   958  		return nil, nil
   959  	})
   960  	defer restore()
   961  
   962  	var info = &snap.Info{
   963  		SuggestedName: "foo",
   964  		SideInfo: snap.SideInfo{
   965  			Revision: snap.R(7),
   966  		},
   967  	}
   968  
   969  	_, err := backend.EstimateSnapshotSize(info, []string{"user1", "user2"})
   970  	c.Assert(err, check.IsNil)
   971  	c.Check(gotUsernames, check.DeepEquals, []string{"user1", "user2"})
   972  }
   973  
   974  func (s *snapshotSuite) TestEstimateSnapshotSizeNotDataDirs(c *check.C) {
   975  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
   976  		return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil
   977  	})
   978  	defer restore()
   979  
   980  	var info = &snap.Info{
   981  		SuggestedName: "foo",
   982  		SideInfo:      snap.SideInfo{Revision: snap.R(7)},
   983  	}
   984  
   985  	sz, err := backend.EstimateSnapshotSize(info, nil)
   986  	c.Assert(err, check.IsNil)
   987  	c.Check(sz, check.Equals, uint64(0))
   988  }
   989  func (s *snapshotSuite) TestExportTwice(c *check.C) {
   990  	// use mocking done in snapshotSuite.SetUpTest
   991  	info := &snap.Info{
   992  		SideInfo: snap.SideInfo{
   993  			RealName: "hello-snap",
   994  			Revision: snap.R(42),
   995  			SnapID:   "hello-id",
   996  		},
   997  		Version: "v1.33",
   998  	}
   999  	// create a snapshot
  1000  	shID := uint64(12)
  1001  	_, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"}, &backend.Flags{})
  1002  	c.Check(err, check.IsNil)
  1003  
  1004  	// num_files + export.json + footer
  1005  	expectedSize := int64(4*512 + 1024 + 2*512)
  1006  	// do on export at the start of the epoch
  1007  	restore := backend.MockTimeNow(func() time.Time { return time.Time{} })
  1008  	defer restore()
  1009  	// export once
  1010  	buf := bytes.NewBuffer(nil)
  1011  	ctx := context.Background()
  1012  	se, err := backend.NewSnapshotExport(ctx, shID)
  1013  	c.Check(err, check.IsNil)
  1014  	err = se.Init()
  1015  	c.Assert(err, check.IsNil)
  1016  	c.Check(se.Size(), check.Equals, expectedSize)
  1017  	// and we can stream the data
  1018  	err = se.StreamTo(buf)
  1019  	c.Assert(err, check.IsNil)
  1020  	c.Check(buf.Len(), check.Equals, int(expectedSize))
  1021  
  1022  	// and again to ensure size does not change when exported again
  1023  	//
  1024  	// Note that moving beyond year 2242 will change the tar format
  1025  	// used by the go internal tar and that will make the size actually
  1026  	// change.
  1027  	restore = backend.MockTimeNow(func() time.Time { return time.Date(2242, 1, 1, 12, 0, 0, 0, time.UTC) })
  1028  	defer restore()
  1029  	se2, err := backend.NewSnapshotExport(ctx, shID)
  1030  	c.Check(err, check.IsNil)
  1031  	err = se2.Init()
  1032  	c.Assert(err, check.IsNil)
  1033  	c.Check(se2.Size(), check.Equals, expectedSize)
  1034  	// and we can stream the data
  1035  	buf.Reset()
  1036  	err = se2.StreamTo(buf)
  1037  	c.Assert(err, check.IsNil)
  1038  	c.Check(buf.Len(), check.Equals, int(expectedSize))
  1039  }
  1040  
  1041  func (s *snapshotSuite) TestExportUnhappy(c *check.C) {
  1042  	se, err := backend.NewSnapshotExport(context.Background(), 5)
  1043  	c.Assert(err, check.ErrorMatches, "no snapshot data found for 5")
  1044  	c.Assert(se, check.IsNil)
  1045  }