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