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