github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  }