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