github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/osutil/disks/disks_linux_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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 disks_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/osutil"
    32  	"github.com/snapcore/snapd/osutil/disks"
    33  	"github.com/snapcore/snapd/testutil"
    34  )
    35  
    36  var (
    37  	virtioDiskDevPath = "/devices/pci0000:00/0000:00:03.0/virtio1/block/vda/"
    38  
    39  	// typical real-world values for tests
    40  	diskUdevPropMap = map[string]string{
    41  		"ID_PART_ENTRY_DISK": "42:0",
    42  		"DEVNAME":            "/dev/vda",
    43  		"DEVPATH":            virtioDiskDevPath,
    44  	}
    45  
    46  	biosBootUdevPropMap = map[string]string{
    47  		"ID_PART_ENTRY_UUID": "bios-boot-partuuid",
    48  		// the udev prop for bios-boot has no fs label, which is typical of the
    49  		// real bios-boot partition on a amd64 pc gadget system, and so we should
    50  		// safely just ignore and skip this partition in the fs label
    51  		// implementation
    52  		"ID_FS_LABEL_ENC": "",
    53  		// we will however still have a partition label of "BIOS Boot"
    54  		"ID_PART_ENTRY_NAME": "BIOS\\x20Boot",
    55  	}
    56  
    57  	// all the ubuntu- partitions have fs labels
    58  	ubuntuSeedUdevPropMap = map[string]string{
    59  		"ID_PART_ENTRY_UUID": "ubuntu-seed-partuuid",
    60  		"ID_FS_LABEL_ENC":    "ubuntu-seed",
    61  		"ID_PART_ENTRY_NAME": "ubuntu-seed",
    62  	}
    63  	ubuntuBootUdevPropMap = map[string]string{
    64  		"ID_PART_ENTRY_UUID": "ubuntu-boot-partuuid",
    65  		"ID_FS_LABEL_ENC":    "ubuntu-boot",
    66  		"ID_PART_ENTRY_NAME": "ubuntu-boot",
    67  	}
    68  	ubuntuDataUdevPropMap = map[string]string{
    69  		"ID_PART_ENTRY_UUID": "ubuntu-data-partuuid",
    70  		"ID_FS_LABEL_ENC":    "ubuntu-data",
    71  		"ID_PART_ENTRY_NAME": "ubuntu-data",
    72  	}
    73  )
    74  
    75  func createVirtioDevicesInSysfs(c *C, devsToPartition map[string]bool) {
    76  	diskDir := filepath.Join(dirs.SysfsDir, virtioDiskDevPath)
    77  	for dev, isPartition := range devsToPartition {
    78  		err := os.MkdirAll(filepath.Join(diskDir, dev), 0755)
    79  		c.Assert(err, IsNil)
    80  		if isPartition {
    81  			err = ioutil.WriteFile(filepath.Join(diskDir, dev, "partition"), []byte("1"), 0644)
    82  			c.Assert(err, IsNil)
    83  		}
    84  	}
    85  }
    86  
    87  type diskSuite struct {
    88  	testutil.BaseTest
    89  }
    90  
    91  var _ = Suite(&diskSuite{})
    92  
    93  func (s *diskSuite) SetUpTest(c *C) {
    94  	dirs.SetRootDir(c.MkDir())
    95  }
    96  
    97  func (s *diskSuite) TestDiskFromNameHappy(c *C) {
    98  	restore := disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
    99  		c.Assert(dev, Equals, "sda")
   100  		return map[string]string{
   101  			"MAJOR":   "1",
   102  			"MINOR":   "2",
   103  			"DEVTYPE": "disk",
   104  		}, nil
   105  	})
   106  	defer restore()
   107  
   108  	d, err := disks.DiskFromDeviceName("sda")
   109  	c.Assert(err, IsNil)
   110  	c.Assert(d.Dev(), Equals, "1:2")
   111  }
   112  
   113  func (s *diskSuite) TestDiskFromNameUnhappyPartition(c *C) {
   114  	restore := disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   115  		c.Assert(dev, Equals, "sda1")
   116  		return map[string]string{
   117  			"MAJOR":   "1",
   118  			"MINOR":   "3",
   119  			"DEVTYPE": "partition",
   120  		}, nil
   121  	})
   122  	defer restore()
   123  
   124  	_, err := disks.DiskFromDeviceName("sda1")
   125  	c.Assert(err, ErrorMatches, "device \"sda1\" is not a disk, it has DEVTYPE of \"partition\"")
   126  }
   127  
   128  func (s *diskSuite) TestDiskFromNameUnhappyBadUdevOutput(c *C) {
   129  	restore := disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   130  		c.Assert(dev, Equals, "sda")
   131  		// udev should always return the major/minor but if it doesn't we should
   132  		// fail
   133  		return map[string]string{
   134  			"MAJOR": "blah blah blah",
   135  		}, nil
   136  	})
   137  	defer restore()
   138  
   139  	_, err := disks.DiskFromDeviceName("sda")
   140  	c.Assert(err, ErrorMatches, "cannot find disk with name \"sda\": malformed udev output")
   141  }
   142  
   143  func (s *diskSuite) TestDiskFromMountPointUnhappyMissingMountpoint(c *C) {
   144  	// no mount points
   145  	restore := osutil.MockMountInfo(``)
   146  	defer restore()
   147  
   148  	_, err := disks.DiskFromMountPoint("/run/mnt/blah", nil)
   149  	c.Assert(err, ErrorMatches, "cannot find mountpoint \"/run/mnt/blah\"")
   150  }
   151  
   152  func (s *diskSuite) TestDiskFromMountPointUnhappyMissingUdevProps(c *C) {
   153  	restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw
   154  `)
   155  	defer restore()
   156  
   157  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   158  		c.Assert(dev, Equals, "/dev/vda4")
   159  		return map[string]string{
   160  			"prop": "hello",
   161  		}, nil
   162  	})
   163  	defer restore()
   164  
   165  	_, err := disks.DiskFromMountPoint("/run/mnt/point", nil)
   166  	c.Assert(err, ErrorMatches, "cannot find disk for partition /dev/vda4, incomplete udev output")
   167  }
   168  
   169  func (s *diskSuite) TestDiskFromMountPointUnhappyBadUdevPropsMountpointPartition(c *C) {
   170  	restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw
   171  `)
   172  	defer restore()
   173  
   174  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   175  		c.Assert(dev, Equals, "/dev/vda4")
   176  		return map[string]string{
   177  			"ID_PART_ENTRY_DISK": "not-a-number",
   178  		}, nil
   179  	})
   180  	defer restore()
   181  
   182  	_, err := disks.DiskFromMountPoint("/run/mnt/point", nil)
   183  	c.Assert(err, ErrorMatches, `cannot find disk for partition /dev/vda4, bad udev output: invalid device number format: \(expected <int>:<int>\)`)
   184  }
   185  
   186  func (s *diskSuite) TestDiskFromMountPointUnhappyIsDecryptedDeviceNotDiskDevice(c *C) {
   187  	restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw
   188  `)
   189  	defer restore()
   190  
   191  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   192  		switch dev {
   193  		case "/dev/vda4":
   194  			return map[string]string{
   195  				"ID_PART_ENTRY_DISK": "42:0",
   196  				// DEVTYPE == partition is unexpected for this, so this makes
   197  				// DiskFromMountPoint fail, as decrypted devices should not be
   198  				// direct partitions, they should be mapper device volumes/disks
   199  				"DEVTYPE": "partition",
   200  			}, nil
   201  		default:
   202  			c.Errorf("unexpected udev device properties requested: %s", dev)
   203  			return nil, fmt.Errorf("unexpected udev device: %s", dev)
   204  		}
   205  	})
   206  	defer restore()
   207  
   208  	opts := &disks.Options{IsDecryptedDevice: true}
   209  	_, err := disks.DiskFromMountPoint("/run/mnt/point", opts)
   210  	c.Assert(err, ErrorMatches, `mountpoint source /dev/vda4 is not a decrypted device: devtype is not disk \(is partition\)`)
   211  }
   212  
   213  func (s *diskSuite) TestDiskFromMountPointUnhappyIsDecryptedDeviceNoSysfs(c *C) {
   214  	restore := osutil.MockMountInfo(`130 30 252:0 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/mapper/something rw
   215  `)
   216  	defer restore()
   217  
   218  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   219  		switch dev {
   220  		case "/dev/mapper/something":
   221  			return map[string]string{
   222  				"DEVTYPE": "disk",
   223  			}, nil
   224  		default:
   225  			c.Errorf("unexpected udev device properties requested: %s", dev)
   226  			return nil, fmt.Errorf("unexpected udev device: %s", dev)
   227  		}
   228  	})
   229  	defer restore()
   230  
   231  	// no sysfs files mocking
   232  
   233  	opts := &disks.Options{IsDecryptedDevice: true}
   234  	_, err := disks.DiskFromMountPoint("/run/mnt/point", opts)
   235  	c.Assert(err, ErrorMatches, fmt.Sprintf(`mountpoint source /dev/mapper/something is not a decrypted device: could not read device mapper metadata: open %s/dev/block/252:0/dm/uuid: no such file or directory`, dirs.SysfsDir))
   236  }
   237  
   238  func (s *diskSuite) TestDiskFromMountPointHappySinglePartitionIgnoresNonPartitionsInSysfs(c *C) {
   239  	restore := osutil.MockMountInfo(`130 30 47:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda4 rw
   240  `)
   241  	defer restore()
   242  
   243  	// mock just the single partition and the disk itself in udev
   244  	n := 0
   245  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   246  		n++
   247  		switch n {
   248  		case 1, 4:
   249  			c.Assert(dev, Equals, "/dev/vda4")
   250  			// this is the partition that was mounted that we initially inspect
   251  			// to get the disk
   252  			// this is also called again when we call MountPointIsFromDisk to
   253  			// verify that the /run/mnt/point is from the same disk
   254  			return map[string]string{
   255  				"ID_PART_ENTRY_DISK": "42:0",
   256  			}, nil
   257  		case 2:
   258  			c.Assert(dev, Equals, "/dev/block/42:0")
   259  			// this is the disk itself, from ID_PART_ENTRY_DISK above
   260  			// note that the major/minor for the disk is not adjacent/related to
   261  			// the partition itself
   262  			return map[string]string{
   263  				"DEVNAME": "/dev/vda",
   264  				"DEVPATH": virtioDiskDevPath,
   265  			}, nil
   266  		case 3:
   267  			c.Assert(dev, Equals, "vda4")
   268  			// this is the sysfs entry for the partition of the disk previously
   269  			// found under the DEVPATH for /dev/block/42:0
   270  			// this is essentially the same as /dev/block/42:1 in actuality, but
   271  			// we search for it differently
   272  			return map[string]string{
   273  				"ID_FS_LABEL_ENC":    "some-label",
   274  				"ID_PART_ENTRY_UUID": "some-uuid",
   275  			}, nil
   276  		default:
   277  			c.Errorf("unexpected udev device properties requested: %s", dev)
   278  			return nil, fmt.Errorf("unexpected udev device: %s", dev)
   279  		}
   280  	})
   281  	defer restore()
   282  
   283  	// create just the single valid partition in sysfs, and an invalid
   284  	// non-partition device that we should ignore
   285  	createVirtioDevicesInSysfs(c, map[string]bool{
   286  		"vda4": true,
   287  		"vda5": false,
   288  	})
   289  
   290  	disk, err := disks.DiskFromMountPoint("/run/mnt/point", nil)
   291  	c.Assert(err, IsNil)
   292  	c.Assert(disk.Dev(), Equals, "42:0")
   293  	c.Assert(disk.HasPartitions(), Equals, true)
   294  	// searching for the single label we have for this partition will succeed
   295  	label, err := disk.FindMatchingPartitionUUIDWithFsLabel("some-label")
   296  	c.Assert(err, IsNil)
   297  	c.Assert(label, Equals, "some-uuid")
   298  
   299  	matches, err := disk.MountPointIsFromDisk("/run/mnt/point", nil)
   300  	c.Assert(err, IsNil)
   301  	c.Assert(matches, Equals, true)
   302  
   303  	// trying to search for any other labels though will fail
   304  	_, err = disk.FindMatchingPartitionUUIDWithFsLabel("ubuntu-boot")
   305  	c.Assert(err, ErrorMatches, "filesystem label \"ubuntu-boot\" not found")
   306  	c.Assert(err, DeepEquals, disks.PartitionNotFoundError{
   307  		SearchType:  "filesystem-label",
   308  		SearchQuery: "ubuntu-boot",
   309  	})
   310  }
   311  
   312  func (s *diskSuite) TestDiskFromMountPointHappyRealUdevadm(c *C) {
   313  	restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/vda1 rw
   314  `)
   315  	defer restore()
   316  
   317  	udevadmCmd := testutil.MockCommand(c, "udevadm", `
   318  if [ "$*" = "info --query property --name /dev/vda1" ]; then
   319  	echo "ID_PART_ENTRY_DISK=42:0"
   320  else
   321  	echo "unexpected arguments"
   322  	exit 1
   323  fi
   324  `)
   325  
   326  	d, err := disks.DiskFromMountPoint("/run/mnt/point", nil)
   327  	c.Assert(err, IsNil)
   328  	c.Assert(d.Dev(), Equals, "42:0")
   329  	c.Assert(d.HasPartitions(), Equals, true)
   330  
   331  	c.Assert(udevadmCmd.Calls(), DeepEquals, [][]string{
   332  		{"udevadm", "info", "--query", "property", "--name", "/dev/vda1"},
   333  	})
   334  }
   335  
   336  func (s *diskSuite) TestDiskFromMountPointVolumeHappy(c *C) {
   337  	restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/mapper/something rw
   338  `)
   339  	defer restore()
   340  
   341  	udevadmCmd := testutil.MockCommand(c, "udevadm", `
   342  if [ "$*" = "info --query property --name /dev/mapper/something" ]; then
   343  	# not a partition, so no ID_PART_ENTRY_DISK, but we will have DEVTYPE=disk
   344  	echo "DEVTYPE=disk"
   345  else
   346  	echo "unexpected arguments"
   347  	exit 1
   348  fi
   349  `)
   350  
   351  	d, err := disks.DiskFromMountPoint("/run/mnt/point", nil)
   352  	c.Assert(err, IsNil)
   353  	c.Assert(d.Dev(), Equals, "42:1")
   354  	c.Assert(d.HasPartitions(), Equals, false)
   355  
   356  	c.Assert(udevadmCmd.Calls(), DeepEquals, [][]string{
   357  		{"udevadm", "info", "--query", "property", "--name", "/dev/mapper/something"},
   358  	})
   359  }
   360  
   361  func (s *diskSuite) TestDiskFromMountPointIsDecryptedDeviceVolumeHappy(c *C) {
   362  	restore := osutil.MockMountInfo(`130 30 242:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/mapper/something rw
   363  `)
   364  	defer restore()
   365  
   366  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   367  		switch dev {
   368  		case "/dev/mapper/something":
   369  			return map[string]string{
   370  				"DEVTYPE": "disk",
   371  			}, nil
   372  		case "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304":
   373  			return map[string]string{
   374  				"DEVTYPE": "disk",
   375  			}, nil
   376  		default:
   377  			c.Errorf("unexpected udev device properties requested: %s", dev)
   378  			return nil, fmt.Errorf("unexpected udev device: %s", dev)
   379  		}
   380  	})
   381  	defer restore()
   382  
   383  	// mock the sysfs dm uuid and name files
   384  	dmDir := filepath.Join(filepath.Join(dirs.SysfsDir, "dev", "block"), "242:1", "dm")
   385  	err := os.MkdirAll(dmDir, 0755)
   386  	c.Assert(err, IsNil)
   387  
   388  	b := []byte("something")
   389  	err = ioutil.WriteFile(filepath.Join(dmDir, "name"), b, 0644)
   390  	c.Assert(err, IsNil)
   391  
   392  	b = []byte("CRYPT-LUKS2-5a522809c87e4dfa81a88dc5667d1304-something")
   393  	err = ioutil.WriteFile(filepath.Join(dmDir, "uuid"), b, 0644)
   394  	c.Assert(err, IsNil)
   395  
   396  	opts := &disks.Options{IsDecryptedDevice: true}
   397  	d, err := disks.DiskFromMountPoint("/run/mnt/point", opts)
   398  	c.Assert(err, IsNil)
   399  	c.Assert(d.Dev(), Equals, "242:1")
   400  	c.Assert(d.HasPartitions(), Equals, false)
   401  }
   402  
   403  func (s *diskSuite) TestDiskFromMountPointNotDiskUnsupported(c *C) {
   404  	restore := osutil.MockMountInfo(`130 30 42:1 / /run/mnt/point rw,relatime shared:54 - ext4 /dev/not-a-disk rw
   405  `)
   406  	defer restore()
   407  
   408  	udevadmCmd := testutil.MockCommand(c, "udevadm", `
   409  if [ "$*" = "info --query property --name /dev/not-a-disk" ]; then
   410  	echo "DEVTYPE=not-a-disk"
   411  else
   412  	echo "unexpected arguments"
   413  	exit 1
   414  fi
   415  `)
   416  
   417  	_, err := disks.DiskFromMountPoint("/run/mnt/point", nil)
   418  	c.Assert(err, ErrorMatches, "unsupported DEVTYPE \"not-a-disk\" for mount point source /dev/not-a-disk")
   419  
   420  	c.Assert(udevadmCmd.Calls(), DeepEquals, [][]string{
   421  		{"udevadm", "info", "--query", "property", "--name", "/dev/not-a-disk"},
   422  	})
   423  }
   424  
   425  func (s *diskSuite) TestDiskFromMountPointPartitionsHappy(c *C) {
   426  	restore := osutil.MockMountInfo(`130 30 42:4 / /run/mnt/data rw,relatime shared:54 - ext4 /dev/vda4 rw
   427   130 30 42:4 / /run/mnt/ubuntu-boot rw,relatime shared:54 - ext4 /dev/vda3 rw
   428  `)
   429  	defer restore()
   430  
   431  	n := 0
   432  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   433  		n++
   434  		switch n {
   435  		case 1:
   436  			// first request is to the mount point source
   437  			c.Assert(dev, Equals, "/dev/vda4")
   438  			return diskUdevPropMap, nil
   439  		case 2:
   440  			// next request is for the disk itself
   441  			c.Assert(dev, Equals, "/dev/block/42:0")
   442  			return diskUdevPropMap, nil
   443  		case 3:
   444  			c.Assert(dev, Equals, "vda1")
   445  			// this is the sysfs entry for the first partition of the disk
   446  			// previously found under the DEVPATH for /dev/block/42:0
   447  			return biosBootUdevPropMap, nil
   448  		case 4:
   449  			c.Assert(dev, Equals, "vda2")
   450  			// the second partition of the disk from sysfs has a fs label
   451  			return ubuntuSeedUdevPropMap, nil
   452  		case 5:
   453  			c.Assert(dev, Equals, "vda3")
   454  			// same for the third partition
   455  			return ubuntuBootUdevPropMap, nil
   456  		case 6:
   457  			c.Assert(dev, Equals, "vda4")
   458  			// same for the fourth partition
   459  			return ubuntuDataUdevPropMap, nil
   460  		case 7:
   461  			// next request is for the MountPointIsFromDisk for ubuntu-boot in
   462  			// this test
   463  			c.Assert(dev, Equals, "/dev/vda3")
   464  			return diskUdevPropMap, nil
   465  		case 8:
   466  			// next request is for the another DiskFromMountPoint build set of methods we
   467  			// call in this test
   468  			c.Assert(dev, Equals, "/dev/vda3")
   469  			return diskUdevPropMap, nil
   470  		case 9:
   471  			// same as for case 2, the disk itself using the major/minor
   472  			c.Assert(dev, Equals, "/dev/block/42:0")
   473  			return diskUdevPropMap, nil
   474  		case 10:
   475  			c.Assert(dev, Equals, "vda1")
   476  			// this is the sysfs entry for the first partition of the disk
   477  			// previously found under the DEVPATH for /dev/block/42:0
   478  			return biosBootUdevPropMap, nil
   479  		case 11:
   480  			c.Assert(dev, Equals, "vda2")
   481  			// the second partition of the disk from sysfs has a fs label
   482  			return ubuntuSeedUdevPropMap, nil
   483  		case 12:
   484  			c.Assert(dev, Equals, "vda3")
   485  			// same for the third partition
   486  			return ubuntuBootUdevPropMap, nil
   487  		case 13:
   488  			c.Assert(dev, Equals, "vda4")
   489  			// same for the fourth partition
   490  			return ubuntuDataUdevPropMap, nil
   491  		case 14:
   492  			// next request is for the MountPointIsFromDisk for ubuntu-data
   493  			c.Assert(dev, Equals, "/dev/vda4")
   494  			return diskUdevPropMap, nil
   495  		default:
   496  			c.Errorf("unexpected udev device properties requested (request %d): %s", n, dev)
   497  			return nil, fmt.Errorf("unexpected udev device (request %d): %s", n, dev)
   498  		}
   499  	})
   500  	defer restore()
   501  
   502  	// create all 4 partitions as device nodes in sysfs
   503  	createVirtioDevicesInSysfs(c, map[string]bool{
   504  		"vda1": true,
   505  		"vda2": true,
   506  		"vda3": true,
   507  		"vda4": true,
   508  	})
   509  
   510  	ubuntuDataDisk, err := disks.DiskFromMountPoint("/run/mnt/data", nil)
   511  	c.Assert(err, IsNil)
   512  	c.Assert(ubuntuDataDisk, Not(IsNil))
   513  	c.Assert(ubuntuDataDisk.Dev(), Equals, "42:0")
   514  
   515  	// we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels
   516  	for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data"} {
   517  		id, err := ubuntuDataDisk.FindMatchingPartitionUUIDWithFsLabel(label)
   518  		c.Assert(err, IsNil)
   519  		c.Assert(id, Equals, label+"-partuuid")
   520  	}
   521  
   522  	// and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the
   523  	// same disk
   524  	matches, err := ubuntuDataDisk.MountPointIsFromDisk("/run/mnt/ubuntu-boot", nil)
   525  	c.Assert(err, IsNil)
   526  	c.Assert(matches, Equals, true)
   527  
   528  	// and we can find the partition for ubuntu-boot first and then match
   529  	// that with ubuntu-data too
   530  	ubuntuBootDisk, err := disks.DiskFromMountPoint("/run/mnt/ubuntu-boot", nil)
   531  	c.Assert(err, IsNil)
   532  	c.Assert(ubuntuBootDisk, Not(IsNil))
   533  	c.Assert(ubuntuBootDisk.Dev(), Equals, "42:0")
   534  
   535  	// we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels
   536  	for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data"} {
   537  		id, err := ubuntuBootDisk.FindMatchingPartitionUUIDWithFsLabel(label)
   538  		c.Assert(err, IsNil)
   539  		c.Assert(id, Equals, label+"-partuuid")
   540  	}
   541  
   542  	// and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the
   543  	// same disk
   544  	matches, err = ubuntuBootDisk.MountPointIsFromDisk("/run/mnt/data", nil)
   545  	c.Assert(err, IsNil)
   546  	c.Assert(matches, Equals, true)
   547  
   548  	// finally we can't find the bios-boot partition because it has no fs label
   549  	_, err = ubuntuBootDisk.FindMatchingPartitionUUIDWithFsLabel("bios-boot")
   550  	c.Assert(err, ErrorMatches, "filesystem label \"bios-boot\" not found")
   551  	c.Assert(err, DeepEquals, disks.PartitionNotFoundError{
   552  		SearchType:  "filesystem-label",
   553  		SearchQuery: "bios-boot",
   554  	})
   555  
   556  	_, err = ubuntuDataDisk.FindMatchingPartitionUUIDWithFsLabel("bios-boot")
   557  	c.Assert(err, ErrorMatches, "filesystem label \"bios-boot\" not found")
   558  	c.Assert(err, DeepEquals, disks.PartitionNotFoundError{
   559  		SearchType:  "filesystem-label",
   560  		SearchQuery: "bios-boot",
   561  	})
   562  
   563  	// however we can find it via the partition label
   564  	uuid, err := ubuntuBootDisk.FindMatchingPartitionUUIDWithPartLabel("BIOS Boot")
   565  	c.Assert(err, IsNil)
   566  	c.Assert(uuid, Equals, "bios-boot-partuuid")
   567  
   568  	uuid, err = ubuntuDataDisk.FindMatchingPartitionUUIDWithPartLabel("BIOS Boot")
   569  	c.Assert(err, IsNil)
   570  	c.Assert(uuid, Equals, "bios-boot-partuuid")
   571  
   572  	// trying to find an unknown partition label fails however
   573  	_, err = ubuntuDataDisk.FindMatchingPartitionUUIDWithPartLabel("NOT BIOS Boot")
   574  	c.Assert(err, ErrorMatches, "partition label \"NOT BIOS Boot\" not found")
   575  	c.Assert(err, DeepEquals, disks.PartitionNotFoundError{
   576  		SearchType:  "partition-label",
   577  		SearchQuery: "NOT BIOS Boot",
   578  	})
   579  }
   580  
   581  func (s *diskSuite) TestDiskFromMountPointDecryptedDevicePartitionsHappy(c *C) {
   582  	restore := osutil.MockMountInfo(`130 30 252:0 / /run/mnt/data rw,relatime shared:54 - ext4 /dev/mapper/ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4 rw
   583   130 30 42:4 / /run/mnt/ubuntu-boot rw,relatime shared:54 - ext4 /dev/vda3 rw
   584  `)
   585  	defer restore()
   586  
   587  	n := 0
   588  	restore = disks.MockUdevPropertiesForDevice(func(dev string) (map[string]string, error) {
   589  		n++
   590  		switch n {
   591  		case 1:
   592  			// first request is to find the disk based on the mapper mount point
   593  			c.Assert(dev, Equals, "/dev/mapper/ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4")
   594  			// the mapper device is a disk/volume
   595  			return map[string]string{
   596  				"DEVTYPE": "disk",
   597  			}, nil
   598  		case 2:
   599  			// next we find the physical disk by the dm uuid
   600  			c.Assert(dev, Equals, "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304")
   601  			return diskUdevPropMap, nil
   602  		case 3:
   603  			// then re-find the disk based on it's dev major / minor
   604  			c.Assert(dev, Equals, "/dev/block/42:0")
   605  			return diskUdevPropMap, nil
   606  		case 4:
   607  			// next find each partition in turn
   608  			c.Assert(dev, Equals, "vda1")
   609  			return biosBootUdevPropMap, nil
   610  		case 5:
   611  			c.Assert(dev, Equals, "vda2")
   612  			return ubuntuSeedUdevPropMap, nil
   613  		case 6:
   614  			c.Assert(dev, Equals, "vda3")
   615  			return ubuntuBootUdevPropMap, nil
   616  		case 7:
   617  			c.Assert(dev, Equals, "vda4")
   618  			return map[string]string{
   619  				"ID_FS_LABEL_ENC":    "ubuntu-data-enc",
   620  				"ID_PART_ENTRY_UUID": "ubuntu-data-enc-partuuid",
   621  			}, nil
   622  		case 8:
   623  			// next we will find the disk for a different mount point via
   624  			// MountPointIsFromDisk for ubuntu-boot
   625  			c.Assert(dev, Equals, "/dev/vda3")
   626  			return diskUdevPropMap, nil
   627  		case 9:
   628  			// next we will build up a disk from the ubuntu-boot mount point
   629  			c.Assert(dev, Equals, "/dev/vda3")
   630  			return diskUdevPropMap, nil
   631  		case 10:
   632  			// same as step 3
   633  			c.Assert(dev, Equals, "/dev/block/42:0")
   634  			return diskUdevPropMap, nil
   635  		case 11:
   636  			// next find each partition in turn again, same as steps 4-7
   637  			c.Assert(dev, Equals, "vda1")
   638  			return biosBootUdevPropMap, nil
   639  		case 12:
   640  			c.Assert(dev, Equals, "vda2")
   641  			return ubuntuSeedUdevPropMap, nil
   642  		case 13:
   643  			c.Assert(dev, Equals, "vda3")
   644  			return ubuntuBootUdevPropMap, nil
   645  		case 14:
   646  			c.Assert(dev, Equals, "vda4")
   647  			return map[string]string{
   648  				"ID_FS_LABEL_ENC":    "ubuntu-data-enc",
   649  				"ID_PART_ENTRY_UUID": "ubuntu-data-enc-partuuid",
   650  			}, nil
   651  		case 15:
   652  			// then we will find the disk for ubuntu-data mapper volume to
   653  			// verify it comes from the same disk as the second disk we just
   654  			// finished finding
   655  			c.Assert(dev, Equals, "/dev/mapper/ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4")
   656  			// the mapper device is a disk/volume
   657  			return map[string]string{
   658  				"DEVTYPE": "disk",
   659  			}, nil
   660  		case 16:
   661  			// then we find the physical disk by the dm uuid
   662  			c.Assert(dev, Equals, "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304")
   663  			return diskUdevPropMap, nil
   664  		default:
   665  			c.Errorf("unexpected udev device properties requested (request %d): %s", n, dev)
   666  			return nil, fmt.Errorf("unexpected udev device (request %d): %s", n, dev)
   667  		}
   668  	})
   669  	defer restore()
   670  
   671  	// mock the sysfs dm uuid and name files
   672  	dmDir := filepath.Join(filepath.Join(dirs.SysfsDir, "dev", "block"), "252:0", "dm")
   673  	err := os.MkdirAll(dmDir, 0755)
   674  	c.Assert(err, IsNil)
   675  
   676  	b := []byte("ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4")
   677  	err = ioutil.WriteFile(filepath.Join(dmDir, "name"), b, 0644)
   678  	c.Assert(err, IsNil)
   679  
   680  	b = []byte("CRYPT-LUKS2-5a522809c87e4dfa81a88dc5667d1304-ubuntu-data-3776bab4-8bcc-46b7-9da2-6a84ce7f93b4")
   681  	err = ioutil.WriteFile(filepath.Join(dmDir, "uuid"), b, 0644)
   682  	c.Assert(err, IsNil)
   683  
   684  	// mock the dev nodes in sysfs for the partitions
   685  	createVirtioDevicesInSysfs(c, map[string]bool{
   686  		"vda1": true,
   687  		"vda2": true,
   688  		"vda3": true,
   689  		"vda4": true,
   690  	})
   691  
   692  	opts := &disks.Options{IsDecryptedDevice: true}
   693  	ubuntuDataDisk, err := disks.DiskFromMountPoint("/run/mnt/data", opts)
   694  	c.Assert(err, IsNil)
   695  	c.Assert(ubuntuDataDisk, Not(IsNil))
   696  	c.Assert(ubuntuDataDisk.Dev(), Equals, "42:0")
   697  
   698  	// we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels
   699  	for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data-enc"} {
   700  		id, err := ubuntuDataDisk.FindMatchingPartitionUUIDWithFsLabel(label)
   701  		c.Assert(err, IsNil)
   702  		c.Assert(id, Equals, label+"-partuuid")
   703  	}
   704  
   705  	// and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the
   706  	// same disk
   707  	matches, err := ubuntuDataDisk.MountPointIsFromDisk("/run/mnt/ubuntu-boot", nil)
   708  	c.Assert(err, IsNil)
   709  	c.Assert(matches, Equals, true)
   710  
   711  	// and we can find the partition for ubuntu-boot first and then match
   712  	// that with ubuntu-data too
   713  	ubuntuBootDisk, err := disks.DiskFromMountPoint("/run/mnt/ubuntu-boot", nil)
   714  	c.Assert(err, IsNil)
   715  	c.Assert(ubuntuBootDisk, Not(IsNil))
   716  	c.Assert(ubuntuBootDisk.Dev(), Equals, "42:0")
   717  
   718  	// we have the ubuntu-seed, ubuntu-boot, and ubuntu-data partition labels
   719  	for _, label := range []string{"ubuntu-seed", "ubuntu-boot", "ubuntu-data-enc"} {
   720  		id, err := ubuntuBootDisk.FindMatchingPartitionUUIDWithFsLabel(label)
   721  		c.Assert(err, IsNil)
   722  		c.Assert(id, Equals, label+"-partuuid")
   723  	}
   724  
   725  	// and the mountpoint for ubuntu-boot at /run/mnt/ubuntu-boot matches the
   726  	// same disk
   727  	matches, err = ubuntuBootDisk.MountPointIsFromDisk("/run/mnt/data", opts)
   728  	c.Assert(err, IsNil)
   729  	c.Assert(matches, Equals, true)
   730  }