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