github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/device_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package gadget_test
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/gadget"
    34  	"github.com/snapcore/snapd/osutil"
    35  )
    36  
    37  type deviceSuite struct {
    38  	dir string
    39  }
    40  
    41  var _ = Suite(&deviceSuite{})
    42  
    43  func (d *deviceSuite) SetUpTest(c *C) {
    44  	d.dir = c.MkDir()
    45  	dirs.SetRootDir(d.dir)
    46  
    47  	err := os.MkdirAll(filepath.Join(d.dir, "/dev/disk/by-label"), 0755)
    48  	c.Assert(err, IsNil)
    49  	err = os.MkdirAll(filepath.Join(d.dir, "/dev/disk/by-partlabel"), 0755)
    50  	c.Assert(err, IsNil)
    51  	err = os.MkdirAll(filepath.Join(d.dir, "/dev/mapper"), 0755)
    52  	c.Assert(err, IsNil)
    53  	err = ioutil.WriteFile(filepath.Join(d.dir, "/dev/fakedevice"), []byte(""), 0644)
    54  	c.Assert(err, IsNil)
    55  }
    56  
    57  func (d *deviceSuite) TearDownTest(c *C) {
    58  	dirs.SetRootDir("/")
    59  }
    60  
    61  func (d *deviceSuite) setupMockSysfs(c *C) {
    62  	// setup everything for 'writable'
    63  	err := ioutil.WriteFile(filepath.Join(d.dir, "/dev/fakedevice0p1"), []byte(""), 0644)
    64  	c.Assert(err, IsNil)
    65  	err = os.Symlink("../../fakedevice0p1", filepath.Join(d.dir, "/dev/disk/by-label/writable"))
    66  	c.Assert(err, IsNil)
    67  	// make parent device
    68  	err = ioutil.WriteFile(filepath.Join(d.dir, "/dev/fakedevice0"), []byte(""), 0644)
    69  	c.Assert(err, IsNil)
    70  	// and fake /sys/block structure
    71  	err = os.MkdirAll(filepath.Join(d.dir, "/sys/block/fakedevice0/fakedevice0p1"), 0755)
    72  	c.Assert(err, IsNil)
    73  }
    74  
    75  func (d *deviceSuite) setupMockSysfsForDevMapper(c *C) {
    76  	// setup a mock /dev/mapper environment (incomplete we have no "happy"
    77  	// test; use a complex setup that mimics LVM in LUKS:
    78  	// /dev/mapper/data_crypt (symlink)
    79  	//   ⤷ /dev/dm-1 (LVM)
    80  	//      ⤷ /dev/dm-0 (LUKS)
    81  	//         ⤷ /dev/fakedevice0 (actual device)
    82  	err := ioutil.WriteFile(filepath.Join(d.dir, "/dev/dm-0"), nil, 0644)
    83  	c.Assert(err, IsNil)
    84  	err = ioutil.WriteFile(filepath.Join(d.dir, "/dev/dm-1"), nil, 0644)
    85  	c.Assert(err, IsNil)
    86  	err = ioutil.WriteFile(filepath.Join(d.dir, "/dev/fakedevice0"), []byte(""), 0644)
    87  	c.Assert(err, IsNil)
    88  	err = ioutil.WriteFile(filepath.Join(d.dir, "/dev/fakedevice"), []byte(""), 0644)
    89  	c.Assert(err, IsNil)
    90  	// symlinks added by dm/udev are relative
    91  	err = os.Symlink("../dm-1", filepath.Join(d.dir, "/dev/mapper/data_crypt"))
    92  	c.Assert(err, IsNil)
    93  	err = os.MkdirAll(filepath.Join(d.dir, "/sys/block/dm-1/slaves/"), 0755)
    94  	c.Assert(err, IsNil)
    95  	// sys symlinks are relative too
    96  	err = os.Symlink("../../dm-0", filepath.Join(d.dir, "/sys/block/dm-1/slaves/dm-0"))
    97  	c.Assert(err, IsNil)
    98  	err = os.MkdirAll(filepath.Join(d.dir, "/sys/block/dm-0/slaves/"), 0755)
    99  	c.Assert(err, IsNil)
   100  	// real symlink would point to ../../../../<bus, eg. pci>/<addr>/block/fakedevice/fakedevice0
   101  	err = os.Symlink("../../../../fakedevice/fakedevice0", filepath.Join(d.dir, "/sys/block/dm-0/slaves/fakedevice0"))
   102  	c.Assert(err, IsNil)
   103  	err = os.MkdirAll(filepath.Join(d.dir, "/sys/block/fakedevice/fakedevice0"), 0755)
   104  	c.Assert(err, IsNil)
   105  }
   106  
   107  func (d *deviceSuite) TestDeviceFindByStructureName(c *C) {
   108  	names := []struct {
   109  		escaped   string
   110  		structure string
   111  	}{
   112  		{"foo", "foo"},
   113  		{"123", "123"},
   114  		{"foo\\x20bar", "foo bar"},
   115  		{"foo#bar", "foo#bar"},
   116  		{"Новый_том", "Новый_том"},
   117  		{`pinkié\x20pie`, `pinkié pie`},
   118  	}
   119  	for _, name := range names {
   120  		err := os.Symlink(filepath.Join(d.dir, "/dev/fakedevice"), filepath.Join(d.dir, "/dev/disk/by-partlabel", name.escaped))
   121  		c.Assert(err, IsNil)
   122  	}
   123  
   124  	for _, tc := range names {
   125  		c.Logf("trying: %q", tc)
   126  		found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   127  			VolumeStructure: &gadget.VolumeStructure{Name: tc.structure},
   128  		})
   129  		c.Check(err, IsNil)
   130  		c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice"))
   131  	}
   132  }
   133  
   134  func (d *deviceSuite) TestDeviceFindRelativeSymlink(c *C) {
   135  	err := os.Symlink("../../fakedevice", filepath.Join(d.dir, "/dev/disk/by-partlabel/relative"))
   136  	c.Assert(err, IsNil)
   137  
   138  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   139  		VolumeStructure: &gadget.VolumeStructure{Name: "relative"},
   140  	})
   141  	c.Check(err, IsNil)
   142  	c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice"))
   143  }
   144  
   145  func (d *deviceSuite) TestDeviceFindByFilesystemLabel(c *C) {
   146  	names := []struct {
   147  		escaped   string
   148  		structure string
   149  	}{
   150  		{"foo", "foo"},
   151  		{"123", "123"},
   152  		{`foo\x20bar`, "foo bar"},
   153  		{"foo#bar", "foo#bar"},
   154  		{"Новый_том", "Новый_том"},
   155  		{`pinkié\x20pie`, `pinkié pie`},
   156  	}
   157  	for _, name := range names {
   158  		err := os.Symlink(filepath.Join(d.dir, "/dev/fakedevice"), filepath.Join(d.dir, "/dev/disk/by-label", name.escaped))
   159  		c.Assert(err, IsNil)
   160  	}
   161  
   162  	for _, tc := range names {
   163  		c.Logf("trying: %q", tc)
   164  		found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   165  			VolumeStructure: &gadget.VolumeStructure{
   166  				Filesystem: "ext4",
   167  				Label:      tc.structure,
   168  			},
   169  		})
   170  		c.Check(err, IsNil)
   171  		c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice"))
   172  	}
   173  }
   174  
   175  func (d *deviceSuite) TestDeviceFindChecksPartlabelAndFilesystemLabelHappy(c *C) {
   176  	fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
   177  	err := os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/foo"))
   178  	c.Assert(err, IsNil)
   179  
   180  	err = os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-partlabel/bar"))
   181  	c.Assert(err, IsNil)
   182  
   183  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   184  		VolumeStructure: &gadget.VolumeStructure{
   185  			Name:  "bar",
   186  			Label: "foo",
   187  		},
   188  	})
   189  	c.Check(err, IsNil)
   190  	c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice"))
   191  }
   192  
   193  func (d *deviceSuite) TestDeviceFindFilesystemLabelToNameFallback(c *C) {
   194  	fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
   195  	// only the by-filesystem-label symlink
   196  	err := os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/foo"))
   197  	c.Assert(err, IsNil)
   198  
   199  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   200  		VolumeStructure: &gadget.VolumeStructure{
   201  			Name:       "foo",
   202  			Filesystem: "ext4",
   203  		},
   204  	})
   205  	c.Check(err, IsNil)
   206  	c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice"))
   207  }
   208  
   209  func (d *deviceSuite) TestDeviceFindChecksPartlabelAndFilesystemLabelMismatch(c *C) {
   210  	fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
   211  	err := os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/foo"))
   212  	c.Assert(err, IsNil)
   213  
   214  	// partlabel of the structure points to a different device
   215  	fakedeviceOther := filepath.Join(d.dir, "/dev/fakedevice-other")
   216  	err = ioutil.WriteFile(fakedeviceOther, []byte(""), 0644)
   217  	c.Assert(err, IsNil)
   218  	err = os.Symlink(fakedeviceOther, filepath.Join(d.dir, "/dev/disk/by-partlabel/bar"))
   219  	c.Assert(err, IsNil)
   220  
   221  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   222  		VolumeStructure: &gadget.VolumeStructure{
   223  			Name:       "bar",
   224  			Label:      "foo",
   225  			Filesystem: "ext4",
   226  		},
   227  	})
   228  	c.Check(err, ErrorMatches, `conflicting device match, ".*/by-label/foo" points to ".*/fakedevice", previous match ".*/by-partlabel/bar" points to ".*/fakedevice-other"`)
   229  	c.Check(found, Equals, "")
   230  }
   231  
   232  func (d *deviceSuite) TestDeviceFindNotFound(c *C) {
   233  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   234  		VolumeStructure: &gadget.VolumeStructure{
   235  			Name:  "bar",
   236  			Label: "foo",
   237  		},
   238  	})
   239  	c.Check(err, ErrorMatches, `device not found`)
   240  	c.Check(found, Equals, "")
   241  }
   242  
   243  func (d *deviceSuite) TestDeviceFindNotFoundEmpty(c *C) {
   244  	// neither name nor filesystem label set
   245  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   246  		VolumeStructure: &gadget.VolumeStructure{
   247  			Name: "",
   248  			// structure has no filesystem, fs label check is
   249  			// ineffective
   250  			Label: "",
   251  		},
   252  	})
   253  	c.Check(err, ErrorMatches, `device not found`)
   254  	c.Check(found, Equals, "")
   255  
   256  	// try with proper filesystem now
   257  	found, err = gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   258  		VolumeStructure: &gadget.VolumeStructure{
   259  			Name:       "",
   260  			Label:      "",
   261  			Filesystem: "ext4",
   262  		},
   263  	})
   264  	c.Check(err, ErrorMatches, `device not found`)
   265  	c.Check(found, Equals, "")
   266  }
   267  
   268  func (d *deviceSuite) TestDeviceFindNotFoundSymlinkPointsNowhere(c *C) {
   269  	fakedevice := filepath.Join(d.dir, "/dev/fakedevice-not-found")
   270  	err := os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/foo"))
   271  	c.Assert(err, IsNil)
   272  
   273  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   274  		VolumeStructure: &gadget.VolumeStructure{
   275  			Label: "foo",
   276  		},
   277  	})
   278  	c.Check(err, ErrorMatches, `device not found`)
   279  	c.Check(found, Equals, "")
   280  }
   281  
   282  func (d *deviceSuite) TestDeviceFindNotFoundNotASymlink(c *C) {
   283  	err := ioutil.WriteFile(filepath.Join(d.dir, "/dev/disk/by-label/foo"), nil, 0644)
   284  	c.Assert(err, IsNil)
   285  
   286  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   287  		VolumeStructure: &gadget.VolumeStructure{
   288  			Filesystem: "ext4",
   289  			Label:      "foo",
   290  		},
   291  	})
   292  	c.Check(err, ErrorMatches, `candidate .*/dev/disk/by-label/foo is not a symlink`)
   293  	c.Check(found, Equals, "")
   294  }
   295  
   296  func (d *deviceSuite) TestDeviceFindBadEvalSymlinks(c *C) {
   297  	fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
   298  	fooSymlink := filepath.Join(d.dir, "/dev/disk/by-label/foo")
   299  	err := os.Symlink(fakedevice, fooSymlink)
   300  	c.Assert(err, IsNil)
   301  
   302  	restore := gadget.MockEvalSymlinks(func(p string) (string, error) {
   303  		c.Assert(p, Equals, fooSymlink)
   304  		return "", errors.New("failed")
   305  	})
   306  	defer restore()
   307  
   308  	found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
   309  		VolumeStructure: &gadget.VolumeStructure{
   310  			Filesystem: "vfat",
   311  			Label:      "foo",
   312  		},
   313  	})
   314  	c.Check(err, ErrorMatches, `cannot read device link: failed`)
   315  	c.Check(found, Equals, "")
   316  }
   317  
   318  var writableMountInfoFmt = `26 27 8:3 / /writable rw,relatime shared:7 - ext4 %s/dev/fakedevice0p1 rw,data=ordered`
   319  
   320  func (d *deviceSuite) TestDeviceFindFallbackNotFoundNoWritable(c *C) {
   321  	badMountInfoFmt := `26 27 8:3 / /not-writable rw,relatime shared:7 - ext4 %s/dev/fakedevice0p1 rw,data=ordered`
   322  	restore := osutil.MockMountInfo(fmt.Sprintf(badMountInfoFmt, d.dir))
   323  	defer restore()
   324  
   325  	found, offs, err := gadget.FindDeviceForStructureWithFallback(&gadget.LaidOutStructure{
   326  		VolumeStructure: &gadget.VolumeStructure{
   327  			Type: "bare",
   328  		},
   329  		StartOffset: 123,
   330  	})
   331  	c.Check(err, ErrorMatches, `device not found`)
   332  	c.Check(found, Equals, "")
   333  	c.Check(offs, Equals, gadget.Size(0))
   334  }
   335  
   336  func (d *deviceSuite) TestDeviceFindFallbackBadWritable(c *C) {
   337  	restore := osutil.MockMountInfo(fmt.Sprintf(writableMountInfoFmt, d.dir))
   338  	defer restore()
   339  
   340  	ps := &gadget.LaidOutStructure{
   341  		VolumeStructure: &gadget.VolumeStructure{
   342  			Type: "bare",
   343  		},
   344  		StartOffset: 123,
   345  	}
   346  
   347  	found, offs, err := gadget.FindDeviceForStructureWithFallback(ps)
   348  	c.Check(err, ErrorMatches, `lstat .*/dev/fakedevice0p1: no such file or directory`)
   349  	c.Check(found, Equals, "")
   350  	c.Check(offs, Equals, gadget.Size(0))
   351  
   352  	c.Assert(ioutil.WriteFile(filepath.Join(d.dir, "dev/fakedevice0p1"), nil, 064), IsNil)
   353  
   354  	found, offs, err = gadget.FindDeviceForStructureWithFallback(ps)
   355  	c.Check(err, ErrorMatches, `unexpected number of matches \(0\) for /sys/block/\*/fakedevice0p1`)
   356  	c.Check(found, Equals, "")
   357  	c.Check(offs, Equals, gadget.Size(0))
   358  
   359  	err = os.MkdirAll(filepath.Join(d.dir, "/sys/block/fakedevice0/fakedevice0p1"), 0755)
   360  	c.Assert(err, IsNil)
   361  
   362  	found, offs, err = gadget.FindDeviceForStructureWithFallback(ps)
   363  	c.Check(err, ErrorMatches, `device .*/dev/fakedevice0 does not exist`)
   364  	c.Check(found, Equals, "")
   365  	c.Check(offs, Equals, gadget.Size(0))
   366  }
   367  
   368  func (d *deviceSuite) TestDeviceFindFallbackHappyWritable(c *C) {
   369  	d.setupMockSysfs(c)
   370  	restore := osutil.MockMountInfo(fmt.Sprintf(writableMountInfoFmt, d.dir))
   371  	defer restore()
   372  
   373  	psJustBare := &gadget.LaidOutStructure{
   374  		VolumeStructure: &gadget.VolumeStructure{
   375  			Type: "bare",
   376  		},
   377  		StartOffset: 123,
   378  	}
   379  	psBareWithName := &gadget.LaidOutStructure{
   380  		VolumeStructure: &gadget.VolumeStructure{
   381  			Type: "bare",
   382  			Name: "foo",
   383  		},
   384  		StartOffset: 123,
   385  	}
   386  	psMBR := &gadget.LaidOutStructure{
   387  		VolumeStructure: &gadget.VolumeStructure{
   388  			Type: "mbr",
   389  			Name: "mbr",
   390  		},
   391  		StartOffset: 0,
   392  	}
   393  	psNoName := &gadget.LaidOutStructure{
   394  		VolumeStructure: &gadget.VolumeStructure{},
   395  		StartOffset:     123,
   396  	}
   397  
   398  	for _, ps := range []*gadget.LaidOutStructure{psJustBare, psBareWithName, psNoName, psMBR} {
   399  		found, offs, err := gadget.FindDeviceForStructureWithFallback(ps)
   400  		c.Check(err, IsNil)
   401  		c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice0"))
   402  		if ps.Type != "mbr" {
   403  			c.Check(offs, Equals, gadget.Size(123))
   404  		} else {
   405  			c.Check(offs, Equals, gadget.Size(0))
   406  		}
   407  	}
   408  }
   409  
   410  func (d *deviceSuite) TestDeviceFindFallbackNotForNamedWritable(c *C) {
   411  	d.setupMockSysfs(c)
   412  	restore := osutil.MockMountInfo(fmt.Sprintf(writableMountInfoFmt, d.dir))
   413  	defer restore()
   414  
   415  	// should not hit the fallback path
   416  	psNamed := &gadget.LaidOutStructure{
   417  		VolumeStructure: &gadget.VolumeStructure{
   418  			Name: "foo",
   419  		},
   420  		StartOffset: 123,
   421  	}
   422  	found, offs, err := gadget.FindDeviceForStructureWithFallback(psNamed)
   423  	c.Check(err, Equals, gadget.ErrDeviceNotFound)
   424  	c.Check(found, Equals, "")
   425  	c.Check(offs, Equals, gadget.Size(0))
   426  }
   427  
   428  func (d *deviceSuite) TestDeviceFindFallbackNotForFilesystem(c *C) {
   429  	d.setupMockSysfs(c)
   430  	restore := osutil.MockMountInfo(writableMountInfoFmt)
   431  	defer restore()
   432  
   433  	psFs := &gadget.LaidOutStructure{
   434  		VolumeStructure: &gadget.VolumeStructure{
   435  			Label:      "foo",
   436  			Filesystem: "ext4",
   437  		},
   438  		StartOffset: 123,
   439  	}
   440  	found, offs, err := gadget.FindDeviceForStructureWithFallback(psFs)
   441  	c.Check(err, ErrorMatches, "internal error: cannot use with filesystem structures")
   442  	c.Check(found, Equals, "")
   443  	c.Check(offs, Equals, gadget.Size(0))
   444  }
   445  
   446  func (d *deviceSuite) TestDeviceFindFallbackBadMountInfo(c *C) {
   447  	d.setupMockSysfs(c)
   448  	restore := osutil.MockMountInfo("garbage")
   449  	defer restore()
   450  	psFs := &gadget.LaidOutStructure{
   451  		VolumeStructure: &gadget.VolumeStructure{
   452  			Name: "foo",
   453  			Type: "bare",
   454  		},
   455  		StartOffset: 123,
   456  	}
   457  	found, offs, err := gadget.FindDeviceForStructureWithFallback(psFs)
   458  	c.Check(err, ErrorMatches, "cannot read mount info: .*")
   459  	c.Check(found, Equals, "")
   460  	c.Check(offs, Equals, gadget.Size(0))
   461  }
   462  
   463  func (d *deviceSuite) TestDeviceFindFallbackPassThrough(c *C) {
   464  	err := ioutil.WriteFile(filepath.Join(d.dir, "/dev/disk/by-partlabel/foo"), nil, 0644)
   465  	c.Assert(err, IsNil)
   466  
   467  	ps := &gadget.LaidOutStructure{
   468  		VolumeStructure: &gadget.VolumeStructure{
   469  			Name: "foo",
   470  		},
   471  	}
   472  	found, offs, err := gadget.FindDeviceForStructureWithFallback(ps)
   473  	c.Check(err, ErrorMatches, `candidate .*/dev/disk/by-partlabel/foo is not a symlink`)
   474  	c.Check(found, Equals, "")
   475  	c.Check(offs, Equals, gadget.Size(0))
   476  
   477  	// create a proper symlink
   478  	err = os.Remove(filepath.Join(d.dir, "/dev/disk/by-partlabel/foo"))
   479  	c.Assert(err, IsNil)
   480  	err = os.Symlink("../../fakedevice", filepath.Join(d.dir, "/dev/disk/by-partlabel/foo"))
   481  	c.Assert(err, IsNil)
   482  
   483  	// this should be happy again
   484  	found, offs, err = gadget.FindDeviceForStructureWithFallback(ps)
   485  	c.Assert(err, IsNil)
   486  	c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice"))
   487  	c.Check(offs, Equals, gadget.Size(0))
   488  }
   489  
   490  func (d *deviceSuite) TestDeviceFindMountPointErrorsWithBare(c *C) {
   491  	p, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   492  		VolumeStructure: &gadget.VolumeStructure{
   493  			// no filesystem
   494  			Filesystem: "",
   495  		},
   496  	})
   497  	c.Assert(err, ErrorMatches, "no filesystem defined")
   498  	c.Check(p, Equals, "")
   499  
   500  	p, err = gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   501  		VolumeStructure: &gadget.VolumeStructure{
   502  			// also counts as bare structure
   503  			Filesystem: "none",
   504  		},
   505  	})
   506  	c.Assert(err, ErrorMatches, "no filesystem defined")
   507  	c.Check(p, Equals, "")
   508  }
   509  
   510  func (d *deviceSuite) TestDeviceFindMountPointErrorsFromDevice(c *C) {
   511  	p, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   512  		VolumeStructure: &gadget.VolumeStructure{
   513  			Label:      "bar",
   514  			Filesystem: "ext4",
   515  		},
   516  	})
   517  	c.Assert(err, ErrorMatches, "device not found")
   518  	c.Check(p, Equals, "")
   519  
   520  	p, err = gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   521  		VolumeStructure: &gadget.VolumeStructure{
   522  			Name:       "bar",
   523  			Filesystem: "ext4",
   524  		},
   525  	})
   526  	c.Assert(err, ErrorMatches, "device not found")
   527  	c.Check(p, Equals, "")
   528  }
   529  
   530  func (d *deviceSuite) TestDeviceFindMountPointErrorBadMountinfo(c *C) {
   531  	// taken from core18 system
   532  
   533  	fakedevice := filepath.Join(d.dir, "/dev/sda2")
   534  	err := ioutil.WriteFile(fakedevice, []byte(""), 0644)
   535  	c.Assert(err, IsNil)
   536  	err = os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/system-boot"))
   537  	c.Assert(err, IsNil)
   538  	restore := osutil.MockMountInfo("garbage")
   539  	defer restore()
   540  
   541  	found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   542  		VolumeStructure: &gadget.VolumeStructure{
   543  			Name:       "EFI System",
   544  			Label:      "system-boot",
   545  			Filesystem: "vfat",
   546  		},
   547  	})
   548  	c.Check(err, ErrorMatches, "cannot read mount info: .*")
   549  	c.Check(found, Equals, "")
   550  }
   551  
   552  func (d *deviceSuite) TestDeviceFindMountPointByLabeHappySimple(c *C) {
   553  	// taken from core18 system
   554  
   555  	fakedevice := filepath.Join(d.dir, "/dev/sda2")
   556  	err := ioutil.WriteFile(fakedevice, []byte(""), 0644)
   557  	c.Assert(err, IsNil)
   558  	err = os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/system-boot"))
   559  	c.Assert(err, IsNil)
   560  	err = os.Symlink(fakedevice, filepath.Join(d.dir, `/dev/disk/by-partlabel/EFI\x20System`))
   561  	c.Assert(err, IsNil)
   562  
   563  	mountInfo := `
   564  170 27 8:2 / /boot/efi rw,relatime shared:58 - vfat ${rootDir}/dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
   565  172 27 8:2 /EFI/ubuntu /boot/grub rw,relatime shared:58 - vfat ${rootDir}/dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
   566  `
   567  	restore := osutil.MockMountInfo(strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
   568  	defer restore()
   569  
   570  	found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   571  		VolumeStructure: &gadget.VolumeStructure{
   572  			Name:       "EFI System",
   573  			Label:      "system-boot",
   574  			Filesystem: "vfat",
   575  		},
   576  	})
   577  	c.Check(err, IsNil)
   578  	c.Check(found, Equals, "/boot/efi")
   579  }
   580  
   581  func (d *deviceSuite) TestDeviceFindMountPointByLabeHappyReversed(c *C) {
   582  	// taken from core18 system
   583  
   584  	fakedevice := filepath.Join(d.dir, "/dev/sda2")
   585  	err := ioutil.WriteFile(fakedevice, []byte(""), 0644)
   586  	c.Assert(err, IsNil)
   587  	// single property match
   588  	err = os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/system-boot"))
   589  	c.Assert(err, IsNil)
   590  
   591  	// reverse the order of lines
   592  	mountInfoReversed := `
   593  172 27 8:2 /EFI/ubuntu /boot/grub rw,relatime shared:58 - vfat ${rootDir}/dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
   594  170 27 8:2 / /boot/efi rw,relatime shared:58 - vfat ${rootDir}/dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
   595  `
   596  
   597  	restore := osutil.MockMountInfo(strings.Replace(mountInfoReversed[1:], "${rootDir}", d.dir, -1))
   598  	defer restore()
   599  
   600  	found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   601  		VolumeStructure: &gadget.VolumeStructure{
   602  			Name:       "EFI System",
   603  			Label:      "system-boot",
   604  			Filesystem: "vfat",
   605  		},
   606  	})
   607  	c.Check(err, IsNil)
   608  	c.Check(found, Equals, "/boot/efi")
   609  }
   610  
   611  func (d *deviceSuite) TestDeviceFindMountPointPicksFirstMatch(c *C) {
   612  	// taken from core18 system
   613  
   614  	fakedevice := filepath.Join(d.dir, "/dev/sda2")
   615  	err := ioutil.WriteFile(fakedevice, []byte(""), 0644)
   616  	c.Assert(err, IsNil)
   617  	// single property match
   618  	err = os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/system-boot"))
   619  	c.Assert(err, IsNil)
   620  
   621  	mountInfo := `
   622  852 134 8:2 / /mnt/foo rw,relatime shared:58 - vfat ${rootDir}/dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
   623  172 27 8:2 /EFI/ubuntu /boot/grub rw,relatime shared:58 - vfat ${rootDir}/dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
   624  170 27 8:2 / /boot/efi rw,relatime shared:58 - vfat ${rootDir}/dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
   625  `
   626  
   627  	restore := osutil.MockMountInfo(strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
   628  	defer restore()
   629  
   630  	found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   631  		VolumeStructure: &gadget.VolumeStructure{
   632  			Name:       "EFI System",
   633  			Label:      "system-boot",
   634  			Filesystem: "vfat",
   635  		},
   636  	})
   637  	c.Check(err, IsNil)
   638  	c.Check(found, Equals, "/mnt/foo")
   639  }
   640  
   641  func (d *deviceSuite) TestDeviceFindMountPointByPartlabel(c *C) {
   642  	fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
   643  	err := ioutil.WriteFile(fakedevice, []byte(""), 0644)
   644  	c.Assert(err, IsNil)
   645  	err = os.Symlink(fakedevice, filepath.Join(d.dir, `/dev/disk/by-partlabel/pinkié\x20pie`))
   646  	c.Assert(err, IsNil)
   647  
   648  	mountInfo := `
   649  170 27 8:2 / /mount-point rw,relatime shared:58 - ext4 ${rootDir}/dev/fakedevice rw
   650  `
   651  
   652  	restore := osutil.MockMountInfo(strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
   653  	defer restore()
   654  
   655  	found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   656  		VolumeStructure: &gadget.VolumeStructure{
   657  			Name:       "pinkié pie",
   658  			Filesystem: "ext4",
   659  		},
   660  	})
   661  	c.Check(err, IsNil)
   662  	c.Check(found, Equals, "/mount-point")
   663  }
   664  
   665  func (d *deviceSuite) TestDeviceFindMountPointChecksFilesystem(c *C) {
   666  	fakedevice := filepath.Join(d.dir, "/dev/fakedevice")
   667  	err := ioutil.WriteFile(fakedevice, []byte(""), 0644)
   668  	c.Assert(err, IsNil)
   669  	err = os.Symlink(fakedevice, filepath.Join(d.dir, `/dev/disk/by-partlabel/label`))
   670  	c.Assert(err, IsNil)
   671  
   672  	mountInfo := `
   673  170 27 8:2 / /mount-point rw,relatime shared:58 - vfat ${rootDir}/dev/fakedevice rw
   674  `
   675  
   676  	restore := osutil.MockMountInfo(strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
   677  	defer restore()
   678  
   679  	found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
   680  		VolumeStructure: &gadget.VolumeStructure{
   681  			Name: "label",
   682  			// different fs than mount entry
   683  			Filesystem: "ext4",
   684  		},
   685  	})
   686  	c.Check(err, ErrorMatches, "mount point not found")
   687  	c.Check(found, Equals, "")
   688  }
   689  
   690  func (d *deviceSuite) TestParentDiskFromMountSource(c *C) {
   691  	d.setupMockSysfs(c)
   692  
   693  	disk, err := gadget.ParentDiskFromMountSource(filepath.Join(dirs.GlobalRootDir, "/dev/fakedevice0p1"))
   694  	c.Assert(err, IsNil)
   695  	c.Check(disk, Matches, ".*/dev/fakedevice0")
   696  }
   697  
   698  func (d *deviceSuite) TestParentDiskFromMountSourceBadSymlinkErr(c *C) {
   699  	d.setupMockSysfs(c)
   700  
   701  	err := os.Symlink("../bad-target", filepath.Join(d.dir, "/dev/mapper/bad-target-symlink"))
   702  	c.Assert(err, IsNil)
   703  
   704  	_, err = gadget.ParentDiskFromMountSource(filepath.Join(dirs.GlobalRootDir, "/dev/mapper/bad-target-symlink"))
   705  	c.Assert(err, ErrorMatches, `cannot resolve mount source symlink .*/dev/mapper/bad-target-symlink: lstat .*/dev/bad-target: no such file or directory`)
   706  }
   707  
   708  func (d *deviceSuite) TestParentDiskFromMountSourceDeviceMapperHappy(c *C) {
   709  	d.setupMockSysfsForDevMapper(c)
   710  
   711  	disk, err := gadget.ParentDiskFromMountSource(filepath.Join(dirs.GlobalRootDir, "/dev/mapper/data_crypt"))
   712  
   713  	c.Assert(err, IsNil)
   714  	c.Check(disk, Matches, ".*/dev/fakedevice")
   715  }
   716  
   717  func (d *deviceSuite) TestParentDiskFromMountSourceDeviceMapperErrGlob(c *C) {
   718  	d.setupMockSysfsForDevMapper(c)
   719  
   720  	// break the intermediate slaves directory
   721  	c.Assert(os.RemoveAll(filepath.Join(d.dir, "/sys/block/dm-0/slaves/fakedevice0")), IsNil)
   722  
   723  	_, err := gadget.ParentDiskFromMountSource(filepath.Join(dirs.GlobalRootDir, "/dev/mapper/data_crypt"))
   724  	c.Assert(err, ErrorMatches, "cannot resolve device mapper device dm-1: unexpected number of dm device dm-0 slaves: 0")
   725  
   726  	c.Assert(os.Chmod(filepath.Join(d.dir, "/sys/block/dm-0"), 0000), IsNil)
   727  	defer os.Chmod(filepath.Join(d.dir, "/sys/block/dm-0"), 0755)
   728  
   729  	_, err = gadget.ParentDiskFromMountSource(filepath.Join(dirs.GlobalRootDir, "/dev/mapper/data_crypt"))
   730  	c.Assert(err, ErrorMatches, "cannot resolve device mapper device dm-1: unexpected number of dm device dm-0 slaves: 0")
   731  }
   732  
   733  func (d *deviceSuite) TestParentDiskFromMountSourceDeviceMapperErrTargetDevice(c *C) {
   734  	d.setupMockSysfsForDevMapper(c)
   735  
   736  	c.Assert(os.RemoveAll(filepath.Join(d.dir, "/sys/block/fakedevice")), IsNil)
   737  
   738  	_, err := gadget.ParentDiskFromMountSource(filepath.Join(dirs.GlobalRootDir, "/dev/mapper/data_crypt"))
   739  	c.Assert(err, ErrorMatches, `unexpected number of matches \(0\) for /sys/block/\*/fakedevice0`)
   740  }
   741  
   742  func (d *deviceSuite) TestParentDiskFromMountSourceDeviceMapperLevels(c *C) {
   743  	err := os.Symlink("../dm-6", filepath.Join(d.dir, "/dev/mapper/data_crypt"))
   744  	c.Assert(err, IsNil)
   745  	for i := 6; i > 0; i-- {
   746  		err := ioutil.WriteFile(filepath.Join(d.dir, fmt.Sprintf("/dev/dm-%v", i)), nil, 0644)
   747  		c.Assert(err, IsNil)
   748  		err = os.MkdirAll(filepath.Join(d.dir, fmt.Sprintf("/sys/block/dm-%v/slaves/", i)), 0755)
   749  		c.Assert(err, IsNil)
   750  		// sys symlinks are relative too
   751  		err = os.Symlink(fmt.Sprintf("../../dm-%v", i-1), filepath.Join(d.dir, fmt.Sprintf("/sys/block/dm-%v/slaves/dm-%v", i, i-1)))
   752  		c.Assert(err, IsNil)
   753  	}
   754  
   755  	_, err = gadget.ParentDiskFromMountSource(filepath.Join(dirs.GlobalRootDir, "/dev/mapper/data_crypt"))
   756  	c.Assert(err, ErrorMatches, `cannot resolve device mapper device dm-6: too many levels`)
   757  }