github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/snapshotstate/backend/backend_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package backend_test
    21  
    22  import (
    23  	"archive/tar"
    24  	"archive/zip"
    25  	"bytes"
    26  	"context"
    27  	"crypto"
    28  	"crypto/sha256"
    29  	"encoding/json"
    30  	"errors"
    31  	"fmt"
    32  	"io"
    33  	"io/ioutil"
    34  	"os"
    35  	"os/exec"
    36  	"os/user"
    37  	"path"
    38  	"path/filepath"
    39  	"sort"
    40  	"strings"
    41  	"testing"
    42  	"time"
    43  
    44  	"gopkg.in/check.v1"
    45  
    46  	"github.com/snapcore/snapd/client"
    47  	"github.com/snapcore/snapd/dirs"
    48  	"github.com/snapcore/snapd/logger"
    49  	"github.com/snapcore/snapd/osutil"
    50  	"github.com/snapcore/snapd/osutil/sys"
    51  	"github.com/snapcore/snapd/overlord/snapshotstate/backend"
    52  	"github.com/snapcore/snapd/snap"
    53  	"github.com/snapcore/snapd/testutil"
    54  )
    55  
    56  type snapshotSuite struct {
    57  	root      string
    58  	restore   []func()
    59  	tarPath   string
    60  	isTesting bool
    61  }
    62  
    63  // silly wrappers to get better failure messages
    64  type isTestingSuite struct{ snapshotSuite }
    65  type noTestingSuite struct{ snapshotSuite }
    66  
    67  var _ = check.Suite(&isTestingSuite{snapshotSuite{isTesting: true}})
    68  var _ = check.Suite(&noTestingSuite{snapshotSuite{isTesting: false}})
    69  
    70  // tie gocheck into testing
    71  func TestSnapshot(t *testing.T) { check.TestingT(t) }
    72  
    73  type tableT struct {
    74  	dir     string
    75  	name    string
    76  	content string
    77  }
    78  
    79  func table(si snap.PlaceInfo, homeDir string) []tableT {
    80  	return []tableT{
    81  		{
    82  			dir:     si.DataDir(),
    83  			name:    "foo",
    84  			content: "versioned system canary\n",
    85  		}, {
    86  			dir:     si.CommonDataDir(),
    87  			name:    "bar",
    88  			content: "common system canary\n",
    89  		}, {
    90  			dir:     si.UserDataDir(homeDir),
    91  			name:    "ufoo",
    92  			content: "versioned user canary\n",
    93  		}, {
    94  			dir:     si.UserCommonDataDir(homeDir),
    95  			name:    "ubar",
    96  			content: "common user canary\n",
    97  		},
    98  	}
    99  }
   100  
   101  func (s *snapshotSuite) SetUpTest(c *check.C) {
   102  	s.root = c.MkDir()
   103  
   104  	dirs.SetRootDir(s.root)
   105  
   106  	si := snap.MinimalPlaceInfo("hello-snap", snap.R(42))
   107  
   108  	for _, t := range table(si, filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   109  		c.Check(os.MkdirAll(t.dir, 0755), check.IsNil)
   110  		c.Check(ioutil.WriteFile(filepath.Join(t.dir, t.name), []byte(t.content), 0644), check.IsNil)
   111  	}
   112  
   113  	cur, err := user.Current()
   114  	c.Assert(err, check.IsNil)
   115  
   116  	s.restore = append(s.restore, backend.MockUserLookup(func(username string) (*user.User, error) {
   117  		if username != "snapuser" {
   118  			return nil, user.UnknownUserError(username)
   119  		}
   120  		rv := *cur
   121  		rv.Username = username
   122  		rv.HomeDir = filepath.Join(dirs.GlobalRootDir, "home/snapuser")
   123  		return &rv, nil
   124  	}),
   125  		backend.MockIsTesting(s.isTesting),
   126  	)
   127  
   128  	s.tarPath, err = exec.LookPath("tar")
   129  	c.Assert(err, check.IsNil)
   130  }
   131  
   132  func (s *snapshotSuite) TearDownTest(c *check.C) {
   133  	dirs.SetRootDir("")
   134  	for _, restore := range s.restore {
   135  		restore()
   136  	}
   137  }
   138  
   139  func hashkeys(snapshot *client.Snapshot) (keys []string) {
   140  	for k := range snapshot.SHA3_384 {
   141  		keys = append(keys, k)
   142  	}
   143  	sort.Strings(keys)
   144  
   145  	return keys
   146  }
   147  
   148  func (s *snapshotSuite) TestLastSnapshotID(c *check.C) {
   149  	// LastSnapshotSetID is happy without any snapshots
   150  	setID, err := backend.LastSnapshotSetID()
   151  	c.Assert(err, check.IsNil)
   152  	c.Check(setID, check.Equals, uint64(0))
   153  
   154  	// create snapshots dir and dummy snapshots
   155  	os.MkdirAll(dirs.SnapshotsDir, os.ModePerm)
   156  	for _, name := range []string{
   157  		"9_some-snap-1.zip", "1234_not-a-snapshot", "12_other-snap.zip", "3_foo.zip",
   158  	} {
   159  		c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, name), []byte{}, 0644), check.IsNil)
   160  	}
   161  	setID, err = backend.LastSnapshotSetID()
   162  	c.Assert(err, check.IsNil)
   163  	c.Check(setID, check.Equals, uint64(12))
   164  }
   165  
   166  func (s *snapshotSuite) TestLastSnapshotIDErrorOnDirNames(c *check.C) {
   167  	// we need snapshots dir, otherwise LastSnapshotSetID exits early.
   168  	c.Assert(os.MkdirAll(dirs.SnapshotsDir, os.ModePerm), check.IsNil)
   169  
   170  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   171  		return nil, fmt.Errorf("fail")
   172  	})()
   173  	setID, err := backend.LastSnapshotSetID()
   174  	c.Assert(err, check.ErrorMatches, "fail")
   175  	c.Check(setID, check.Equals, uint64(0))
   176  }
   177  
   178  func (s *snapshotSuite) TestIsSnapshotFilename(c *check.C) {
   179  	tests := []struct {
   180  		name  string
   181  		valid bool
   182  		setID uint64
   183  	}{
   184  		{"1_foo.zip", true, 1},
   185  		{"14_hello-world_6.4_29.zip", true, 14},
   186  		{"1_.zip", false, 0},
   187  		{"1_foo.zip.bak", false, 0},
   188  		{"foo_1_foo.zip", false, 0},
   189  		{"foo_bar_baz.zip", false, 0},
   190  		{"", false, 0},
   191  		{"1_", false, 0},
   192  	}
   193  
   194  	for _, t := range tests {
   195  		ok, setID := backend.IsSnapshotFilename(t.name)
   196  		c.Check(ok, check.Equals, t.valid, check.Commentf("fail: %s", t.name))
   197  		c.Check(setID, check.Equals, t.setID, check.Commentf("fail: %s", t.name))
   198  	}
   199  }
   200  
   201  func (s *snapshotSuite) TestIterBailsIfContextDone(c *check.C) {
   202  	ctx, cancel := context.WithCancel(context.Background())
   203  	cancel()
   204  	triedToOpenDir := false
   205  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   206  		triedToOpenDir = true
   207  		return nil, nil // deal with it
   208  	})()
   209  
   210  	err := backend.Iter(ctx, nil)
   211  	c.Check(err, check.Equals, context.Canceled)
   212  	c.Check(triedToOpenDir, check.Equals, false)
   213  }
   214  
   215  func (s *snapshotSuite) TestIterBailsIfContextDoneMidway(c *check.C) {
   216  	ctx, cancel := context.WithCancel(context.Background())
   217  	triedToOpenDir := false
   218  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   219  		triedToOpenDir = true
   220  		return os.Open(os.DevNull)
   221  	})()
   222  	readNames := 0
   223  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   224  		readNames++
   225  		cancel()
   226  		return []string{"hello"}, nil
   227  	})()
   228  	triedToOpenSnapshot := false
   229  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   230  		triedToOpenSnapshot = true
   231  		return nil, nil
   232  	})()
   233  
   234  	err := backend.Iter(ctx, nil)
   235  	c.Check(err, check.Equals, context.Canceled)
   236  	c.Check(triedToOpenDir, check.Equals, true)
   237  	// bails as soon as
   238  	c.Check(readNames, check.Equals, 1)
   239  	c.Check(triedToOpenSnapshot, check.Equals, false)
   240  }
   241  
   242  func (s *snapshotSuite) TestIterReturnsOkIfSnapshotsDirNonexistent(c *check.C) {
   243  	triedToOpenDir := false
   244  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   245  		triedToOpenDir = true
   246  		return nil, os.ErrNotExist
   247  	})()
   248  
   249  	err := backend.Iter(context.Background(), nil)
   250  	c.Check(err, check.IsNil)
   251  	c.Check(triedToOpenDir, check.Equals, true)
   252  }
   253  
   254  func (s *snapshotSuite) TestIterBailsIfSnapshotsDirFails(c *check.C) {
   255  	triedToOpenDir := false
   256  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   257  		triedToOpenDir = true
   258  		return nil, os.ErrInvalid
   259  	})()
   260  
   261  	err := backend.Iter(context.Background(), nil)
   262  	c.Check(err, check.ErrorMatches, "cannot open snapshots directory: invalid argument")
   263  	c.Check(triedToOpenDir, check.Equals, true)
   264  }
   265  
   266  func (s *snapshotSuite) TestIterWarnsOnOpenErrorIfSnapshotNil(c *check.C) {
   267  	logbuf, restore := logger.MockLogger()
   268  	defer restore()
   269  	triedToOpenDir := false
   270  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   271  		triedToOpenDir = true
   272  		return new(os.File), nil
   273  	})()
   274  	readNames := 0
   275  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   276  		readNames++
   277  		if readNames > 1 {
   278  			return nil, io.EOF
   279  		}
   280  		return []string{"1_hello.zip"}, nil
   281  	})()
   282  	triedToOpenSnapshot := false
   283  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   284  		triedToOpenSnapshot = true
   285  		return nil, os.ErrInvalid
   286  	})()
   287  
   288  	calledF := false
   289  	f := func(snapshot *backend.Reader) error {
   290  		calledF = true
   291  		return nil
   292  	}
   293  
   294  	err := backend.Iter(context.Background(), f)
   295  	// snapshot open errors are not failures:
   296  	c.Check(err, check.IsNil)
   297  	c.Check(triedToOpenDir, check.Equals, true)
   298  	c.Check(readNames, check.Equals, 2)
   299  	c.Check(triedToOpenSnapshot, check.Equals, true)
   300  	c.Check(logbuf.String(), check.Matches, `(?m).* Cannot open snapshot "1_hello.zip": invalid argument.`)
   301  	c.Check(calledF, check.Equals, false)
   302  }
   303  
   304  func (s *snapshotSuite) TestIterCallsFuncIfSnapshotNotNil(c *check.C) {
   305  	logbuf, restore := logger.MockLogger()
   306  	defer restore()
   307  	triedToOpenDir := false
   308  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   309  		triedToOpenDir = true
   310  		return new(os.File), nil
   311  	})()
   312  	readNames := 0
   313  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   314  		readNames++
   315  		if readNames > 1 {
   316  			return nil, io.EOF
   317  		}
   318  		return []string{"1_hello.zip"}, nil
   319  	})()
   320  	triedToOpenSnapshot := false
   321  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   322  		triedToOpenSnapshot = true
   323  		// NOTE non-nil reader, and error, returned
   324  		r := backend.Reader{}
   325  		r.SetID = 1
   326  		r.Broken = "xyzzy"
   327  		return &r, os.ErrInvalid
   328  	})()
   329  
   330  	calledF := false
   331  	f := func(snapshot *backend.Reader) error {
   332  		c.Check(snapshot.Broken, check.Equals, "xyzzy")
   333  		calledF = true
   334  		return nil
   335  	}
   336  
   337  	err := backend.Iter(context.Background(), f)
   338  	// snapshot open errors are not failures:
   339  	c.Check(err, check.IsNil)
   340  	c.Check(triedToOpenDir, check.Equals, true)
   341  	c.Check(readNames, check.Equals, 2)
   342  	c.Check(triedToOpenSnapshot, check.Equals, true)
   343  	c.Check(logbuf.String(), check.Equals, "")
   344  	c.Check(calledF, check.Equals, true)
   345  }
   346  
   347  func (s *snapshotSuite) TestIterReportsCloseError(c *check.C) {
   348  	logbuf, restore := logger.MockLogger()
   349  	defer restore()
   350  	triedToOpenDir := false
   351  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   352  		triedToOpenDir = true
   353  		return new(os.File), nil
   354  	})()
   355  	readNames := 0
   356  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   357  		readNames++
   358  		if readNames > 1 {
   359  			return nil, io.EOF
   360  		}
   361  		return []string{"42_hello.zip"}, nil
   362  	})()
   363  	triedToOpenSnapshot := false
   364  	defer backend.MockOpen(func(string, uint64) (*backend.Reader, error) {
   365  		triedToOpenSnapshot = true
   366  		r := backend.Reader{}
   367  		r.SetID = 42
   368  		return &r, nil
   369  	})()
   370  
   371  	calledF := false
   372  	f := func(snapshot *backend.Reader) error {
   373  		c.Check(snapshot.SetID, check.Equals, uint64(42))
   374  		calledF = true
   375  		return nil
   376  	}
   377  
   378  	err := backend.Iter(context.Background(), f)
   379  	// snapshot close errors _are_ failures (because they're completely unexpected):
   380  	c.Check(err, check.Equals, os.ErrInvalid)
   381  	c.Check(triedToOpenDir, check.Equals, true)
   382  	c.Check(readNames, check.Equals, 1) // never gets to read another one
   383  	c.Check(triedToOpenSnapshot, check.Equals, true)
   384  	c.Check(logbuf.String(), check.Equals, "")
   385  	c.Check(calledF, check.Equals, true)
   386  }
   387  
   388  func readerForFilename(fname string, c *check.C) *backend.Reader {
   389  	var snapname string
   390  	var id uint64
   391  	fn := strings.TrimSuffix(filepath.Base(fname), ".zip")
   392  	_, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname)
   393  	c.Assert(err, check.IsNil, check.Commentf(fn))
   394  	f, err := os.Open(os.DevNull)
   395  	c.Assert(err, check.IsNil, check.Commentf(fn))
   396  	return &backend.Reader{
   397  		File: f,
   398  		Snapshot: client.Snapshot{
   399  			SetID: id,
   400  			Snap:  snapname,
   401  		},
   402  	}
   403  }
   404  
   405  func (s *snapshotSuite) TestIterIgnoresSnapshotsWithInvalidNames(c *check.C) {
   406  	logbuf, restore := logger.MockLogger()
   407  	defer restore()
   408  
   409  	defer backend.MockOsOpen(func(string) (*os.File, error) {
   410  		return new(os.File), nil
   411  	})()
   412  	readNames := 0
   413  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   414  		readNames++
   415  		if readNames > 1 {
   416  			return nil, io.EOF
   417  		}
   418  		return []string{
   419  			"_foo.zip",
   420  			"43_bar.zip",
   421  			"foo_bar.zip",
   422  			"bar.",
   423  		}, nil
   424  	})()
   425  	defer backend.MockOpen(func(fname string, setID uint64) (*backend.Reader, error) {
   426  		return readerForFilename(fname, c), nil
   427  	})()
   428  
   429  	var calledF int
   430  	f := func(snapshot *backend.Reader) error {
   431  		calledF++
   432  		c.Check(snapshot.SetID, check.Equals, uint64(43))
   433  		return nil
   434  	}
   435  
   436  	err := backend.Iter(context.Background(), f)
   437  	c.Check(err, check.IsNil)
   438  	c.Check(logbuf.String(), check.Equals, "")
   439  	c.Check(calledF, check.Equals, 1)
   440  }
   441  
   442  func (s *snapshotSuite) TestIterSetIDoverride(c *check.C) {
   443  	if os.Geteuid() == 0 {
   444  		c.Skip("this test cannot run as root (runuser will fail)")
   445  	}
   446  	logger.SimpleSetup()
   447  
   448  	epoch := snap.E("42*")
   449  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   450  	cfg := map[string]interface{}{"some-setting": false}
   451  
   452  	shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"})
   453  	c.Assert(err, check.IsNil)
   454  	c.Check(shw.SetID, check.Equals, uint64(12))
   455  
   456  	snapshotPath := filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")
   457  	c.Check(backend.Filename(shw), check.Equals, snapshotPath)
   458  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
   459  
   460  	// rename the snapshot, verify that set id from the filename is used by the reader.
   461  	c.Assert(os.Rename(snapshotPath, filepath.Join(dirs.SnapshotsDir, "33_hello.zip")), check.IsNil)
   462  
   463  	var calledF int
   464  	f := func(snapshot *backend.Reader) error {
   465  		calledF++
   466  		c.Check(snapshot.SetID, check.Equals, uint64(uint(33)))
   467  		c.Check(snapshot.Snap, check.Equals, "hello-snap")
   468  		return nil
   469  	}
   470  
   471  	c.Assert(backend.Iter(context.Background(), f), check.IsNil)
   472  	c.Check(calledF, check.Equals, 1)
   473  }
   474  
   475  func (s *snapshotSuite) TestList(c *check.C) {
   476  	logbuf, restore := logger.MockLogger()
   477  	defer restore()
   478  	defer backend.MockOsOpen(func(string) (*os.File, error) { return new(os.File), nil })()
   479  
   480  	readNames := 0
   481  	defer backend.MockDirNames(func(*os.File, int) ([]string, error) {
   482  		readNames++
   483  		if readNames > 4 {
   484  			return nil, io.EOF
   485  		}
   486  		return []string{
   487  			fmt.Sprintf("%d_foo.zip", readNames),
   488  			fmt.Sprintf("%d_bar.zip", readNames),
   489  			fmt.Sprintf("%d_baz.zip", readNames),
   490  		}, nil
   491  	})()
   492  	defer backend.MockOpen(func(fn string, setID uint64) (*backend.Reader, error) {
   493  		var id uint64
   494  		var snapname string
   495  		c.Assert(strings.HasSuffix(fn, ".zip"), check.Equals, true)
   496  		fn = strings.TrimSuffix(filepath.Base(fn), ".zip")
   497  		_, err := fmt.Sscanf(fn, "%d_%s", &id, &snapname)
   498  		c.Assert(err, check.IsNil, check.Commentf(fn))
   499  		f, err := os.Open(os.DevNull)
   500  		c.Assert(err, check.IsNil, check.Commentf(fn))
   501  		return &backend.Reader{
   502  			File: f,
   503  			Snapshot: client.Snapshot{
   504  				SetID:    id,
   505  				Snap:     snapname,
   506  				SnapID:   "id-for-" + snapname,
   507  				Version:  "v1.0-" + snapname,
   508  				Revision: snap.R(int(id)),
   509  			},
   510  		}, nil
   511  	})()
   512  
   513  	type tableT struct {
   514  		setID     uint64
   515  		snapnames []string
   516  		numSets   int
   517  		numShots  int
   518  		predicate func(*client.Snapshot) bool
   519  	}
   520  	table := []tableT{
   521  		{0, nil, 4, 12, nil},
   522  		{0, []string{"foo"}, 4, 4, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" }},
   523  		{1, nil, 1, 3, func(snapshot *client.Snapshot) bool { return snapshot.SetID == 1 }},
   524  		{2, []string{"bar"}, 1, 1, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "bar" && snapshot.SetID == 2 }},
   525  		{0, []string{"foo", "bar"}, 4, 8, func(snapshot *client.Snapshot) bool { return snapshot.Snap == "foo" || snapshot.Snap == "bar" }},
   526  	}
   527  
   528  	for i, t := range table {
   529  		comm := check.Commentf("%d: %d/%v", i, t.setID, t.snapnames)
   530  		// reset
   531  		readNames = 0
   532  		logbuf.Reset()
   533  
   534  		sets, err := backend.List(context.Background(), t.setID, t.snapnames)
   535  		c.Check(err, check.IsNil, comm)
   536  		c.Check(readNames, check.Equals, 5, comm)
   537  		c.Check(logbuf.String(), check.Equals, "", comm)
   538  		c.Check(sets, check.HasLen, t.numSets, comm)
   539  		nShots := 0
   540  		fnTpl := filepath.Join(dirs.SnapshotsDir, "%d_%s_%s_%s.zip")
   541  		for j, ss := range sets {
   542  			for k, snapshot := range ss.Snapshots {
   543  				comm := check.Commentf("%d: %d/%v #%d/%d", i, t.setID, t.snapnames, j, k)
   544  				if t.predicate != nil {
   545  					c.Check(t.predicate(snapshot), check.Equals, true, comm)
   546  				}
   547  				nShots++
   548  				fn := fmt.Sprintf(fnTpl, snapshot.SetID, snapshot.Snap, snapshot.Version, snapshot.Revision)
   549  				c.Check(backend.Filename(snapshot), check.Equals, fn, comm)
   550  				c.Check(snapshot.SnapID, check.Equals, "id-for-"+snapshot.Snap)
   551  			}
   552  		}
   553  		c.Check(nShots, check.Equals, t.numShots)
   554  	}
   555  }
   556  
   557  func (s *snapshotSuite) TestAddDirToZipBails(c *check.C) {
   558  	snapshot := &client.Snapshot{SetID: 42, Snap: "a-snap"}
   559  	buf, restore := logger.MockLogger()
   560  	defer restore()
   561  	// note as the zip is nil this would panic if it didn't bail
   562  	c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", filepath.Join(s.root, "nonexistent")), check.IsNil)
   563  	// no log for the non-existent case
   564  	c.Check(buf.String(), check.Equals, "")
   565  	buf.Reset()
   566  	c.Check(backend.AddDirToZip(nil, snapshot, nil, "", "an/entry", "/etc/passwd"), check.IsNil)
   567  	c.Check(buf.String(), check.Matches, "(?m).* is not a directory.")
   568  }
   569  
   570  func (s *snapshotSuite) TestAddDirToZipTarFails(c *check.C) {
   571  	d := filepath.Join(s.root, "foo")
   572  	c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil)
   573  	c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil)
   574  
   575  	ctx, cancel := context.WithCancel(context.Background())
   576  	cancel()
   577  
   578  	var buf bytes.Buffer
   579  	z := zip.NewWriter(&buf)
   580  	c.Assert(backend.AddDirToZip(ctx, nil, z, "", "an/entry", d), check.ErrorMatches, ".* context canceled")
   581  }
   582  
   583  func (s *snapshotSuite) TestAddDirToZip(c *check.C) {
   584  	d := filepath.Join(s.root, "foo")
   585  	c.Assert(os.MkdirAll(filepath.Join(d, "bar"), 0755), check.IsNil)
   586  	c.Assert(os.MkdirAll(filepath.Join(s.root, "common"), 0755), check.IsNil)
   587  	c.Assert(ioutil.WriteFile(filepath.Join(d, "bar", "baz"), []byte("hello\n"), 0644), check.IsNil)
   588  
   589  	var buf bytes.Buffer
   590  	z := zip.NewWriter(&buf)
   591  	snapshot := &client.Snapshot{
   592  		SHA3_384: map[string]string{},
   593  	}
   594  	c.Assert(backend.AddDirToZip(context.Background(), snapshot, z, "", "an/entry", d), check.IsNil)
   595  	z.Close() // write out the central directory
   596  
   597  	c.Check(snapshot.SHA3_384, check.HasLen, 1)
   598  	c.Check(snapshot.SHA3_384["an/entry"], check.HasLen, 96)
   599  	c.Check(snapshot.Size > 0, check.Equals, true) // actual size most likely system-dependent
   600  	br := bytes.NewReader(buf.Bytes())
   601  	r, err := zip.NewReader(br, int64(br.Len()))
   602  	c.Assert(err, check.IsNil)
   603  	c.Check(r.File, check.HasLen, 1)
   604  	c.Check(r.File[0].Name, check.Equals, "an/entry")
   605  }
   606  
   607  func (s *snapshotSuite) TestHappyRoundtrip(c *check.C) {
   608  	s.testHappyRoundtrip(c, "marker")
   609  }
   610  
   611  func (s *snapshotSuite) TestHappyRoundtripNoCommon(c *check.C) {
   612  	for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   613  		if _, d := filepath.Split(t.dir); d == "common" {
   614  			c.Assert(os.RemoveAll(t.dir), check.IsNil)
   615  		}
   616  	}
   617  	s.testHappyRoundtrip(c, "marker")
   618  }
   619  
   620  func (s *snapshotSuite) TestHappyRoundtripNoRev(c *check.C) {
   621  	for _, t := range table(snap.MinimalPlaceInfo("hello-snap", snap.R(42)), filepath.Join(dirs.GlobalRootDir, "home/snapuser")) {
   622  		if _, d := filepath.Split(t.dir); d == "42" {
   623  			c.Assert(os.RemoveAll(t.dir), check.IsNil)
   624  		}
   625  	}
   626  	s.testHappyRoundtrip(c, "../common/marker")
   627  }
   628  
   629  func (s *snapshotSuite) testHappyRoundtrip(c *check.C, marker string) {
   630  	if os.Geteuid() == 0 {
   631  		c.Skip("this test cannot run as root (runuser will fail)")
   632  	}
   633  	logger.SimpleSetup()
   634  
   635  	epoch := snap.E("42*")
   636  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   637  	cfg := map[string]interface{}{"some-setting": false}
   638  	shID := uint64(12)
   639  
   640  	shw, err := backend.Save(context.TODO(), shID, info, cfg, []string{"snapuser"})
   641  	c.Assert(err, check.IsNil)
   642  	c.Check(shw.SetID, check.Equals, shID)
   643  	c.Check(shw.Snap, check.Equals, info.InstanceName())
   644  	c.Check(shw.SnapID, check.Equals, info.SnapID)
   645  	c.Check(shw.Version, check.Equals, info.Version)
   646  	c.Check(shw.Epoch, check.DeepEquals, epoch)
   647  	c.Check(shw.Revision, check.Equals, info.Revision)
   648  	c.Check(shw.Conf, check.DeepEquals, cfg)
   649  	c.Check(shw.Auto, check.Equals, false)
   650  	c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   651  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
   652  
   653  	shs, err := backend.List(context.TODO(), 0, nil)
   654  	c.Assert(err, check.IsNil)
   655  	c.Assert(shs, check.HasLen, 1)
   656  	c.Assert(shs[0].Snapshots, check.HasLen, 1)
   657  
   658  	shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID)
   659  	c.Assert(err, check.IsNil)
   660  	defer shr.Close()
   661  
   662  	for label, sh := range map[string]*client.Snapshot{"open": &shr.Snapshot, "list": shs[0].Snapshots[0]} {
   663  		comm := check.Commentf("%q", label)
   664  		c.Check(sh.SetID, check.Equals, shID, comm)
   665  		c.Check(sh.Snap, check.Equals, info.InstanceName(), comm)
   666  		c.Check(sh.SnapID, check.Equals, info.SnapID, comm)
   667  		c.Check(sh.Version, check.Equals, info.Version, comm)
   668  		c.Check(sh.Epoch, check.DeepEquals, epoch)
   669  		c.Check(sh.Revision, check.Equals, info.Revision, comm)
   670  		c.Check(sh.Conf, check.DeepEquals, cfg, comm)
   671  		c.Check(sh.SHA3_384, check.DeepEquals, shw.SHA3_384, comm)
   672  		c.Check(sh.Auto, check.Equals, false)
   673  	}
   674  	c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   675  	c.Check(shr.Check(context.TODO(), nil), check.IsNil)
   676  
   677  	newroot := c.MkDir()
   678  	c.Assert(os.MkdirAll(filepath.Join(newroot, "home/snapuser"), 0755), check.IsNil)
   679  	dirs.SetRootDir(newroot)
   680  
   681  	var diff = func() *exec.Cmd {
   682  		cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot)
   683  		// cmd.Stdout = os.Stdout
   684  		// cmd.Stderr = os.Stderr
   685  		return cmd
   686  	}
   687  
   688  	for i := 0; i < 3; i++ {
   689  		comm := check.Commentf("%d", i)
   690  		// sanity check
   691  		c.Check(diff().Run(), check.NotNil, comm)
   692  
   693  		// restore leaves things like they were (again and again)
   694  		rs, err := shr.Restore(context.TODO(), snap.R(0), nil, logger.Debugf)
   695  		c.Assert(err, check.IsNil, comm)
   696  		rs.Cleanup()
   697  		c.Check(diff().Run(), check.IsNil, comm)
   698  
   699  		// dirty it -> no longer like it was
   700  		c.Check(ioutil.WriteFile(filepath.Join(info.DataDir(), marker), []byte("scribble\n"), 0644), check.IsNil, comm)
   701  	}
   702  }
   703  
   704  func (s *snapshotSuite) TestOpenSetIDoverride(c *check.C) {
   705  	if os.Geteuid() == 0 {
   706  		c.Skip("this test cannot run as root (runuser will fail)")
   707  	}
   708  	logger.SimpleSetup()
   709  
   710  	epoch := snap.E("42*")
   711  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   712  	cfg := map[string]interface{}{"some-setting": false}
   713  
   714  	shw, err := backend.Save(context.TODO(), 12, info, cfg, []string{"snapuser"})
   715  	c.Assert(err, check.IsNil)
   716  	c.Check(shw.SetID, check.Equals, uint64(12))
   717  
   718  	c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   719  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
   720  
   721  	shr, err := backend.Open(backend.Filename(shw), 99)
   722  	c.Assert(err, check.IsNil)
   723  	defer shr.Close()
   724  
   725  	c.Check(shr.SetID, check.Equals, uint64(99))
   726  }
   727  
   728  func (s *snapshotSuite) TestRestoreRoundtripDifferentRevision(c *check.C) {
   729  	if os.Geteuid() == 0 {
   730  		c.Skip("this test cannot run as root (runuser will fail)")
   731  	}
   732  	logger.SimpleSetup()
   733  
   734  	epoch := snap.E("42*")
   735  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
   736  	shID := uint64(12)
   737  
   738  	shw, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"})
   739  	c.Assert(err, check.IsNil)
   740  	c.Check(shw.Revision, check.Equals, info.Revision)
   741  
   742  	shr, err := backend.Open(backend.Filename(shw), backend.ExtractFnameSetID)
   743  	c.Assert(err, check.IsNil)
   744  	defer shr.Close()
   745  
   746  	c.Check(shr.Revision, check.Equals, info.Revision)
   747  	c.Check(shr.Name(), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
   748  
   749  	// move the expected data to its expected place
   750  	for _, dir := range []string{
   751  		filepath.Join(s.root, "home", "snapuser", "snap", "hello-snap"),
   752  		filepath.Join(dirs.SnapDataDir, "hello-snap"),
   753  	} {
   754  		c.Check(os.Rename(filepath.Join(dir, "42"), filepath.Join(dir, "17")), check.IsNil)
   755  	}
   756  
   757  	newroot := c.MkDir()
   758  	c.Assert(os.MkdirAll(filepath.Join(newroot, "home", "snapuser"), 0755), check.IsNil)
   759  	dirs.SetRootDir(newroot)
   760  
   761  	var diff = func() *exec.Cmd {
   762  		cmd := exec.Command("diff", "-urN", "-x*.zip", s.root, newroot)
   763  		// cmd.Stdout = os.Stdout
   764  		// cmd.Stderr = os.Stderr
   765  		return cmd
   766  	}
   767  
   768  	// sanity check
   769  	c.Check(diff().Run(), check.NotNil)
   770  
   771  	// restore leaves things like they were, but in the new dir
   772  	rs, err := shr.Restore(context.TODO(), snap.R("17"), nil, logger.Debugf)
   773  	c.Assert(err, check.IsNil)
   774  	rs.Cleanup()
   775  	c.Check(diff().Run(), check.IsNil)
   776  }
   777  
   778  func (s *snapshotSuite) TestPickUserWrapperRunuser(c *check.C) {
   779  	n := 0
   780  	defer backend.MockExecLookPath(func(s string) (string, error) {
   781  		n++
   782  		if s != "runuser" {
   783  			c.Fatalf(`expected to get "runuser", got %q`, s)
   784  		}
   785  		return "/sbin/runuser", nil
   786  	})()
   787  
   788  	c.Check(backend.PickUserWrapper(), check.Equals, "/sbin/runuser")
   789  	c.Check(n, check.Equals, 1)
   790  }
   791  
   792  func (s *snapshotSuite) TestPickUserWrapperSudo(c *check.C) {
   793  	n := 0
   794  	defer backend.MockExecLookPath(func(s string) (string, error) {
   795  		n++
   796  		if n == 1 {
   797  			if s != "runuser" {
   798  				c.Fatalf(`expected to get "runuser" first, got %q`, s)
   799  			}
   800  			return "", errors.New("no such thing")
   801  		}
   802  		if s != "sudo" {
   803  			c.Fatalf(`expected to get "sudo" next, got %q`, s)
   804  		}
   805  		return "/usr/bin/sudo", nil
   806  	})()
   807  
   808  	c.Check(backend.PickUserWrapper(), check.Equals, "/usr/bin/sudo")
   809  	c.Check(n, check.Equals, 2)
   810  }
   811  
   812  func (s *snapshotSuite) TestPickUserWrapperNothing(c *check.C) {
   813  	n := 0
   814  	defer backend.MockExecLookPath(func(s string) (string, error) {
   815  		n++
   816  		return "", errors.New("no such thing")
   817  	})()
   818  
   819  	c.Check(backend.PickUserWrapper(), check.Equals, "")
   820  	c.Check(n, check.Equals, 2)
   821  }
   822  
   823  func (s *snapshotSuite) TestMaybeRunuserHappyRunuser(c *check.C) {
   824  	uid := sys.UserID(0)
   825  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   826  	defer backend.SetUserWrapper("/sbin/runuser")()
   827  	logbuf, restore := logger.MockLogger()
   828  	defer restore()
   829  
   830  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   831  		Path: "/sbin/runuser",
   832  		Args: []string{"/sbin/runuser", "-u", "test", "--", "tar", "--bar"},
   833  	})
   834  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   835  		Path: s.tarPath,
   836  		Args: []string{"tar", "--bar"},
   837  	})
   838  	uid = 42
   839  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   840  		Path: s.tarPath,
   841  		Args: []string{"tar", "--bar"},
   842  	})
   843  	c.Check(logbuf.String(), check.Equals, "")
   844  }
   845  
   846  func (s *snapshotSuite) TestMaybeRunuserHappySudo(c *check.C) {
   847  	uid := sys.UserID(0)
   848  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   849  	defer backend.SetUserWrapper("/usr/bin/sudo")()
   850  	logbuf, restore := logger.MockLogger()
   851  	defer restore()
   852  
   853  	cmd := backend.TarAsUser("test", "--bar")
   854  	c.Check(cmd, check.DeepEquals, &exec.Cmd{
   855  		Path: "/usr/bin/sudo",
   856  		Args: []string{"/usr/bin/sudo", "-u", "test", "--", "tar", "--bar"},
   857  	})
   858  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   859  		Path: s.tarPath,
   860  		Args: []string{"tar", "--bar"},
   861  	})
   862  	uid = 42
   863  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   864  		Path: s.tarPath,
   865  		Args: []string{"tar", "--bar"},
   866  	})
   867  	c.Check(logbuf.String(), check.Equals, "")
   868  }
   869  
   870  func (s *snapshotSuite) TestMaybeRunuserNoHappy(c *check.C) {
   871  	uid := sys.UserID(0)
   872  	defer backend.MockSysGeteuid(func() sys.UserID { return uid })()
   873  	defer backend.SetUserWrapper("")()
   874  	logbuf, restore := logger.MockLogger()
   875  	defer restore()
   876  
   877  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   878  		Path: s.tarPath,
   879  		Args: []string{"tar", "--bar"},
   880  	})
   881  	c.Check(backend.TarAsUser("root", "--bar"), check.DeepEquals, &exec.Cmd{
   882  		Path: s.tarPath,
   883  		Args: []string{"tar", "--bar"},
   884  	})
   885  	uid = 42
   886  	c.Check(backend.TarAsUser("test", "--bar"), check.DeepEquals, &exec.Cmd{
   887  		Path: s.tarPath,
   888  		Args: []string{"tar", "--bar"},
   889  	})
   890  	c.Check(strings.TrimSpace(logbuf.String()), check.Matches, ".* No user wrapper found.*")
   891  }
   892  
   893  func (s *snapshotSuite) TestImport(c *check.C) {
   894  	tempdir := c.MkDir()
   895  
   896  	// create snapshot export file
   897  	tarFile1 := path.Join(tempdir, "exported1.snapshot")
   898  	err := createTestExportFile(tarFile1, &createTestExportFlags{exportJSON: true})
   899  	c.Check(err, check.IsNil)
   900  
   901  	// create an exported snapshot with missing export.json
   902  	tarFile2 := path.Join(tempdir, "exported2.snapshot")
   903  	err = createTestExportFile(tarFile2, &createTestExportFlags{})
   904  	c.Check(err, check.IsNil)
   905  
   906  	// create invalid exported file
   907  	tarFile3 := path.Join(tempdir, "exported3.snapshot")
   908  	err = ioutil.WriteFile(tarFile3, []byte("invalid"), 0755)
   909  	c.Check(err, check.IsNil)
   910  
   911  	// create an exported snapshot with a directory
   912  	tarFile4 := path.Join(tempdir, "exported4.snapshot")
   913  	flags := &createTestExportFlags{
   914  		exportJSON: true,
   915  		withDir:    true,
   916  	}
   917  	err = createTestExportFile(tarFile4, flags)
   918  	c.Check(err, check.IsNil)
   919  
   920  	type tableT struct {
   921  		setID      uint64
   922  		filename   string
   923  		inProgress bool
   924  		error      string
   925  	}
   926  
   927  	table := []tableT{
   928  		{14, tarFile1, false, ""},
   929  		{14, tarFile2, false, "cannot import snapshot 14: no export.json file in uploaded data"},
   930  		{14, tarFile3, false, "cannot import snapshot 14: cannot read snapshot import: unexpected EOF"},
   931  		{14, tarFile4, false, "cannot import snapshot 14: unexpected directory in import file"},
   932  		{14, tarFile1, true, "cannot import snapshot 14: already in progress for this set id"},
   933  	}
   934  
   935  	for i, t := range table {
   936  		comm := check.Commentf("%d: %d %s", i, t.setID, t.filename)
   937  
   938  		// reset
   939  		err = os.RemoveAll(dirs.SnapshotsDir)
   940  		c.Assert(err, check.IsNil, comm)
   941  		importingFile := filepath.Join(dirs.SnapshotsDir, fmt.Sprintf("%d_importing", t.setID))
   942  		if t.inProgress {
   943  			err := os.MkdirAll(dirs.SnapshotsDir, 0700)
   944  			c.Assert(err, check.IsNil, comm)
   945  			err = ioutil.WriteFile(importingFile, nil, 0644)
   946  			c.Assert(err, check.IsNil)
   947  		} else {
   948  			err = os.RemoveAll(importingFile)
   949  			c.Assert(err, check.IsNil, comm)
   950  		}
   951  
   952  		f, err := os.Open(t.filename)
   953  		c.Assert(err, check.IsNil, comm)
   954  		defer f.Close()
   955  
   956  		snapNames, err := backend.Import(context.Background(), t.setID, f, nil)
   957  		if t.error != "" {
   958  			c.Check(err, check.ErrorMatches, t.error, comm)
   959  			continue
   960  		}
   961  		c.Check(err, check.IsNil, comm)
   962  		sort.Strings(snapNames)
   963  		c.Check(snapNames, check.DeepEquals, []string{"bar", "baz", "foo"})
   964  
   965  		dir, err := os.Open(dirs.SnapshotsDir)
   966  		c.Assert(err, check.IsNil, comm)
   967  		defer dir.Close()
   968  		names, err := dir.Readdirnames(100)
   969  		c.Assert(err, check.IsNil, comm)
   970  		c.Check(len(names), check.Equals, 3, comm)
   971  	}
   972  }
   973  
   974  func (s *snapshotSuite) TestImportCheckError(c *check.C) {
   975  	err := os.MkdirAll(dirs.SnapshotsDir, 0755)
   976  	c.Assert(err, check.IsNil)
   977  
   978  	// create snapshot export file
   979  	tarFile1 := path.Join(c.MkDir(), "exported1.snapshot")
   980  	flags := &createTestExportFlags{
   981  		exportJSON:      true,
   982  		corruptChecksum: true,
   983  	}
   984  	err = createTestExportFile(tarFile1, flags)
   985  	c.Assert(err, check.IsNil)
   986  
   987  	f, err := os.Open(tarFile1)
   988  	c.Assert(err, check.IsNil)
   989  	_, err = backend.Import(context.Background(), 14, f, nil)
   990  	c.Assert(err, check.ErrorMatches, `cannot import snapshot 14: validation failed for .+/14_foo_1.0_199.zip": snapshot entry "archive.tgz" expected hash \(d5ef563…\) does not match actual \(6655519…\)`)
   991  }
   992  
   993  func (s *snapshotSuite) TestImportDuplicated(c *check.C) {
   994  	err := os.MkdirAll(dirs.SnapshotsDir, 0755)
   995  	c.Assert(err, check.IsNil)
   996  
   997  	ctx := context.TODO()
   998  
   999  	epoch := snap.E("42*")
  1000  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
  1001  	shID := uint64(12)
  1002  
  1003  	shw, err := backend.Save(ctx, shID, info, nil, []string{"snapuser"})
  1004  	c.Assert(err, check.IsNil)
  1005  
  1006  	export, err := backend.NewSnapshotExport(ctx, shw.SetID)
  1007  	c.Assert(err, check.IsNil)
  1008  	err = export.Init()
  1009  	c.Assert(err, check.IsNil)
  1010  
  1011  	buf := bytes.NewBuffer(nil)
  1012  	c.Assert(export.StreamTo(buf), check.IsNil)
  1013  	c.Check(buf.Len(), check.Equals, int(export.Size()))
  1014  
  1015  	// now import it
  1016  	_, err = backend.Import(ctx, 123, buf, nil)
  1017  	dupErr, ok := err.(backend.DuplicatedSnapshotImportError)
  1018  	c.Assert(ok, check.Equals, true)
  1019  	c.Assert(dupErr, check.DeepEquals, backend.DuplicatedSnapshotImportError{SetID: shID, SnapNames: []string{"hello-snap"}})
  1020  }
  1021  
  1022  func (s *snapshotSuite) TestImportExportRoundtrip(c *check.C) {
  1023  	err := os.MkdirAll(dirs.SnapshotsDir, 0755)
  1024  	c.Assert(err, check.IsNil)
  1025  
  1026  	ctx := context.TODO()
  1027  
  1028  	epoch := snap.E("42*")
  1029  	info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}, Version: "v1.33", Epoch: epoch}
  1030  	cfg := map[string]interface{}{"some-setting": false}
  1031  	shID := uint64(12)
  1032  
  1033  	shw, err := backend.Save(ctx, shID, info, cfg, []string{"snapuser"})
  1034  	c.Assert(err, check.IsNil)
  1035  	c.Check(shw.SetID, check.Equals, shID)
  1036  
  1037  	c.Check(backend.Filename(shw), check.Equals, filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip"))
  1038  	c.Check(hashkeys(shw), check.DeepEquals, []string{"archive.tgz", "user/snapuser.tgz"})
  1039  
  1040  	export, err := backend.NewSnapshotExport(ctx, shw.SetID)
  1041  	c.Assert(err, check.IsNil)
  1042  	err = export.Init()
  1043  	c.Assert(err, check.IsNil)
  1044  
  1045  	buf := bytes.NewBuffer(nil)
  1046  	c.Assert(export.StreamTo(buf), check.IsNil)
  1047  	c.Check(buf.Len(), check.Equals, int(export.Size()))
  1048  
  1049  	// now import it
  1050  	c.Assert(os.Remove(filepath.Join(dirs.SnapshotsDir, "12_hello-snap_v1.33_42.zip")), check.IsNil)
  1051  
  1052  	names, err := backend.Import(ctx, 123, buf, nil)
  1053  	c.Assert(err, check.IsNil)
  1054  	c.Check(names, check.DeepEquals, []string{"hello-snap"})
  1055  
  1056  	sets, err := backend.List(ctx, 0, nil)
  1057  	c.Assert(err, check.IsNil)
  1058  	c.Assert(sets, check.HasLen, 1)
  1059  	c.Check(sets[0].ID, check.Equals, uint64(123))
  1060  
  1061  	rdr, err := backend.Open(filepath.Join(dirs.SnapshotsDir, "123_hello-snap_v1.33_42.zip"), backend.ExtractFnameSetID)
  1062  	defer rdr.Close()
  1063  	c.Check(err, check.IsNil)
  1064  	c.Check(rdr.SetID, check.Equals, uint64(123))
  1065  	c.Check(rdr.Snap, check.Equals, "hello-snap")
  1066  	c.Check(rdr.IsValid(), check.Equals, true)
  1067  }
  1068  
  1069  func (s *snapshotSuite) TestEstimateSnapshotSize(c *check.C) {
  1070  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
  1071  		return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil
  1072  	})
  1073  	defer restore()
  1074  
  1075  	var info = &snap.Info{
  1076  		SuggestedName: "foo",
  1077  		SideInfo: snap.SideInfo{
  1078  			Revision: snap.R(7),
  1079  		},
  1080  	}
  1081  
  1082  	snapData := []string{
  1083  		"/var/snap/foo/7/somedatadir",
  1084  		"/var/snap/foo/7/otherdata",
  1085  		"/var/snap/foo/7",
  1086  		"/var/snap/foo/common",
  1087  		"/var/snap/foo/common/a",
  1088  		"/home/user1/snap/foo/7/somedata",
  1089  		"/home/user1/snap/foo/common",
  1090  	}
  1091  	var data []byte
  1092  	var expected int
  1093  	for _, d := range snapData {
  1094  		data = append(data, 0)
  1095  		expected += len(data)
  1096  		c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil)
  1097  		c.Assert(ioutil.WriteFile(filepath.Join(s.root, d, "somfile"), data, 0644), check.IsNil)
  1098  	}
  1099  
  1100  	sz, err := backend.EstimateSnapshotSize(info, nil)
  1101  	c.Assert(err, check.IsNil)
  1102  	c.Check(sz, check.Equals, uint64(expected))
  1103  }
  1104  
  1105  func (s *snapshotSuite) TestEstimateSnapshotSizeEmpty(c *check.C) {
  1106  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
  1107  		return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil
  1108  	})
  1109  	defer restore()
  1110  
  1111  	var info = &snap.Info{
  1112  		SuggestedName: "foo",
  1113  		SideInfo: snap.SideInfo{
  1114  			Revision: snap.R(7),
  1115  		},
  1116  	}
  1117  
  1118  	snapData := []string{
  1119  		"/var/snap/foo/common",
  1120  		"/var/snap/foo/7",
  1121  		"/home/user1/snap/foo/7",
  1122  		"/home/user1/snap/foo/common",
  1123  	}
  1124  	for _, d := range snapData {
  1125  		c.Assert(os.MkdirAll(filepath.Join(s.root, d), 0755), check.IsNil)
  1126  	}
  1127  
  1128  	sz, err := backend.EstimateSnapshotSize(info, nil)
  1129  	c.Assert(err, check.IsNil)
  1130  	c.Check(sz, check.Equals, uint64(0))
  1131  }
  1132  
  1133  func (s *snapshotSuite) TestEstimateSnapshotPassesUsernames(c *check.C) {
  1134  	var gotUsernames []string
  1135  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
  1136  		gotUsernames = usernames
  1137  		return nil, nil
  1138  	})
  1139  	defer restore()
  1140  
  1141  	var info = &snap.Info{
  1142  		SuggestedName: "foo",
  1143  		SideInfo: snap.SideInfo{
  1144  			Revision: snap.R(7),
  1145  		},
  1146  	}
  1147  
  1148  	_, err := backend.EstimateSnapshotSize(info, []string{"user1", "user2"})
  1149  	c.Assert(err, check.IsNil)
  1150  	c.Check(gotUsernames, check.DeepEquals, []string{"user1", "user2"})
  1151  }
  1152  
  1153  func (s *snapshotSuite) TestEstimateSnapshotSizeNotDataDirs(c *check.C) {
  1154  	restore := backend.MockUsersForUsernames(func(usernames []string) ([]*user.User, error) {
  1155  		return []*user.User{{HomeDir: filepath.Join(s.root, "home/user1")}}, nil
  1156  	})
  1157  	defer restore()
  1158  
  1159  	var info = &snap.Info{
  1160  		SuggestedName: "foo",
  1161  		SideInfo:      snap.SideInfo{Revision: snap.R(7)},
  1162  	}
  1163  
  1164  	sz, err := backend.EstimateSnapshotSize(info, nil)
  1165  	c.Assert(err, check.IsNil)
  1166  	c.Check(sz, check.Equals, uint64(0))
  1167  }
  1168  func (s *snapshotSuite) TestExportTwice(c *check.C) {
  1169  	// use mocking done in snapshotSuite.SetUpTest
  1170  	info := &snap.Info{
  1171  		SideInfo: snap.SideInfo{
  1172  			RealName: "hello-snap",
  1173  			Revision: snap.R(42),
  1174  			SnapID:   "hello-id",
  1175  		},
  1176  		Version: "v1.33",
  1177  	}
  1178  	// create a snapshot
  1179  	shID := uint64(12)
  1180  	_, err := backend.Save(context.TODO(), shID, info, nil, []string{"snapuser"})
  1181  	c.Check(err, check.IsNil)
  1182  
  1183  	// content.json + num_files + export.json + footer
  1184  	expectedSize := int64(1024 + 4*512 + 1024 + 2*512)
  1185  	// do on export at the start of the epoch
  1186  	restore := backend.MockTimeNow(func() time.Time { return time.Time{} })
  1187  	defer restore()
  1188  	// export once
  1189  	buf := bytes.NewBuffer(nil)
  1190  	ctx := context.Background()
  1191  	se, err := backend.NewSnapshotExport(ctx, shID)
  1192  	c.Check(err, check.IsNil)
  1193  	err = se.Init()
  1194  	c.Assert(err, check.IsNil)
  1195  	c.Check(se.Size(), check.Equals, expectedSize)
  1196  	// and we can stream the data
  1197  	err = se.StreamTo(buf)
  1198  	c.Assert(err, check.IsNil)
  1199  	c.Check(buf.Len(), check.Equals, int(expectedSize))
  1200  
  1201  	// and again to ensure size does not change when exported again
  1202  	//
  1203  	// Note that moving beyond year 2242 will change the tar format
  1204  	// used by the go internal tar and that will make the size actually
  1205  	// change.
  1206  	restore = backend.MockTimeNow(func() time.Time { return time.Date(2242, 1, 1, 12, 0, 0, 0, time.UTC) })
  1207  	defer restore()
  1208  	se2, err := backend.NewSnapshotExport(ctx, shID)
  1209  	c.Check(err, check.IsNil)
  1210  	err = se2.Init()
  1211  	c.Assert(err, check.IsNil)
  1212  	c.Check(se2.Size(), check.Equals, expectedSize)
  1213  	// and we can stream the data
  1214  	buf.Reset()
  1215  	err = se2.StreamTo(buf)
  1216  	c.Assert(err, check.IsNil)
  1217  	c.Check(buf.Len(), check.Equals, int(expectedSize))
  1218  }
  1219  
  1220  func (s *snapshotSuite) TestExportUnhappy(c *check.C) {
  1221  	se, err := backend.NewSnapshotExport(context.Background(), 5)
  1222  	c.Assert(err, check.ErrorMatches, "no snapshot data found for 5")
  1223  	c.Assert(se, check.IsNil)
  1224  }
  1225  
  1226  type createTestExportFlags struct {
  1227  	exportJSON      bool
  1228  	withDir         bool
  1229  	corruptChecksum bool
  1230  }
  1231  
  1232  func createTestExportFile(filename string, flags *createTestExportFlags) error {
  1233  	tf, err := os.Create(filename)
  1234  	if err != nil {
  1235  		return err
  1236  	}
  1237  	defer tf.Close()
  1238  	tw := tar.NewWriter(tf)
  1239  	defer tw.Close()
  1240  
  1241  	for _, s := range []string{"foo", "bar", "baz"} {
  1242  		fname := fmt.Sprintf("5_%s_1.0_199.zip", s)
  1243  
  1244  		buf := bytes.NewBuffer(nil)
  1245  		zipW := zip.NewWriter(buf)
  1246  		defer zipW.Close()
  1247  
  1248  		sha := map[string]string{}
  1249  
  1250  		// create dummy archive.tgz
  1251  		archiveWriter, err := zipW.CreateHeader(&zip.FileHeader{Name: "archive.tgz"})
  1252  		if err != nil {
  1253  			return err
  1254  		}
  1255  		var sz osutil.Sizer
  1256  		hasher := crypto.SHA3_384.New()
  1257  		out := io.MultiWriter(archiveWriter, hasher, &sz)
  1258  		if _, err := out.Write([]byte(s)); err != nil {
  1259  			return err
  1260  		}
  1261  
  1262  		if flags.corruptChecksum {
  1263  			hasher.Write([]byte{0})
  1264  		}
  1265  		sha["archive.tgz"] = fmt.Sprintf("%x", hasher.Sum(nil))
  1266  
  1267  		snapshot := backend.MockSnapshot(5, s, snap.Revision{N: 199}, sz.Size(), sha)
  1268  
  1269  		// create meta.json
  1270  		metaWriter, err := zipW.Create("meta.json")
  1271  		if err != nil {
  1272  			return err
  1273  		}
  1274  		hasher = crypto.SHA3_384.New()
  1275  		enc := json.NewEncoder(io.MultiWriter(metaWriter, hasher))
  1276  		if err := enc.Encode(snapshot); err != nil {
  1277  			return err
  1278  		}
  1279  
  1280  		// write meta.sha3_384
  1281  		metaSha3Writer, err := zipW.Create("meta.sha3_384")
  1282  		if err != nil {
  1283  			return err
  1284  		}
  1285  		fmt.Fprintf(metaSha3Writer, "%x\n", hasher.Sum(nil))
  1286  		zipW.Close()
  1287  
  1288  		hdr := &tar.Header{
  1289  			Name: fname,
  1290  			Mode: 0644,
  1291  			Size: int64(buf.Len()),
  1292  		}
  1293  		if err := tw.WriteHeader(hdr); err != nil {
  1294  			return err
  1295  		}
  1296  		if _, err := tw.Write(buf.Bytes()); err != nil {
  1297  			return err
  1298  		}
  1299  	}
  1300  
  1301  	if flags.withDir {
  1302  		hdr := &tar.Header{
  1303  			Name:     dirs.SnapshotsDir,
  1304  			Mode:     0700,
  1305  			Size:     int64(0),
  1306  			Typeflag: tar.TypeDir,
  1307  		}
  1308  		if err := tw.WriteHeader(hdr); err != nil {
  1309  			return err
  1310  		}
  1311  		if _, err = tw.Write([]byte("")); err != nil {
  1312  			return nil
  1313  		}
  1314  	}
  1315  
  1316  	if flags.exportJSON {
  1317  		exp := fmt.Sprintf(`{"format":1, "date":"%s"}`, time.Now().Format(time.RFC3339))
  1318  		hdr := &tar.Header{
  1319  			Name: "export.json",
  1320  			Mode: 0644,
  1321  			Size: int64(len(exp)),
  1322  		}
  1323  		if err := tw.WriteHeader(hdr); err != nil {
  1324  			return err
  1325  		}
  1326  		if _, err = tw.Write([]byte(exp)); err != nil {
  1327  			return nil
  1328  		}
  1329  	}
  1330  
  1331  	return nil
  1332  }
  1333  
  1334  func makeMockSnapshotZipContent(c *check.C) []byte {
  1335  	buf := bytes.NewBuffer(nil)
  1336  	zipW := zip.NewWriter(buf)
  1337  
  1338  	// create dummy archive.tgz
  1339  	archiveWriter, err := zipW.CreateHeader(&zip.FileHeader{Name: "archive.tgz"})
  1340  	c.Assert(err, check.IsNil)
  1341  	_, err = archiveWriter.Write([]byte("mock archive.tgz content"))
  1342  	c.Assert(err, check.IsNil)
  1343  
  1344  	// create dummy meta.json
  1345  	archiveWriter, err = zipW.CreateHeader(&zip.FileHeader{Name: "meta.json"})
  1346  	c.Assert(err, check.IsNil)
  1347  	_, err = archiveWriter.Write([]byte("{}"))
  1348  	c.Assert(err, check.IsNil)
  1349  
  1350  	zipW.Close()
  1351  	return buf.Bytes()
  1352  }
  1353  
  1354  func (s *snapshotSuite) TestIterWithMockedSnapshotFiles(c *check.C) {
  1355  	err := os.MkdirAll(dirs.SnapshotsDir, 0755)
  1356  	c.Assert(err, check.IsNil)
  1357  
  1358  	fn := "1_hello_1.0_x1.zip"
  1359  	err = ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, fn), makeMockSnapshotZipContent(c), 0644)
  1360  	c.Assert(err, check.IsNil)
  1361  
  1362  	callbackCalled := 0
  1363  	f := func(snapshot *backend.Reader) error {
  1364  		callbackCalled++
  1365  		return nil
  1366  	}
  1367  
  1368  	err = backend.Iter(context.Background(), f)
  1369  	c.Check(err, check.IsNil)
  1370  	c.Check(callbackCalled, check.Equals, 1)
  1371  
  1372  	// now pretend we are importing snapshot id 1
  1373  	callbackCalled = 0
  1374  	fn = "1_importing"
  1375  	err = ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, fn), nil, 0644)
  1376  	c.Assert(err, check.IsNil)
  1377  
  1378  	// and while importing Iter() does not call the callback
  1379  	err = backend.Iter(context.Background(), f)
  1380  	c.Check(err, check.IsNil)
  1381  	c.Check(callbackCalled, check.Equals, 0)
  1382  }
  1383  
  1384  func (s *snapshotSuite) TestCleanupAbandondedImports(c *check.C) {
  1385  	err := os.MkdirAll(dirs.SnapshotsDir, 0755)
  1386  	c.Assert(err, check.IsNil)
  1387  
  1388  	// create 2 snapshot IDs 1,2
  1389  	snapshotFiles := map[int][]string{}
  1390  	for i := 1; i < 3; i++ {
  1391  		fn := fmt.Sprintf("%d_hello_%d.0_x1.zip", i, i)
  1392  		p := filepath.Join(dirs.SnapshotsDir, fn)
  1393  		snapshotFiles[i] = append(snapshotFiles[i], p)
  1394  		err = ioutil.WriteFile(p, makeMockSnapshotZipContent(c), 0644)
  1395  		c.Assert(err, check.IsNil)
  1396  
  1397  		fn = fmt.Sprintf("%d_olleh_%d.0_x1.zip", i, i)
  1398  		p = filepath.Join(dirs.SnapshotsDir, fn)
  1399  		snapshotFiles[i] = append(snapshotFiles[i], p)
  1400  		err = ioutil.WriteFile(p, makeMockSnapshotZipContent(c), 0644)
  1401  		c.Assert(err, check.IsNil)
  1402  	}
  1403  
  1404  	// pretend setID 2 has a import file which means which means that
  1405  	// an import was started in the past but did not complete
  1406  	fn := "2_importing"
  1407  	err = ioutil.WriteFile(filepath.Join(dirs.SnapshotsDir, fn), nil, 0644)
  1408  	c.Assert(err, check.IsNil)
  1409  
  1410  	// run cleanup
  1411  	cleaned, err := backend.CleanupAbandondedImports()
  1412  	c.Check(cleaned, check.Equals, 1)
  1413  	c.Check(err, check.IsNil)
  1414  
  1415  	// id1 untouched
  1416  	c.Check(snapshotFiles[1][0], testutil.FilePresent)
  1417  	c.Check(snapshotFiles[1][1], testutil.FilePresent)
  1418  	// id2 cleaned
  1419  	c.Check(snapshotFiles[2][0], testutil.FileAbsent)
  1420  	c.Check(snapshotFiles[2][1], testutil.FileAbsent)
  1421  }
  1422  
  1423  func (s *snapshotSuite) TestCleanupAbandondedImportsFailMany(c *check.C) {
  1424  	restore := backend.MockFilepathGlob(func(string) ([]string, error) {
  1425  		return []string{
  1426  			"/var/lib/snapd/snapshots/NaN_importing",
  1427  			"/var/lib/snapd/snapshots/11_importing",
  1428  			"/var/lib/snapd/snapshots/22_importing",
  1429  		}, nil
  1430  	})
  1431  	defer restore()
  1432  
  1433  	_, err := backend.CleanupAbandondedImports()
  1434  	c.Assert(err, check.ErrorMatches, `cannot cleanup imports:
  1435  - cannot determine snapshot id from "/var/lib/snapd/snapshots/NaN_importing"
  1436  - cannot cancel import for set id 11:
  1437   - remove /.*/var/lib/snapd/snapshots/11_importing: no such file or directory
  1438  - cannot cancel import for set id 22:
  1439   - remove /.*/var/lib/snapd/snapshots/22_importing: no such file or directory`)
  1440  }
  1441  
  1442  func (s *snapshotSuite) TestMultiError(c *check.C) {
  1443  	me2 := backend.NewMultiError("deeper nested wrongness", []error{
  1444  		fmt.Errorf("some error in level 2"),
  1445  	})
  1446  	me1 := backend.NewMultiError("nested wrongness", []error{
  1447  		fmt.Errorf("some error in level 1"),
  1448  		me2,
  1449  		fmt.Errorf("other error in level 1"),
  1450  	})
  1451  	me := backend.NewMultiError("many things went wrong", []error{
  1452  		fmt.Errorf("some normal error"),
  1453  		me1,
  1454  	})
  1455  
  1456  	c.Check(me, check.ErrorMatches, `many things went wrong:
  1457  - some normal error
  1458  - nested wrongness:
  1459   - some error in level 1
  1460   - deeper nested wrongness:
  1461    - some error in level 2
  1462   - other error in level 1`)
  1463  
  1464  	// do it again
  1465  	c.Check(me, check.ErrorMatches, `many things went wrong:
  1466  - some normal error
  1467  - nested wrongness:
  1468   - some error in level 1
  1469   - deeper nested wrongness:
  1470    - some error in level 2
  1471   - other error in level 1`)
  1472  }
  1473  
  1474  func (s *snapshotSuite) TestMultiErrorCycle(c *check.C) {
  1475  	errs := []error{nil, fmt.Errorf("e5")}
  1476  	me5 := backend.NewMultiError("he5", errs)
  1477  	// very hard to happen in practice
  1478  	errs[0] = me5
  1479  	me4 := backend.NewMultiError("he4", []error{me5})
  1480  	me3 := backend.NewMultiError("he3", []error{me4})
  1481  	me2 := backend.NewMultiError("he3", []error{me3})
  1482  	me1 := backend.NewMultiError("he1", []error{me2})
  1483  	me := backend.NewMultiError("he", []error{me1})
  1484  
  1485  	c.Check(me, check.ErrorMatches, `he:
  1486  - he1:
  1487   - he3:
  1488    - he3:
  1489     - he4:
  1490      - he5:
  1491       - he5:
  1492        - he5:
  1493         - he5:
  1494          - circular or too deep error nesting \(max 8\)\?!
  1495          - e5
  1496         - e5
  1497        - e5
  1498       - e5`)
  1499  }
  1500  
  1501  func (s *snapshotSuite) TestSnapshotExportContentHash(c *check.C) {
  1502  	ctx := context.TODO()
  1503  	info := &snap.Info{
  1504  		SideInfo: snap.SideInfo{
  1505  			RealName: "hello-snap",
  1506  			Revision: snap.R(42),
  1507  			SnapID:   "hello-id",
  1508  		},
  1509  		Version: "v1.33",
  1510  	}
  1511  	shID := uint64(12)
  1512  	shw, err := backend.Save(ctx, shID, info, nil, []string{"snapuser"})
  1513  	c.Check(err, check.IsNil)
  1514  
  1515  	// now export it
  1516  	export, err := backend.NewSnapshotExport(ctx, shw.SetID)
  1517  	c.Assert(err, check.IsNil)
  1518  	c.Check(export.ContentHash(), check.HasLen, sha256.Size)
  1519  
  1520  	// and check that exporting it again leads to the same content hash
  1521  	export2, err := backend.NewSnapshotExport(ctx, shw.SetID)
  1522  	c.Assert(err, check.IsNil)
  1523  	c.Check(export.ContentHash(), check.DeepEquals, export2.ContentHash())
  1524  
  1525  	// but changing the snapshot changes the content hash
  1526  	info = &snap.Info{
  1527  		SideInfo: snap.SideInfo{
  1528  			RealName: "hello-snap",
  1529  			Revision: snap.R(9999),
  1530  			SnapID:   "hello-id",
  1531  		},
  1532  		Version: "v1.33",
  1533  	}
  1534  	shw, err = backend.Save(ctx, shID, info, nil, []string{"snapuser"})
  1535  	c.Check(err, check.IsNil)
  1536  
  1537  	export3, err := backend.NewSnapshotExport(ctx, shw.SetID)
  1538  	c.Assert(err, check.IsNil)
  1539  	c.Check(export.ContentHash(), check.Not(check.DeepEquals), export3.ContentHash())
  1540  }