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