github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/gadget/install/partition_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 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 install_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"path/filepath"
    27  	"testing"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/gadget"
    34  	"github.com/snapcore/snapd/gadget/install"
    35  	"github.com/snapcore/snapd/gadget/quantity"
    36  	"github.com/snapcore/snapd/testutil"
    37  )
    38  
    39  func TestInstall(t *testing.T) { TestingT(t) }
    40  
    41  type partitionTestSuite struct {
    42  	testutil.BaseTest
    43  
    44  	dir        string
    45  	gadgetRoot string
    46  	cmdPartx   *testutil.MockCmd
    47  }
    48  
    49  var _ = Suite(&partitionTestSuite{})
    50  
    51  func (s *partitionTestSuite) SetUpTest(c *C) {
    52  	s.BaseTest.SetUpTest(c)
    53  
    54  	s.dir = c.MkDir()
    55  	s.gadgetRoot = filepath.Join(c.MkDir(), "gadget")
    56  
    57  	s.cmdPartx = testutil.MockCommand(c, "partx", "")
    58  	s.AddCleanup(s.cmdPartx.Restore)
    59  
    60  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", `echo "sfdisk was not mocked"; exit 1`)
    61  	s.AddCleanup(cmdSfdisk.Restore)
    62  	cmdLsblk := testutil.MockCommand(c, "lsblk", `echo "lsblk was not mocked"; exit 1`)
    63  	s.AddCleanup(cmdLsblk.Restore)
    64  }
    65  
    66  const (
    67  	scriptPartitionsBios = iota
    68  	scriptPartitionsBiosSeed
    69  	scriptPartitionsBiosSeedData
    70  )
    71  
    72  func makeSfdiskScript(num int) string {
    73  	var b bytes.Buffer
    74  
    75  	b.WriteString(`
    76  >&2 echo "Some warning from sfdisk"
    77  echo '{
    78    "partitiontable": {
    79      "label": "gpt",
    80      "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
    81      "device": "/dev/node",
    82      "unit": "sectors",
    83      "firstlba": 34,
    84      "lastlba": 8388574,
    85      "partitions": [`)
    86  
    87  	// BIOS boot partition
    88  	if num >= scriptPartitionsBios {
    89  		b.WriteString(`
    90        {
    91          "node": "/dev/node1",
    92          "start": 2048,
    93          "size": 2048,
    94          "type": "21686148-6449-6E6F-744E-656564454649",
    95          "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F",
    96          "name": "BIOS Boot"
    97        }`)
    98  	}
    99  
   100  	// Seed partition
   101  	if num >= scriptPartitionsBiosSeed {
   102  		b.WriteString(`,
   103        {
   104          "node": "/dev/node2",
   105          "start": 4096,
   106          "size": 2457600,
   107          "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
   108          "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F",
   109          "name": "Recovery"
   110        }`)
   111  	}
   112  
   113  	// Data partition
   114  	if num >= scriptPartitionsBiosSeedData {
   115  		b.WriteString(`,
   116        {
   117          "node": "/dev/node3",
   118          "start": 2461696,
   119          "size": 2457600,
   120          "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
   121          "uuid": "f940029d-bfbb-4887-9d44-321e85c63866",
   122          "name": "Writable"
   123        }`)
   124  	}
   125  
   126  	b.WriteString(`
   127      ]
   128    }
   129  }'`)
   130  	return b.String()
   131  }
   132  
   133  func makeLsblkScript(num int) string {
   134  	var b bytes.Buffer
   135  
   136  	// BIOS boot partition
   137  	if num >= scriptPartitionsBios {
   138  		b.WriteString(`
   139  [ "$3" == "/dev/node1" ] && echo '{
   140      "blockdevices": [ {"name": "node1", "fstype": null, "label": null, "uuid": null, "mountpoint": null} ]
   141  }'`)
   142  	}
   143  
   144  	// Seed partition
   145  	if num >= scriptPartitionsBiosSeed {
   146  		b.WriteString(`
   147  [ "$3" == "/dev/node2" ] && echo '{
   148      "blockdevices": [ {"name": "node2", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "A644-B807", "mountpoint": null} ]
   149  }'`)
   150  	}
   151  
   152  	// Data partition
   153  	if num >= scriptPartitionsBiosSeedData {
   154  		b.WriteString(`
   155  [ "$3" == "/dev/node3" ] && echo '{
   156      "blockdevices": [ {"name": "node3", "fstype": "ext4", "label": "ubuntu-data", "uuid": "8781-433a", "mountpoint": null} ]
   157  }'`)
   158  	}
   159  
   160  	b.WriteString(`
   161  exit 0`)
   162  
   163  	return b.String()
   164  }
   165  
   166  var mockOnDiskStructureWritable = gadget.OnDiskStructure{
   167  	Node: "/dev/node3",
   168  	LaidOutStructure: gadget.LaidOutStructure{
   169  		VolumeStructure: &gadget.VolumeStructure{
   170  			Name:       "Writable",
   171  			Size:       1258291200,
   172  			Type:       "83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
   173  			Role:       "system-data",
   174  			Label:      "ubuntu-data",
   175  			Filesystem: "ext4",
   176  		},
   177  		StartOffset: 1260388352,
   178  		Index:       3,
   179  	},
   180  	// expanded to fill the disk
   181  	Size: 2*quantity.SizeGiB + 845*quantity.SizeMiB + 1031680,
   182  }
   183  
   184  var mockOnDiskStructureSave = gadget.OnDiskStructure{
   185  	Node: "/dev/node3",
   186  	LaidOutStructure: gadget.LaidOutStructure{
   187  		VolumeStructure: &gadget.VolumeStructure{
   188  			Name:       "Save",
   189  			Size:       128 * quantity.SizeMiB,
   190  			Type:       "83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
   191  			Role:       "system-save",
   192  			Label:      "ubuntu-save",
   193  			Filesystem: "ext4",
   194  		},
   195  		StartOffset: 1260388352,
   196  		Index:       3,
   197  	},
   198  	Size: 128 * quantity.SizeMiB,
   199  }
   200  
   201  var mockOnDiskStructureWritableAfterSave = gadget.OnDiskStructure{
   202  	Node: "/dev/node4",
   203  	LaidOutStructure: gadget.LaidOutStructure{
   204  		VolumeStructure: &gadget.VolumeStructure{
   205  			Name:       "Writable",
   206  			Size:       1200 * quantity.SizeMiB,
   207  			Type:       "83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
   208  			Role:       "system-data",
   209  			Label:      "ubuntu-data",
   210  			Filesystem: "ext4",
   211  		},
   212  		StartOffset: 1394606080,
   213  		Index:       4,
   214  	},
   215  	// expanded to fill the disk
   216  	Size: 2*quantity.SizeGiB + 717*quantity.SizeMiB + 1031680,
   217  }
   218  
   219  func (s *partitionTestSuite) TestBuildPartitionList(c *C) {
   220  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", makeSfdiskScript(scriptPartitionsBiosSeed))
   221  	defer cmdSfdisk.Restore()
   222  
   223  	cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBiosSeed))
   224  	defer cmdLsblk.Restore()
   225  
   226  	err := makeMockGadget(s.gadgetRoot, gptGadgetContentWithSave)
   227  	c.Assert(err, IsNil)
   228  	pv, err := gadget.LaidOutVolumeFromGadget(s.gadgetRoot, uc20mod)
   229  	c.Assert(err, IsNil)
   230  
   231  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   232  	c.Assert(err, IsNil)
   233  
   234  	// the expected expanded writable partition size is:
   235  	// start offset = (2M + 1200M), expanded size in sectors = (8388575*512 - start offset)/512
   236  	sfdiskInput, create := install.BuildPartitionList(dl, pv)
   237  	c.Assert(sfdiskInput.String(), Equals,
   238  		`/dev/node3 : start=     2461696, size=      262144, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="Save"
   239  /dev/node4 : start=     2723840, size=     5664735, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="Writable"
   240  `)
   241  	c.Check(create, NotNil)
   242  	c.Assert(create, DeepEquals, []gadget.OnDiskStructure{mockOnDiskStructureSave, mockOnDiskStructureWritableAfterSave})
   243  }
   244  
   245  type uc20Constraints struct{}
   246  
   247  func (c uc20Constraints) Classic() bool             { return false }
   248  func (c uc20Constraints) Grade() asserts.ModelGrade { return asserts.ModelSigned }
   249  
   250  var uc20mod = uc20Constraints{}
   251  
   252  func (s *partitionTestSuite) TestCreatePartitions(c *C) {
   253  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", makeSfdiskScript(scriptPartitionsBiosSeed))
   254  	defer cmdSfdisk.Restore()
   255  
   256  	cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBiosSeed))
   257  	defer cmdLsblk.Restore()
   258  
   259  	calls := 0
   260  	restore := install.MockEnsureNodesExist(func(ds []gadget.OnDiskStructure, timeout time.Duration) error {
   261  		calls++
   262  		c.Assert(ds, HasLen, 1)
   263  		c.Assert(ds[0].Node, Equals, "/dev/node3")
   264  		return nil
   265  	})
   266  	defer restore()
   267  
   268  	err := makeMockGadget(s.gadgetRoot, gadgetContent)
   269  	c.Assert(err, IsNil)
   270  	pv, err := gadget.LaidOutVolumeFromGadget(s.gadgetRoot, uc20mod)
   271  	c.Assert(err, IsNil)
   272  
   273  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   274  	c.Assert(err, IsNil)
   275  	created, err := install.CreateMissingPartitions(dl, pv)
   276  	c.Assert(err, IsNil)
   277  	c.Assert(created, DeepEquals, []gadget.OnDiskStructure{mockOnDiskStructureWritable})
   278  	c.Assert(calls, Equals, 1)
   279  
   280  	// Check partition table read and write
   281  	c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
   282  		{"sfdisk", "--json", "/dev/node"},
   283  		{"sfdisk", "--append", "--no-reread", "/dev/node"},
   284  	})
   285  
   286  	// Check partition table update
   287  	c.Assert(s.cmdPartx.Calls(), DeepEquals, [][]string{
   288  		{"partx", "-u", "/dev/node"},
   289  	})
   290  }
   291  
   292  func (s *partitionTestSuite) TestRemovePartitionsTrivial(c *C) {
   293  	// no locally created partitions
   294  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", makeSfdiskScript(scriptPartitionsBios))
   295  	defer cmdSfdisk.Restore()
   296  
   297  	cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBios))
   298  	defer cmdLsblk.Restore()
   299  
   300  	err := makeMockGadget(s.gadgetRoot, gadgetContent)
   301  	c.Assert(err, IsNil)
   302  	pv, err := gadget.LaidOutVolumeFromGadget(s.gadgetRoot, uc20mod)
   303  	c.Assert(err, IsNil)
   304  
   305  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   306  	c.Assert(err, IsNil)
   307  
   308  	err = install.RemoveCreatedPartitions(pv, dl)
   309  	c.Assert(err, IsNil)
   310  
   311  	c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
   312  		{"sfdisk", "--json", "/dev/node"},
   313  	})
   314  
   315  	c.Assert(cmdLsblk.Calls(), DeepEquals, [][]string{
   316  		{"lsblk", "--fs", "--json", "/dev/node1"},
   317  	})
   318  }
   319  
   320  func (s *partitionTestSuite) TestRemovePartitions(c *C) {
   321  	const mockSfdiskScriptRemovablePartition = `
   322  if [ -f %[1]s/2 ]; then
   323     rm %[1]s/[0-9]
   324  elif [ -f %[1]s/1 ]; then
   325     touch %[1]s/2
   326     exit 0
   327  else
   328     PART=',
   329     {"node": "/dev/node2", "start": 4096, "size": 2457600, "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4", "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F", "name": "Recovery"},
   330     {"node": "/dev/node3", "start": 2461696, "size": 2457600, "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4", "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F", "name": "Recovery"}
   331     '
   332     touch %[1]s/1
   333  fi
   334  echo '{
   335     "partitiontable": {
   336        "label": "gpt",
   337        "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
   338        "device": "/dev/node",
   339        "unit": "sectors",
   340        "firstlba": 34,
   341        "lastlba": 8388574,
   342        "partitions": [
   343           {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"}
   344           '"$PART
   345        ]
   346     }
   347  }"`
   348  
   349  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", fmt.Sprintf(mockSfdiskScriptRemovablePartition, s.dir))
   350  	defer cmdSfdisk.Restore()
   351  
   352  	cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBiosSeedData))
   353  	defer cmdLsblk.Restore()
   354  
   355  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   356  	c.Assert(err, IsNil)
   357  
   358  	c.Assert(cmdLsblk.Calls(), DeepEquals, [][]string{
   359  		{"lsblk", "--fs", "--json", "/dev/node1"},
   360  		{"lsblk", "--fs", "--json", "/dev/node2"},
   361  		{"lsblk", "--fs", "--json", "/dev/node3"},
   362  	})
   363  
   364  	err = makeMockGadget(s.gadgetRoot, gadgetContent)
   365  	c.Assert(err, IsNil)
   366  	pv, err := gadget.LaidOutVolumeFromGadget(s.gadgetRoot, uc20mod)
   367  	c.Assert(err, IsNil)
   368  
   369  	err = install.RemoveCreatedPartitions(pv, dl)
   370  	c.Assert(err, IsNil)
   371  
   372  	c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
   373  		{"sfdisk", "--json", "/dev/node"},
   374  		{"sfdisk", "--no-reread", "--delete", "/dev/node", "3"},
   375  		{"sfdisk", "--json", "/dev/node"},
   376  	})
   377  }
   378  
   379  func (s *partitionTestSuite) TestRemovePartitionsError(c *C) {
   380  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", makeSfdiskScript(scriptPartitionsBiosSeedData))
   381  	defer cmdSfdisk.Restore()
   382  
   383  	cmdLsblk := testutil.MockCommand(c, "lsblk", makeLsblkScript(scriptPartitionsBiosSeedData))
   384  	defer cmdLsblk.Restore()
   385  
   386  	dl, err := gadget.OnDiskVolumeFromDevice("node")
   387  	c.Assert(err, IsNil)
   388  
   389  	err = makeMockGadget(s.gadgetRoot, gadgetContent)
   390  	c.Assert(err, IsNil)
   391  	pv, err := gadget.LaidOutVolumeFromGadget(s.gadgetRoot, uc20mod)
   392  	c.Assert(err, IsNil)
   393  
   394  	err = install.RemoveCreatedPartitions(pv, dl)
   395  	c.Assert(err, ErrorMatches, "cannot remove partitions: /dev/node3")
   396  }
   397  
   398  func (s *partitionTestSuite) TestEnsureNodesExist(c *C) {
   399  	const mockUdevadmScript = `err=%q; echo "$err"; [ -n "$err" ] && exit 1 || exit 0`
   400  	for _, tc := range []struct {
   401  		utErr string
   402  		err   string
   403  	}{
   404  		{utErr: "", err: ""},
   405  		{utErr: "some error", err: "some error"},
   406  	} {
   407  		c.Logf("utErr:%q err:%q", tc.utErr, tc.err)
   408  
   409  		node := filepath.Join(c.MkDir(), "node")
   410  		err := ioutil.WriteFile(node, nil, 0644)
   411  		c.Assert(err, IsNil)
   412  
   413  		cmdUdevadm := testutil.MockCommand(c, "udevadm", fmt.Sprintf(mockUdevadmScript, tc.utErr))
   414  		defer cmdUdevadm.Restore()
   415  
   416  		ds := []gadget.OnDiskStructure{{Node: node}}
   417  		err = install.EnsureNodesExist(ds, 10*time.Millisecond)
   418  		if tc.err == "" {
   419  			c.Assert(err, IsNil)
   420  		} else {
   421  			c.Assert(err, ErrorMatches, tc.err)
   422  		}
   423  
   424  		c.Assert(cmdUdevadm.Calls(), DeepEquals, [][]string{
   425  			{"udevadm", "trigger", "--settle", node},
   426  		})
   427  	}
   428  }
   429  
   430  func (s *partitionTestSuite) TestEnsureNodesExistTimeout(c *C) {
   431  	cmdUdevadm := testutil.MockCommand(c, "udevadm", "")
   432  	defer cmdUdevadm.Restore()
   433  
   434  	node := filepath.Join(c.MkDir(), "node")
   435  	ds := []gadget.OnDiskStructure{{Node: node}}
   436  	t := time.Now()
   437  	timeout := 1 * time.Second
   438  	err := install.EnsureNodesExist(ds, timeout)
   439  	c.Assert(err, ErrorMatches, fmt.Sprintf("device %s not available", node))
   440  	c.Assert(time.Since(t) >= timeout, Equals, true)
   441  	c.Assert(cmdUdevadm.Calls(), HasLen, 0)
   442  }
   443  
   444  const gptGadgetContentWithSave = `volumes:
   445    pc:
   446      bootloader: grub
   447      structure:
   448        - name: mbr
   449          type: mbr
   450          size: 440
   451          content:
   452            - image: pc-boot.img
   453        - name: BIOS Boot
   454          type: DA,21686148-6449-6E6F-744E-656564454649
   455          size: 1M
   456          offset: 1M
   457          offset-write: mbr+92
   458          content:
   459            - image: pc-core.img
   460        - name: Recovery
   461          role: system-seed
   462          filesystem: vfat
   463          # UEFI will boot the ESP partition by default first
   464          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   465          size: 1200M
   466          content:
   467            - source: grubx64.efi
   468              target: EFI/boot/grubx64.efi
   469        - name: Save
   470          role: system-save
   471          filesystem: ext4
   472          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   473          size: 128M
   474        - name: Writable
   475          role: system-data
   476          filesystem: ext4
   477          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   478          size: 1200M
   479  `
   480  
   481  func (s *partitionTestSuite) TestCreatedDuringInstallGPT(c *C) {
   482  	cmdLsblk := testutil.MockCommand(c, "lsblk", `
   483  case $3 in
   484  	/dev/node1)
   485  		echo '{ "blockdevices": [ {"fstype":"ext4", "label":null} ] }'
   486  		;;
   487  	/dev/node2)
   488  		echo '{ "blockdevices": [ {"fstype":"ext4", "label":"ubuntu-seed"} ] }'
   489  		;;
   490  	/dev/node3)
   491  		echo '{ "blockdevices": [ {"fstype":"ext4", "label":"ubuntu-save"} ] }'
   492  		;;
   493  	/dev/node4)
   494  		echo '{ "blockdevices": [ {"fstype":"ext4", "label":"ubuntu-data"} ] }'
   495  		;;
   496  	*)
   497  		echo "unexpected args: $*"
   498  		exit 1
   499  		;;
   500  esac
   501  `)
   502  	defer cmdLsblk.Restore()
   503  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", `
   504  echo '{
   505    "partitiontable": {
   506      "label": "gpt",
   507      "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
   508      "device": "/dev/node",
   509      "unit": "sectors",
   510      "firstlba": 34,
   511      "lastlba": 8388574,
   512      "partitions": [
   513       {
   514           "node": "/dev/node1",
   515           "start": 2048,
   516           "size": 2048,
   517           "type": "21686148-6449-6E6F-744E-656564454649",
   518           "uuid": "30a26851-4b08-4b8d-8aea-f686e723ed8c",
   519           "name": "BIOS boot partition"
   520       },
   521       {
   522           "node": "/dev/node2",
   523           "start": 4096,
   524           "size": 2457600,
   525           "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
   526           "uuid": "7ea3a75a-3f6d-4647-8134-89ae61fe88d5",
   527           "name": "Linux filesystem"
   528       },
   529       {
   530           "node": "/dev/node3",
   531           "start": 2461696,
   532           "size": 262144,
   533           "type": "0fc63daf-8483-4772-8e79-3d69d8477de4",
   534           "uuid": "641764aa-a680-4d36-a7ad-f7bd01fd8d12",
   535           "name": "Linux filesystem"
   536       },
   537       {
   538           "node": "/dev/node4",
   539           "start": 2723840,
   540           "size": 2457600,
   541           "type": "0fc63daf-8483-4772-8e79-3d69d8477de4",
   542           "uuid": "8ab3e8fd-d53d-4d72-9c5e-56146915fd07",
   543           "name": "Another Linux filesystem"
   544       }
   545       ]
   546    }
   547  }'
   548  `)
   549  	defer cmdSfdisk.Restore()
   550  
   551  	err := makeMockGadget(s.gadgetRoot, gptGadgetContentWithSave)
   552  	c.Assert(err, IsNil)
   553  	pv, err := gadget.LaidOutVolumeFromGadget(s.gadgetRoot, uc20mod)
   554  	c.Assert(err, IsNil)
   555  
   556  	dl, err := gadget.OnDiskVolumeFromDevice("node")
   557  	c.Assert(err, IsNil)
   558  
   559  	list := install.CreatedDuringInstall(pv, dl)
   560  	// only save and writable should show up
   561  	c.Check(list, DeepEquals, []string{"/dev/node3", "/dev/node4"})
   562  }
   563  
   564  // this is an mbr gadget like the pi, but doesn't have the amd64 mbr structure
   565  // so it's probably not representative, but still useful for unit tests here
   566  const mbrGadgetContentWithSave = `volumes:
   567    pc:
   568      schema: mbr
   569      bootloader: grub
   570      structure:
   571        - name: Recovery
   572          role: system-seed
   573          filesystem: vfat
   574          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   575          offset: 2M
   576          size: 1200M
   577          content:
   578            - source: grubx64.efi
   579              target: EFI/boot/grubx64.efi
   580        - name: Boot
   581          role: system-boot
   582          filesystem: ext4
   583          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   584          size: 1200M
   585        - name: Save
   586          role: system-save
   587          filesystem: ext4
   588          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   589          size: 128M
   590        - name: Writable
   591          role: system-data
   592          filesystem: ext4
   593          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   594          size: 1200M
   595  `
   596  
   597  func (s *partitionTestSuite) TestCreatedDuringInstallMBR(c *C) {
   598  	cmdLsblk := testutil.MockCommand(c, "lsblk", `
   599  what=
   600  shift 2
   601  case "$1" in
   602     /dev/node1)
   603        what='{"name": "node1", "fstype":"ext4", "label":"ubuntu-seed"}'
   604        ;;
   605     /dev/node2)
   606        what='{"name": "node2", "fstype":"vfat", "label":"ubuntu-boot"}'
   607        ;;
   608     /dev/node3)
   609        what='{"name": "node3", "fstype":"ext4", "label":"ubuntu-save"}'
   610        ;;
   611     /dev/node4)
   612        what='{"name": "node4", "fstype":"ext4", "label":"ubuntu-data"}'
   613        ;;
   614    *)
   615      echo "unexpected call"
   616      exit 1
   617  esac
   618  
   619  cat <<EOF
   620  {
   621  "blockdevices": [
   622     $what
   623    ]
   624  }
   625  EOF`)
   626  	defer cmdLsblk.Restore()
   627  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", `
   628  echo '{
   629    "partitiontable": {
   630      "label": "dos",
   631      "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
   632      "device": "/dev/node",
   633      "unit": "sectors",
   634      "firstlba": 0,
   635      "lastlba": 8388574,
   636      "partitions": [
   637       {
   638           "node": "/dev/node1",
   639           "start": 0,
   640           "size": 2460672,
   641           "type": "0a"
   642       },
   643       {
   644           "node": "/dev/node2",
   645           "start": 2461696,
   646           "size": 2460672,
   647           "type": "b"
   648       },
   649       {
   650           "node": "/dev/node3",
   651           "start": 4919296,
   652           "size": 262144,
   653           "type": "c"
   654       },
   655       {
   656           "node": "/dev/node4",
   657           "start": 5181440,
   658           "size": 2460672,
   659           "type": "0d"
   660       }
   661       ]
   662    }
   663  }'
   664  `)
   665  	defer cmdSfdisk.Restore()
   666  	cmdBlockdev := testutil.MockCommand(c, "blockdev", `echo '1234567'`)
   667  	defer cmdBlockdev.Restore()
   668  
   669  	dl, err := gadget.OnDiskVolumeFromDevice("node")
   670  	c.Assert(err, IsNil)
   671  
   672  	err = makeMockGadget(s.gadgetRoot, mbrGadgetContentWithSave)
   673  	c.Assert(err, IsNil)
   674  	pv, err := gadget.LaidOutVolumeFromGadget(s.gadgetRoot, uc20mod)
   675  	c.Assert(err, IsNil)
   676  
   677  	list := install.CreatedDuringInstall(pv, dl)
   678  	c.Assert(list, DeepEquals, []string{"/dev/node2", "/dev/node3", "/dev/node4"})
   679  }