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