github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/raw_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 gadget_test 21 22 import ( 23 "errors" 24 "io" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/gadget" 31 "github.com/snapcore/snapd/osutil" 32 "github.com/snapcore/snapd/testutil" 33 ) 34 35 type rawTestSuite struct { 36 dir string 37 backup string 38 } 39 40 var _ = Suite(&rawTestSuite{}) 41 42 func (r *rawTestSuite) SetUpTest(c *C) { 43 r.dir = c.MkDir() 44 r.backup = c.MkDir() 45 } 46 47 func openSizedFile(c *C, path string, size gadget.Size) *os.File { 48 f, err := os.Create(path) 49 c.Assert(err, IsNil) 50 51 if size != 0 { 52 err = f.Truncate(int64(size)) 53 c.Assert(err, IsNil) 54 } 55 56 return f 57 } 58 59 type mutateWrite struct { 60 what []byte 61 off int64 62 } 63 64 func mutateFile(c *C, path string, size gadget.Size, writes []mutateWrite) { 65 out := openSizedFile(c, path, size) 66 for _, op := range writes { 67 _, err := out.WriteAt(op.what, op.off) 68 c.Assert(err, IsNil) 69 } 70 } 71 72 func (r *rawTestSuite) TestRawWriterHappy(c *C) { 73 74 out := openSizedFile(c, filepath.Join(r.dir, "out.img"), 2048) 75 defer out.Close() 76 77 makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("foo foo foo")) 78 makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 128, []byte("bar bar bar")) 79 80 ps := &gadget.LaidOutStructure{ 81 VolumeStructure: &gadget.VolumeStructure{ 82 Size: 2048, 83 }, 84 LaidOutContent: []gadget.LaidOutContent{ 85 { 86 VolumeContent: &gadget.VolumeContent{ 87 Image: "foo.img", 88 }, 89 StartOffset: 0, 90 Size: 128, 91 Index: 0, 92 }, { 93 VolumeContent: &gadget.VolumeContent{ 94 Image: "bar.img", 95 }, 96 StartOffset: 1024, 97 Size: 128, 98 Index: 1, 99 }, 100 }, 101 } 102 rw, err := gadget.NewRawStructureWriter(r.dir, ps) 103 c.Assert(err, IsNil) 104 c.Assert(rw, NotNil) 105 106 err = rw.Write(out) 107 c.Assert(err, IsNil) 108 109 expectedPath := filepath.Join(r.dir, "expected.img") 110 mutateFile(c, expectedPath, 2048, []mutateWrite{ 111 {[]byte("foo foo foo"), 0}, 112 {[]byte("bar bar bar"), 1024}, 113 }) 114 expected, err := os.Open(expectedPath) 115 c.Assert(err, IsNil) 116 defer expected.Close() 117 118 // rewind 119 _, err = out.Seek(0, io.SeekStart) 120 c.Assert(err, IsNil) 121 _, err = expected.Seek(0, io.SeekStart) 122 c.Assert(err, IsNil) 123 124 c.Check(osutil.StreamsEqual(out, expected), Equals, true) 125 } 126 127 func (r *rawTestSuite) TestRawWriterNoFile(c *C) { 128 129 ps := &gadget.LaidOutStructure{ 130 VolumeStructure: &gadget.VolumeStructure{ 131 Size: 2048, 132 }, 133 LaidOutContent: []gadget.LaidOutContent{ 134 { 135 VolumeContent: &gadget.VolumeContent{ 136 Image: "foo.img", 137 }, 138 StartOffset: 0, 139 }, 140 }, 141 } 142 rw, err := gadget.NewRawStructureWriter(r.dir, ps) 143 c.Assert(err, IsNil) 144 c.Assert(rw, NotNil) 145 146 out := openSizedFile(c, filepath.Join(r.dir, "out.img"), 2048) 147 defer out.Close() 148 149 err = rw.Write(out) 150 c.Assert(err, ErrorMatches, "failed to write image.* cannot open image file:.* no such file or directory") 151 } 152 153 type mockWriteSeeker struct { 154 write func(b []byte) (n int, err error) 155 seek func(offset int64, whence int) (ret int64, err error) 156 } 157 158 func (m *mockWriteSeeker) Write(b []byte) (n int, err error) { 159 if m.write != nil { 160 return m.write(b) 161 } 162 return len(b), nil 163 } 164 165 func (m *mockWriteSeeker) Seek(offset int64, whence int) (ret int64, err error) { 166 if m.seek != nil { 167 return m.seek(offset, whence) 168 } 169 return offset, nil 170 } 171 172 func (r *rawTestSuite) TestRawWriterFailInWriteSeeker(c *C) { 173 out := &mockWriteSeeker{ 174 write: func(b []byte) (n int, err error) { 175 c.Logf("write write\n") 176 return 0, errors.New("failed") 177 }, 178 } 179 makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("foo foo foo")) 180 181 ps := &gadget.LaidOutStructure{ 182 VolumeStructure: &gadget.VolumeStructure{ 183 Size: 2048, 184 }, 185 LaidOutContent: []gadget.LaidOutContent{ 186 { 187 VolumeContent: &gadget.VolumeContent{ 188 Image: "foo.img", 189 }, 190 StartOffset: 1024, 191 Size: 128, 192 }, 193 }, 194 } 195 rw, err := gadget.NewRawStructureWriter(r.dir, ps) 196 c.Assert(err, IsNil) 197 c.Assert(rw, NotNil) 198 199 err = rw.Write(out) 200 c.Assert(err, ErrorMatches, "failed to write image .*: cannot write image: failed") 201 202 out = &mockWriteSeeker{ 203 seek: func(offset int64, whence int) (ret int64, err error) { 204 return 0, errors.New("failed") 205 }, 206 } 207 err = rw.Write(out) 208 c.Assert(err, ErrorMatches, "failed to write image .*: cannot seek to content start offset 0x400: failed") 209 } 210 211 func (r *rawTestSuite) TestRawWriterNoImage(c *C) { 212 out := &mockWriteSeeker{} 213 ps := &gadget.LaidOutStructure{ 214 VolumeStructure: &gadget.VolumeStructure{ 215 Size: 2048, 216 }, 217 LaidOutContent: []gadget.LaidOutContent{ 218 { 219 // invalid content 220 VolumeContent: &gadget.VolumeContent{ 221 Image: "", 222 }, 223 StartOffset: 1024, 224 Size: 128, 225 }, 226 }, 227 } 228 rw, err := gadget.NewRawStructureWriter(r.dir, ps) 229 c.Assert(err, IsNil) 230 c.Assert(rw, NotNil) 231 232 err = rw.Write(out) 233 c.Assert(err, ErrorMatches, "failed to write image .*: no image defined") 234 } 235 236 func (r *rawTestSuite) TestRawWriterFailWithNonBare(c *C) { 237 ps := &gadget.LaidOutStructure{ 238 VolumeStructure: &gadget.VolumeStructure{ 239 Size: 2048, 240 // non-bare 241 Filesystem: "ext4", 242 }, 243 } 244 245 rw, err := gadget.NewRawStructureWriter(r.dir, ps) 246 c.Assert(err, ErrorMatches, "internal error: structure #0 has a filesystem") 247 c.Assert(rw, IsNil) 248 } 249 250 func (r *rawTestSuite) TestRawWriterInternalErrors(c *C) { 251 ps := &gadget.LaidOutStructure{ 252 VolumeStructure: &gadget.VolumeStructure{ 253 Size: 2048, 254 }, 255 } 256 257 rw, err := gadget.NewRawStructureWriter("", ps) 258 c.Assert(err, ErrorMatches, "internal error: gadget content directory cannot be unset") 259 c.Assert(rw, IsNil) 260 261 rw, err = gadget.NewRawStructureWriter(r.dir, nil) 262 c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`) 263 c.Assert(rw, IsNil) 264 } 265 266 func getFileSize(c *C, path string) int64 { 267 stat, err := os.Stat(path) 268 c.Assert(err, IsNil) 269 return stat.Size() 270 } 271 272 func (r *rawTestSuite) TestRawUpdaterFailWithNonBare(c *C) { 273 ps := &gadget.LaidOutStructure{ 274 VolumeStructure: &gadget.VolumeStructure{ 275 Size: 2048, 276 // non-bare 277 Filesystem: "ext4", 278 }, 279 } 280 281 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 282 c.Fatalf("unexpected call") 283 return "", 0, nil 284 }) 285 c.Assert(err, ErrorMatches, "internal error: structure #0 has a filesystem") 286 c.Assert(ru, IsNil) 287 } 288 289 func (r *rawTestSuite) TestRawUpdaterBackupUpdateRestoreSame(c *C) { 290 291 partitionPath := filepath.Join(r.dir, "partition.img") 292 mutateFile(c, partitionPath, 2048, []mutateWrite{ 293 {[]byte("foo foo foo"), 0}, 294 {[]byte("bar bar bar"), 1024}, 295 }) 296 297 makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("foo foo foo")) 298 makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 128, []byte("bar bar bar")) 299 ps := &gadget.LaidOutStructure{ 300 VolumeStructure: &gadget.VolumeStructure{ 301 Size: 2048, 302 }, 303 StartOffset: 1 * gadget.SizeMiB, 304 LaidOutContent: []gadget.LaidOutContent{ 305 { 306 VolumeContent: &gadget.VolumeContent{ 307 Image: "foo.img", 308 }, 309 StartOffset: 1 * gadget.SizeMiB, 310 Size: 128, 311 }, { 312 VolumeContent: &gadget.VolumeContent{ 313 Image: "bar.img", 314 }, 315 StartOffset: 1*gadget.SizeMiB + 1024, 316 Size: 128, 317 Index: 1, 318 }, 319 }, 320 } 321 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 322 c.Check(to, DeepEquals, ps) 323 // Structure has a partition, thus it starts at 0 offset. 324 return partitionPath, 0, nil 325 }) 326 c.Assert(err, IsNil) 327 c.Assert(ru, NotNil) 328 329 err = ru.Backup() 330 c.Assert(err, IsNil) 331 332 c.Check(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".same", testutil.FilePresent) 333 c.Check(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1])+".same", testutil.FilePresent) 334 335 emptyDiskPath := filepath.Join(r.dir, "disk-not-written.img") 336 err = osutil.AtomicWriteFile(emptyDiskPath, nil, 0644, 0) 337 c.Assert(err, IsNil) 338 // update should be a noop now, use the same locations, point to a file 339 // of 0 size, so that seek fails and write would increase the size 340 ru, err = gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 341 return emptyDiskPath, 0, nil 342 }) 343 c.Assert(err, IsNil) 344 c.Assert(ru, NotNil) 345 346 err = ru.Update() 347 c.Assert(err, Equals, gadget.ErrNoUpdate) 348 c.Check(getFileSize(c, emptyDiskPath), Equals, int64(0)) 349 350 // rollback also is a noop 351 err = ru.Rollback() 352 c.Assert(err, IsNil) 353 c.Check(getFileSize(c, emptyDiskPath), Equals, int64(0)) 354 } 355 356 func (r *rawTestSuite) TestRawUpdaterBackupUpdateRestoreDifferent(c *C) { 357 358 diskPath := filepath.Join(r.dir, "partition.img") 359 mutateFile(c, diskPath, 4096, []mutateWrite{ 360 {[]byte("foo foo foo"), 0}, 361 {[]byte("bar bar bar"), 1024}, 362 {[]byte("unchanged unchanged"), 2048}, 363 }) 364 365 pristinePath := filepath.Join(r.dir, "pristine.img") 366 err := osutil.CopyFile(diskPath, pristinePath, 0) 367 c.Assert(err, IsNil) 368 369 expectedPath := filepath.Join(r.dir, "expected.img") 370 mutateFile(c, expectedPath, 4096, []mutateWrite{ 371 {[]byte("zzz zzz zzz zzz"), 0}, 372 {[]byte("xxx xxx xxx xxx"), 1024}, 373 {[]byte("unchanged unchanged"), 2048}, 374 }) 375 376 makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("zzz zzz zzz zzz")) 377 makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 256, []byte("xxx xxx xxx xxx")) 378 makeSizedFile(c, filepath.Join(r.dir, "unchanged.img"), 128, []byte("unchanged unchanged")) 379 ps := &gadget.LaidOutStructure{ 380 VolumeStructure: &gadget.VolumeStructure{ 381 Size: 4096, 382 }, 383 StartOffset: 1 * gadget.SizeMiB, 384 LaidOutContent: []gadget.LaidOutContent{ 385 { 386 VolumeContent: &gadget.VolumeContent{ 387 Image: "foo.img", 388 }, 389 StartOffset: 1 * gadget.SizeMiB, 390 Size: 128, 391 }, { 392 VolumeContent: &gadget.VolumeContent{ 393 Image: "bar.img", 394 }, 395 StartOffset: 1*gadget.SizeMiB + 1024, 396 Size: 256, 397 Index: 1, 398 }, { 399 VolumeContent: &gadget.VolumeContent{ 400 Image: "unchanged.img", 401 }, 402 StartOffset: 1*gadget.SizeMiB + 2048, 403 Size: 128, 404 Index: 2, 405 }, 406 }, 407 } 408 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 409 c.Check(to, DeepEquals, ps) 410 // Structure has a partition, thus it starts at 0 offset. 411 return diskPath, 0, nil 412 }) 413 c.Assert(err, IsNil) 414 c.Assert(ru, NotNil) 415 416 err = ru.Backup() 417 c.Assert(err, IsNil) 418 419 for _, e := range []struct { 420 path string 421 size int64 422 exists bool 423 }{ 424 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0]) + ".backup", 128, true}, 425 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".backup", 256, true}, 426 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[2]) + ".backup", 0, false}, 427 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".same", 0, false}, 428 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".same", 0, false}, 429 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[2]) + ".same", 0, true}, 430 } { 431 if e.exists { 432 c.Check(e.path, testutil.FilePresent) 433 c.Check(getFileSize(c, e.path), Equals, e.size) 434 } else { 435 c.Check(e.path, testutil.FileAbsent) 436 } 437 } 438 439 err = ru.Update() 440 c.Assert(err, IsNil) 441 442 // after update, files should be identical 443 c.Check(osutil.FilesAreEqual(diskPath, expectedPath), Equals, true) 444 445 // rollback restores the original contents 446 err = ru.Rollback() 447 c.Assert(err, IsNil) 448 449 // which should match the pristine copy now 450 c.Check(osutil.FilesAreEqual(diskPath, pristinePath), Equals, true) 451 } 452 453 func (r *rawTestSuite) TestRawUpdaterBackupUpdateRestoreNoPartition(c *C) { 454 diskPath := filepath.Join(r.dir, "disk.img") 455 456 mutateFile(c, diskPath, gadget.SizeMiB+2048, []mutateWrite{ 457 {[]byte("baz baz baz"), int64(gadget.SizeMiB)}, 458 {[]byte("oof oof oof"), int64(gadget.SizeMiB + 1024)}, 459 }) 460 461 pristinePath := filepath.Join(r.dir, "pristine.img") 462 err := osutil.CopyFile(diskPath, pristinePath, 0) 463 c.Assert(err, IsNil) 464 465 expectedPath := filepath.Join(r.dir, "expected.img") 466 mutateFile(c, expectedPath, gadget.SizeMiB+2048, []mutateWrite{ 467 {[]byte("zzz zzz zzz zzz"), int64(gadget.SizeMiB)}, 468 {[]byte("xxx xxx xxx xxx"), int64(gadget.SizeMiB + 1024)}, 469 }) 470 471 makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("zzz zzz zzz zzz")) 472 makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 256, []byte("xxx xxx xxx xxx")) 473 ps := &gadget.LaidOutStructure{ 474 VolumeStructure: &gadget.VolumeStructure{ 475 // No partition table entry, would trigger fallback lookup path. 476 Type: "bare", 477 Size: 2048, 478 }, 479 StartOffset: 1 * gadget.SizeMiB, 480 LaidOutContent: []gadget.LaidOutContent{ 481 { 482 VolumeContent: &gadget.VolumeContent{ 483 Image: "foo.img", 484 }, 485 StartOffset: 1 * gadget.SizeMiB, 486 Size: 128, 487 }, { 488 VolumeContent: &gadget.VolumeContent{ 489 Image: "bar.img", 490 }, 491 StartOffset: 1*gadget.SizeMiB + 1024, 492 Size: 256, 493 Index: 1, 494 }, 495 }, 496 } 497 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 498 c.Check(to, DeepEquals, ps) 499 // No partition table, returned path corresponds to a disk, start offset is non-0. 500 return diskPath, ps.StartOffset, nil 501 }) 502 c.Assert(err, IsNil) 503 c.Assert(ru, NotNil) 504 505 err = ru.Backup() 506 c.Assert(err, IsNil) 507 508 for _, e := range []struct { 509 path string 510 size int64 511 }{ 512 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0]) + ".backup", 128}, 513 {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".backup", 256}, 514 } { 515 c.Check(e.path, testutil.FilePresent) 516 c.Check(getFileSize(c, e.path), Equals, e.size) 517 } 518 519 err = ru.Update() 520 c.Assert(err, IsNil) 521 522 // After update, files should be identical. 523 c.Check(osutil.FilesAreEqual(diskPath, expectedPath), Equals, true) 524 525 // Rollback restores the original contents. 526 err = ru.Rollback() 527 c.Assert(err, IsNil) 528 529 // Which should match the pristine copy now. 530 c.Check(osutil.FilesAreEqual(diskPath, pristinePath), Equals, true) 531 } 532 533 func (r *rawTestSuite) TestRawUpdaterBackupErrors(c *C) { 534 diskPath := filepath.Join(r.dir, "disk.img") 535 ps := &gadget.LaidOutStructure{ 536 VolumeStructure: &gadget.VolumeStructure{ 537 Size: 2048, 538 }, 539 StartOffset: 0, 540 LaidOutContent: []gadget.LaidOutContent{ 541 { 542 VolumeContent: &gadget.VolumeContent{ 543 Image: "foo.img", 544 }, 545 StartOffset: 128, 546 Size: 128, 547 }, 548 }, 549 } 550 551 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 552 c.Check(to, DeepEquals, ps) 553 return diskPath, 0, nil 554 }) 555 c.Assert(err, IsNil) 556 c.Assert(ru, NotNil) 557 558 err = ru.Backup() 559 c.Assert(err, ErrorMatches, "cannot open device for reading: .*") 560 c.Check(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup", testutil.FileAbsent) 561 562 // 0 sized disk, copying will fail with early EOF 563 makeSizedFile(c, diskPath, 0, nil) 564 565 err = ru.Backup() 566 c.Assert(err, ErrorMatches, "cannot backup image .*: cannot backup original image: EOF") 567 c.Check(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup", testutil.FileAbsent) 568 569 // make proper disk image now 570 err = os.Remove(diskPath) 571 c.Assert(err, IsNil) 572 makeSizedFile(c, diskPath, 2048, nil) 573 574 err = ru.Backup() 575 c.Assert(err, ErrorMatches, "cannot backup image .*: cannot checksum update image: .*") 576 c.Check(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup", testutil.FileAbsent) 577 } 578 579 func (r *rawTestSuite) TestRawUpdaterBackupIdempotent(c *C) { 580 diskPath := filepath.Join(r.dir, "disk.img") 581 // 0 sized disk, copying will fail with early EOF 582 makeSizedFile(c, diskPath, 0, nil) 583 584 ps := &gadget.LaidOutStructure{ 585 VolumeStructure: &gadget.VolumeStructure{ 586 Size: 2048, 587 }, 588 StartOffset: 0, 589 LaidOutContent: []gadget.LaidOutContent{ 590 { 591 VolumeContent: &gadget.VolumeContent{ 592 Image: "foo.img", 593 }, 594 StartOffset: 128, 595 Size: 128, 596 }, 597 }, 598 } 599 600 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 601 c.Check(to, DeepEquals, ps) 602 return diskPath, 0, nil 603 }) 604 c.Assert(err, IsNil) 605 c.Assert(ru, NotNil) 606 607 contentBackupBasePath := gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0]) 608 // mock content backed-up marker 609 makeSizedFile(c, contentBackupBasePath+".backup", 0, nil) 610 611 // never reached copy, hence no error 612 err = ru.Backup() 613 c.Assert(err, IsNil) 614 615 err = os.Remove(contentBackupBasePath + ".backup") 616 c.Assert(err, IsNil) 617 618 // mock content is-identical marker 619 makeSizedFile(c, contentBackupBasePath+".same", 0, nil) 620 // never reached copy, hence no error 621 err = ru.Backup() 622 c.Assert(err, IsNil) 623 } 624 625 func (r *rawTestSuite) TestRawUpdaterFindDeviceFailed(c *C) { 626 ps := &gadget.LaidOutStructure{ 627 VolumeStructure: &gadget.VolumeStructure{ 628 Size: 2048, 629 }, 630 StartOffset: 0, 631 LaidOutContent: []gadget.LaidOutContent{ 632 { 633 VolumeContent: &gadget.VolumeContent{ 634 Image: "foo.img", 635 }, 636 StartOffset: 128, 637 Size: 128, 638 }, 639 }, 640 } 641 642 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, nil) 643 c.Assert(err, ErrorMatches, "internal error: device lookup helper must be provided") 644 c.Assert(ru, IsNil) 645 646 ru, err = gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 647 c.Check(to, DeepEquals, ps) 648 return "", 0, errors.New("failed") 649 }) 650 c.Assert(err, IsNil) 651 c.Assert(ru, NotNil) 652 653 err = ru.Backup() 654 c.Assert(err, ErrorMatches, "cannot find device matching structure #0: failed") 655 656 err = ru.Update() 657 c.Assert(err, ErrorMatches, "cannot find device matching structure #0: failed") 658 659 err = ru.Rollback() 660 c.Assert(err, ErrorMatches, "cannot find device matching structure #0: failed") 661 } 662 663 func (r *rawTestSuite) TestRawUpdaterRollbackErrors(c *C) { 664 diskPath := filepath.Join(r.dir, "disk.img") 665 // 0 sized disk, copying will fail with early EOF 666 makeSizedFile(c, diskPath, 0, nil) 667 668 ps := &gadget.LaidOutStructure{ 669 VolumeStructure: &gadget.VolumeStructure{ 670 Size: 2048, 671 }, 672 StartOffset: 0, 673 LaidOutContent: []gadget.LaidOutContent{ 674 { 675 VolumeContent: &gadget.VolumeContent{ 676 Image: "foo.img", 677 }, 678 StartOffset: 128, 679 Size: 128, 680 }, 681 }, 682 } 683 684 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 685 c.Check(to, DeepEquals, ps) 686 return diskPath, 0, nil 687 }) 688 c.Assert(err, IsNil) 689 c.Assert(ru, NotNil) 690 691 err = ru.Rollback() 692 c.Assert(err, ErrorMatches, `cannot rollback image #0 \("foo.img"@0x80\{128\}\): cannot open backup image: .*no such file or directory`) 693 694 contentBackupPath := gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0]) + ".backup" 695 696 // trigger short read 697 makeSizedFile(c, contentBackupPath, 0, nil) 698 699 err = ru.Rollback() 700 c.Assert(err, ErrorMatches, `cannot rollback image #0 \("foo.img"@0x80\{128\}\): cannot restore backup: cannot write image: EOF`) 701 702 // pretend device cannot be opened for writing 703 err = os.Chmod(diskPath, 0000) 704 c.Assert(err, IsNil) 705 err = ru.Rollback() 706 c.Assert(err, ErrorMatches, "cannot open device for writing: .* permission denied") 707 } 708 709 func (r *rawTestSuite) TestRawUpdaterUpdateErrors(c *C) { 710 diskPath := filepath.Join(r.dir, "disk.img") 711 // 0 sized disk, copying will fail with early EOF 712 makeSizedFile(c, diskPath, 2048, nil) 713 714 ps := &gadget.LaidOutStructure{ 715 VolumeStructure: &gadget.VolumeStructure{ 716 Size: 2048, 717 }, 718 StartOffset: 0, 719 LaidOutContent: []gadget.LaidOutContent{ 720 { 721 VolumeContent: &gadget.VolumeContent{ 722 Image: "foo.img", 723 }, 724 StartOffset: 128, 725 Size: 128, 726 }, 727 }, 728 } 729 730 ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 731 c.Check(to, DeepEquals, ps) 732 return diskPath, 0, nil 733 }) 734 c.Assert(err, IsNil) 735 c.Assert(ru, NotNil) 736 737 // backup/analysis not performed 738 err = ru.Update() 739 c.Assert(err, ErrorMatches, `cannot update image #0 \("foo.img"@0x80\{128\}\): missing backup file`) 740 741 // pretend backup was done 742 makeSizedFile(c, gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup", 0, nil) 743 744 err = ru.Update() 745 c.Assert(err, ErrorMatches, `cannot update image #0 \("foo.img"@0x80\{128\}\).*: cannot open image file: .*no such file or directory`) 746 747 makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 0, nil) 748 err = ru.Update() 749 c.Assert(err, ErrorMatches, `cannot update image #0 \("foo.img"@0x80\{128\}\).*: cannot write image: EOF`) 750 751 // pretend device cannot be opened for writing 752 err = os.Chmod(diskPath, 0000) 753 c.Assert(err, IsNil) 754 err = ru.Update() 755 c.Assert(err, ErrorMatches, "cannot open device for writing: .* permission denied") 756 } 757 758 func (r *rawTestSuite) TestRawUpdaterContentBackupPath(c *C) { 759 ps := &gadget.LaidOutStructure{ 760 VolumeStructure: &gadget.VolumeStructure{}, 761 StartOffset: 0, 762 LaidOutContent: []gadget.LaidOutContent{ 763 { 764 VolumeContent: &gadget.VolumeContent{}, 765 }, 766 }, 767 } 768 pc := &ps.LaidOutContent[0] 769 770 p := gadget.RawContentBackupPath(r.backup, ps, pc) 771 c.Assert(p, Equals, r.backup+"/struct-0-0") 772 pc.Index = 5 773 p = gadget.RawContentBackupPath(r.backup, ps, pc) 774 c.Assert(p, Equals, r.backup+"/struct-0-5") 775 ps.Index = 9 776 p = gadget.RawContentBackupPath(r.backup, ps, pc) 777 c.Assert(p, Equals, r.backup+"/struct-9-5") 778 } 779 780 func (r *rawTestSuite) TestRawUpdaterInternalErrors(c *C) { 781 ps := &gadget.LaidOutStructure{ 782 VolumeStructure: &gadget.VolumeStructure{ 783 Size: 2048, 784 }, 785 } 786 787 f := func(to *gadget.LaidOutStructure) (string, gadget.Size, error) { 788 return "", 0, errors.New("unexpected call") 789 } 790 rw, err := gadget.NewRawStructureUpdater("", ps, r.backup, f) 791 c.Assert(err, ErrorMatches, "internal error: gadget content directory cannot be unset") 792 c.Assert(rw, IsNil) 793 794 rw, err = gadget.NewRawStructureUpdater(r.dir, nil, r.backup, f) 795 c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`) 796 c.Assert(rw, IsNil) 797 798 rw, err = gadget.NewRawStructureUpdater(r.dir, ps, "", f) 799 c.Assert(err, ErrorMatches, "internal error: backup directory cannot be unset") 800 c.Assert(rw, IsNil) 801 802 rw, err = gadget.NewRawStructureUpdater(r.dir, ps, r.backup, nil) 803 c.Assert(err, ErrorMatches, "internal error: device lookup helper must be provided") 804 c.Assert(rw, IsNil) 805 }