github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/gadget/ondisk_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 gadget_test
    21  
    22  import (
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/gadget"
    30  	"github.com/snapcore/snapd/gadget/quantity"
    31  	"github.com/snapcore/snapd/testutil"
    32  )
    33  
    34  type ondiskTestSuite struct {
    35  	testutil.BaseTest
    36  
    37  	dir string
    38  
    39  	gadgetRoot string
    40  }
    41  
    42  var _ = Suite(&ondiskTestSuite{})
    43  
    44  func (s *ondiskTestSuite) SetUpTest(c *C) {
    45  	s.BaseTest.SetUpTest(c)
    46  
    47  	s.dir = c.MkDir()
    48  
    49  	s.gadgetRoot = c.MkDir()
    50  	err := makeMockGadget(s.gadgetRoot, gadgetContent)
    51  	c.Assert(err, IsNil)
    52  }
    53  
    54  const mockSfdiskScriptBiosSeed = `
    55  >&2 echo "Some warning from sfdisk"
    56  echo '{
    57    "partitiontable": {
    58      "label": "gpt",
    59      "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
    60      "device": "/dev/node",
    61      "unit": "sectors",
    62      "firstlba": 34,
    63      "lastlba": 8388574,
    64      "partitions": [
    65        {
    66          "node": "/dev/node1",
    67          "start": 2048,
    68          "size": 2048,
    69          "type": "21686148-6449-6E6F-744E-656564454649",
    70          "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F",
    71          "name": "BIOS Boot"
    72        },
    73        {
    74          "node": "/dev/node2",
    75          "start": 4096,
    76          "size": 2457600,
    77          "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
    78          "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F",
    79          "name": "Recovery"
    80        }
    81      ]
    82    }
    83  }'`
    84  
    85  const mockLsblkScriptBiosSeed = `
    86  [ "$3" == "/dev/node1" ] && echo '{
    87      "blockdevices": [ {"name": "node1", "fstype": null, "label": null, "uuid": null, "mountpoint": null} ]
    88  }'
    89  [ "$3" == "/dev/node2" ] && echo '{
    90      "blockdevices": [ {"name": "node2", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "A644-B807", "mountpoint": null} ]
    91  }'
    92  exit 0`
    93  
    94  func makeMockGadget(gadgetRoot, gadgetContent string) error {
    95  	if err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755); err != nil {
    96  		return err
    97  	}
    98  	if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetContent), 0644); err != nil {
    99  		return err
   100  	}
   101  	if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "pc-boot.img"), []byte("pc-boot.img content"), 0644); err != nil {
   102  		return err
   103  	}
   104  	if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "pc-core.img"), []byte("pc-core.img content"), 0644); err != nil {
   105  		return err
   106  	}
   107  	if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "grubx64.efi"), []byte("grubx64.efi content"), 0644); err != nil {
   108  		return err
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  const gadgetContent = `volumes:
   115    pc:
   116      bootloader: grub
   117      structure:
   118        - name: mbr
   119          type: mbr
   120          size: 440
   121          content:
   122            - image: pc-boot.img
   123        - name: BIOS Boot
   124          type: DA,21686148-6449-6E6F-744E-656564454649
   125          size: 1M
   126          offset: 1M
   127          offset-write: mbr+92
   128          content:
   129            - image: pc-core.img
   130        - name: Recovery
   131          role: system-seed
   132          filesystem: vfat
   133          # UEFI will boot the ESP partition by default first
   134          type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   135          size: 1200M
   136          content:
   137            - source: grubx64.efi
   138              target: EFI/boot/grubx64.efi
   139        - name: Save
   140          role: system-save
   141          filesystem: ext4
   142          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   143          size: 128M
   144        - name: Writable
   145          role: system-data
   146          filesystem: ext4
   147          type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   148          size: 1200M
   149  `
   150  
   151  func (s *ondiskTestSuite) TestDeviceInfoGPT(c *C) {
   152  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBiosSeed)
   153  	defer cmdSfdisk.Restore()
   154  
   155  	cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScriptBiosSeed)
   156  	defer cmdLsblk.Restore()
   157  
   158  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   159  	c.Assert(err, IsNil)
   160  	c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
   161  		{"sfdisk", "--json", "/dev/node"},
   162  	})
   163  	c.Assert(cmdLsblk.Calls(), DeepEquals, [][]string{
   164  		{"lsblk", "--fs", "--json", "/dev/node1"},
   165  		{"lsblk", "--fs", "--json", "/dev/node2"},
   166  	})
   167  	c.Assert(err, IsNil)
   168  	c.Assert(dl.Schema, Equals, "gpt")
   169  	c.Assert(dl.ID, Equals, "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA")
   170  	c.Assert(dl.Device, Equals, "/dev/node")
   171  	c.Assert(dl.SectorSize, Equals, quantity.Size(512))
   172  	c.Assert(dl.Size, Equals, quantity.Size(8388575*512))
   173  	c.Assert(len(dl.Structure), Equals, 2)
   174  
   175  	c.Assert(dl.Structure, DeepEquals, []gadget.OnDiskStructure{
   176  		{
   177  			LaidOutStructure: gadget.LaidOutStructure{
   178  				VolumeStructure: &gadget.VolumeStructure{
   179  					Name:       "BIOS Boot",
   180  					Size:       0x100000,
   181  					Label:      "",
   182  					Type:       "21686148-6449-6E6F-744E-656564454649",
   183  					Filesystem: "",
   184  				},
   185  				StartOffset: 0x100000,
   186  				Index:       1,
   187  			},
   188  			Node: "/dev/node1",
   189  		},
   190  		{
   191  			LaidOutStructure: gadget.LaidOutStructure{
   192  				VolumeStructure: &gadget.VolumeStructure{
   193  					Name:       "Recovery",
   194  					Size:       0x4b000000,
   195  					Label:      "ubuntu-seed",
   196  					Type:       "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
   197  					Filesystem: "vfat",
   198  				},
   199  				StartOffset: 0x200000,
   200  				Index:       2,
   201  			},
   202  			Node: "/dev/node2",
   203  		},
   204  	})
   205  }
   206  
   207  func (s *ondiskTestSuite) TestDeviceInfoMBR(c *C) {
   208  	const mockSfdiskWithMBR = `
   209  >&2 echo "Some warning from sfdisk"
   210  echo '{
   211     "partitiontable": {
   212        "label": "dos",
   213        "device": "/dev/node",
   214        "unit": "sectors",
   215        "partitions": [
   216           {"node": "/dev/node1", "start": 4096, "size": 2457600, "type": "c"},
   217           {"node": "/dev/node2", "start": 2461696, "size": 1048576, "type": "d"},
   218           {"node": "/dev/node3", "start": 3510272, "size": 1048576, "type": "d"},
   219           {"node": "/dev/node4", "start": 4558848, "size": 1048576, "type": "d"}
   220        ]
   221     }
   222  }'`
   223  	const mockLsblkForMBR = `
   224  [ "$3" == "/dev/node1" ] && echo '{
   225      "blockdevices": [ {"name": "node1", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "A644-B807", "mountpoint": null} ]
   226  }'
   227  [ "$3" == "/dev/node2" ] && echo '{
   228      "blockdevices": [ {"name": "node2", "fstype": "vfat", "label": "ubuntu-boot", "uuid": "A644-B808", "mountpoint": null} ]
   229  }'
   230  [ "$3" == "/dev/node3" ] && echo '{
   231      "blockdevices": [ {"name": "node3", "fstype": "ext4", "label": "ubuntu-save", "mountpoint": null} ]
   232  }'
   233  [ "$3" == "/dev/node4" ] && echo '{
   234      "blockdevices": [ {"name": "node3", "fstype": "ext4", "label": "ubuntu-data", "mountpoint": null} ]
   235  }'
   236  exit 0`
   237  
   238  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskWithMBR)
   239  	defer cmdSfdisk.Restore()
   240  
   241  	cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkForMBR)
   242  	defer cmdLsblk.Restore()
   243  
   244  	cmdBlockdev := testutil.MockCommand(c, "blockdev", "echo 12345670")
   245  	defer cmdBlockdev.Restore()
   246  
   247  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   248  	c.Assert(err, IsNil)
   249  	c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
   250  		{"sfdisk", "--json", "/dev/node"},
   251  	})
   252  	c.Assert(cmdLsblk.Calls(), DeepEquals, [][]string{
   253  		{"lsblk", "--fs", "--json", "/dev/node1"},
   254  		{"lsblk", "--fs", "--json", "/dev/node2"},
   255  		{"lsblk", "--fs", "--json", "/dev/node3"},
   256  		{"lsblk", "--fs", "--json", "/dev/node4"},
   257  	})
   258  	c.Assert(cmdBlockdev.Calls(), DeepEquals, [][]string{
   259  		{"blockdev", "--getsz", "/dev/node"},
   260  	})
   261  	c.Assert(err, IsNil)
   262  	c.Assert(dl.ID, Equals, "")
   263  	c.Assert(dl.Schema, Equals, "dos")
   264  	c.Assert(dl.Device, Equals, "/dev/node")
   265  	c.Assert(dl.SectorSize, Equals, quantity.Size(512))
   266  	c.Assert(dl.Size, Equals, quantity.Size(12345670*512))
   267  	c.Assert(len(dl.Structure), Equals, 4)
   268  
   269  	c.Assert(dl.Structure, DeepEquals, []gadget.OnDiskStructure{
   270  		{
   271  			LaidOutStructure: gadget.LaidOutStructure{
   272  				VolumeStructure: &gadget.VolumeStructure{
   273  					Size:       0x4b000000,
   274  					Label:      "ubuntu-seed",
   275  					Type:       "0C",
   276  					Filesystem: "vfat",
   277  				},
   278  				StartOffset: 0x200000,
   279  				Index:       1,
   280  			},
   281  			Node: "/dev/node1",
   282  		},
   283  		{
   284  			LaidOutStructure: gadget.LaidOutStructure{
   285  				VolumeStructure: &gadget.VolumeStructure{
   286  					Size:       0x20000000,
   287  					Label:      "ubuntu-boot",
   288  					Type:       "0D",
   289  					Filesystem: "vfat",
   290  				},
   291  				StartOffset: 0x4b200000,
   292  				Index:       2,
   293  			},
   294  			Node: "/dev/node2",
   295  		},
   296  		{
   297  			LaidOutStructure: gadget.LaidOutStructure{
   298  				VolumeStructure: &gadget.VolumeStructure{
   299  					Size:       0x20000000,
   300  					Label:      "ubuntu-save",
   301  					Type:       "0D",
   302  					Filesystem: "ext4",
   303  				},
   304  				StartOffset: 0x6b200000,
   305  				Index:       3,
   306  			},
   307  			Node: "/dev/node3",
   308  		},
   309  		{
   310  			LaidOutStructure: gadget.LaidOutStructure{
   311  				VolumeStructure: &gadget.VolumeStructure{
   312  					Size:       0x20000000,
   313  					Label:      "ubuntu-data",
   314  					Type:       "0D",
   315  					Filesystem: "ext4",
   316  				},
   317  				StartOffset: 0x8b200000,
   318  				Index:       4,
   319  			},
   320  			Node: "/dev/node4",
   321  		},
   322  	})
   323  }
   324  
   325  func (s *ondiskTestSuite) TestDeviceInfoNotSectors(c *C) {
   326  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", `echo '{
   327     "partitiontable": {
   328        "label": "gpt",
   329        "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
   330        "device": "/dev/node",
   331        "unit": "not_sectors",
   332        "firstlba": 34,
   333        "lastlba": 8388574,
   334        "partitions": [
   335           {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"}
   336        ]
   337     }
   338  }'`)
   339  	defer cmdSfdisk.Restore()
   340  
   341  	_, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   342  	c.Assert(err, ErrorMatches, "cannot position partitions: unknown unit .*")
   343  }
   344  
   345  func (s *ondiskTestSuite) TestDeviceInfoFilesystemInfoError(c *C) {
   346  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", `echo '{
   347     "partitiontable": {
   348        "label": "gpt",
   349        "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
   350        "device": "/dev/node",
   351        "unit": "sectors",
   352        "firstlba": 34,
   353        "lastlba": 8388574,
   354        "partitions": [
   355           {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"}
   356        ]
   357     }
   358  }'`)
   359  	defer cmdSfdisk.Restore()
   360  
   361  	cmdLsblk := testutil.MockCommand(c, "lsblk", "echo lsblk error; exit 1")
   362  	defer cmdLsblk.Restore()
   363  
   364  	_, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   365  	c.Assert(err, ErrorMatches, "cannot obtain filesystem information: lsblk error")
   366  }
   367  
   368  func (s *ondiskTestSuite) TestDeviceInfoJsonError(c *C) {
   369  	cmd := testutil.MockCommand(c, "sfdisk", `echo 'This is not a json'`)
   370  	defer cmd.Restore()
   371  
   372  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   373  	c.Assert(err, ErrorMatches, "cannot parse sfdisk output: invalid .*")
   374  	c.Assert(dl, IsNil)
   375  }
   376  
   377  func (s *ondiskTestSuite) TestDeviceInfoError(c *C) {
   378  	cmd := testutil.MockCommand(c, "sfdisk", "echo 'sfdisk: not found'; exit 127")
   379  	defer cmd.Restore()
   380  
   381  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   382  	c.Assert(err, ErrorMatches, "sfdisk: not found")
   383  	c.Assert(dl, IsNil)
   384  }
   385  
   386  func (s *ondiskTestSuite) TestUpdatePartitionList(c *C) {
   387  	const mockSfdiskScriptBios = `
   388  >&2 echo "Some warning from sfdisk"
   389  echo '{
   390    "partitiontable": {
   391      "label": "gpt",
   392      "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
   393      "device": "/dev/node",
   394      "unit": "sectors",
   395      "firstlba": 34,
   396      "lastlba": 8388574,
   397      "partitions": [
   398        {
   399          "node": "/dev/node1",
   400          "start": 2048,
   401          "size": 2048,
   402          "type": "21686148-6449-6E6F-744E-656564454649",
   403          "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F",
   404          "name": "BIOS Boot"
   405        }
   406      ]
   407    }
   408  }'`
   409  
   410  	const mockLsblkScriptBios = `
   411  [ "$3" == "/dev/node1" ] && echo '{
   412      "blockdevices": [ {"name": "node1", "fstype": null, "label": null, "uuid": null, "mountpoint": null} ]
   413  }'
   414  exit 0`
   415  
   416  	// start with a single partition
   417  	cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBios)
   418  	defer cmdSfdisk.Restore()
   419  
   420  	cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScriptBios)
   421  	defer cmdLsblk.Restore()
   422  
   423  	dl, err := gadget.OnDiskVolumeFromDevice("/dev/node")
   424  	c.Assert(err, IsNil)
   425  	c.Assert(len(dl.Structure), Equals, 1)
   426  	c.Assert(dl.Structure[0].Node, Equals, "/dev/node1")
   427  
   428  	// add a partition
   429  	cmdSfdisk = testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBiosSeed)
   430  	defer cmdSfdisk.Restore()
   431  
   432  	cmdLsblk = testutil.MockCommand(c, "lsblk", mockLsblkScriptBiosSeed)
   433  	defer cmdLsblk.Restore()
   434  
   435  	// update the partition list
   436  	err = gadget.UpdatePartitionList(dl)
   437  	c.Assert(err, IsNil)
   438  
   439  	// check if the partition list was updated
   440  	c.Assert(len(dl.Structure), Equals, 2)
   441  	c.Assert(dl.Structure[0].Node, Equals, "/dev/node1")
   442  	c.Assert(dl.Structure[1].Node, Equals, "/dev/node2")
   443  }
   444  
   445  func (s *ondiskTestSuite) TestFilesystemInfo(c *C) {
   446  	cmd := testutil.MockCommand(c, "lsblk", `echo '{
   447     "blockdevices": [
   448        {"name": "loop8p2", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "C1F4-CE43", "mountpoint": null}
   449     ]
   450  }'`)
   451  	defer cmd.Restore()
   452  
   453  	info, err := gadget.FilesystemInfo("/dev/node")
   454  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   455  		{"lsblk", "--fs", "--json", "/dev/node"},
   456  	})
   457  	c.Assert(err, IsNil)
   458  	c.Assert(len(info.BlockDevices), Equals, 1)
   459  	bd := info.BlockDevices[0]
   460  	c.Assert(bd.Name, Equals, "loop8p2")
   461  	c.Assert(bd.FSType, Equals, "vfat")
   462  	c.Assert(bd.Label, Equals, "ubuntu-seed")
   463  	c.Assert(bd.UUID, Equals, "C1F4-CE43")
   464  }
   465  
   466  func (s *ondiskTestSuite) TestFilesystemInfoJsonError(c *C) {
   467  	cmd := testutil.MockCommand(c, "lsblk", `echo 'This is not a json'`)
   468  	defer cmd.Restore()
   469  
   470  	info, err := gadget.FilesystemInfo("/dev/node")
   471  	c.Assert(err, ErrorMatches, "cannot parse lsblk output: invalid .*")
   472  	c.Assert(info, IsNil)
   473  }
   474  
   475  func (s *ondiskTestSuite) TestFilesystemInfoError(c *C) {
   476  	cmd := testutil.MockCommand(c, "lsblk", "echo 'lsblk: not found'; exit 127")
   477  	defer cmd.Restore()
   478  
   479  	info, err := gadget.FilesystemInfo("/dev/node")
   480  	c.Assert(err, ErrorMatches, "lsblk: not found")
   481  	c.Assert(info, IsNil)
   482  }