gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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  }