...

Source file src/k8s.io/utils/mount/mount_linux.go

Documentation: k8s.io/utils/mount

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5  Copyright 2014 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package mount
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"strconv"
    28  	"strings"
    29  	"syscall"
    30  
    31  	"k8s.io/klog/v2"
    32  	utilexec "k8s.io/utils/exec"
    33  	utilio "k8s.io/utils/io"
    34  )
    35  
    36  const (
    37  	// Number of fields per line in /proc/mounts as per the fstab man page.
    38  	expectedNumFieldsPerLine = 6
    39  	// Location of the mount file to use
    40  	procMountsPath = "/proc/mounts"
    41  	// Location of the mountinfo file
    42  	procMountInfoPath = "/proc/self/mountinfo"
    43  	// 'fsck' found errors and corrected them
    44  	fsckErrorsCorrected = 1
    45  	// 'fsck' found errors but exited without correcting them
    46  	fsckErrorsUncorrected = 4
    47  )
    48  
    49  // Mounter provides the default implementation of mount.Interface
    50  // for the linux platform.  This implementation assumes that the
    51  // kubelet is running in the host's root mount namespace.
    52  type Mounter struct {
    53  	mounterPath string
    54  	withSystemd bool
    55  }
    56  
    57  // New returns a mount.Interface for the current system.
    58  // It provides options to override the default mounter behavior.
    59  // mounterPath allows using an alternative to `/bin/mount` for mounting.
    60  func New(mounterPath string) Interface {
    61  	return &Mounter{
    62  		mounterPath: mounterPath,
    63  		withSystemd: detectSystemd(),
    64  	}
    65  }
    66  
    67  // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must
    68  // be an empty string in case it's not required, e.g. for remount, or for auto filesystem
    69  // type, where kernel handles fstype for you. The mount 'options' is a list of options,
    70  // currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is
    71  // required, call Mount with an empty string list or nil.
    72  func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
    73  	return mounter.MountSensitive(source, target, fstype, options, nil)
    74  }
    75  
    76  // MountSensitive is the same as Mount() but this method allows
    77  // sensitiveOptions to be passed in a separate parameter from the normal
    78  // mount options and ensures the sensitiveOptions are never logged. This
    79  // method should be used by callers that pass sensitive material (like
    80  // passwords) as mount options.
    81  func (mounter *Mounter) MountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error {
    82  	// Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty.
    83  	// All Linux distros are expected to be shipped with a mount utility that a support bind mounts.
    84  	mounterPath := ""
    85  	bind, bindOpts, bindRemountOpts, bindRemountOptsSensitive := MakeBindOptsSensitive(options, sensitiveOptions)
    86  	if bind {
    87  		err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts, bindRemountOptsSensitive)
    88  		if err != nil {
    89  			return err
    90  		}
    91  		return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts, bindRemountOptsSensitive)
    92  	}
    93  	// The list of filesystems that require containerized mounter on GCI image cluster
    94  	fsTypesNeedMounter := map[string]struct{}{
    95  		"nfs":       {},
    96  		"glusterfs": {},
    97  		"ceph":      {},
    98  		"cifs":      {},
    99  	}
   100  	if _, ok := fsTypesNeedMounter[fstype]; ok {
   101  		mounterPath = mounter.mounterPath
   102  	}
   103  	return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, options, sensitiveOptions)
   104  }
   105  
   106  // doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used.
   107  // sensitiveOptions is an extension of options except they will not be logged (because they may contain sensitive material)
   108  func (mounter *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string, sensitiveOptions []string) error {
   109  	mountArgs, mountArgsLogStr := MakeMountArgsSensitive(source, target, fstype, options, sensitiveOptions)
   110  	if len(mounterPath) > 0 {
   111  		mountArgs = append([]string{mountCmd}, mountArgs...)
   112  		mountArgsLogStr = mountCmd + " " + mountArgsLogStr
   113  		mountCmd = mounterPath
   114  	}
   115  
   116  	if mounter.withSystemd {
   117  		// Try to run mount via systemd-run --scope. This will escape the
   118  		// service where kubelet runs and any fuse daemons will be started in a
   119  		// specific scope. kubelet service than can be restarted without killing
   120  		// these fuse daemons.
   121  		//
   122  		// Complete command line (when mounterPath is not used):
   123  		// systemd-run --description=... --scope -- mount -t <type> <what> <where>
   124  		//
   125  		// Expected flow:
   126  		// * systemd-run creates a transient scope (=~ cgroup) and executes its
   127  		//   argument (/bin/mount) there.
   128  		// * mount does its job, forks a fuse daemon if necessary and finishes.
   129  		//   (systemd-run --scope finishes at this point, returning mount's exit
   130  		//   code and stdout/stderr - thats one of --scope benefits).
   131  		// * systemd keeps the fuse daemon running in the scope (i.e. in its own
   132  		//   cgroup) until the fuse daemon dies (another --scope benefit).
   133  		//   Kubelet service can be restarted and the fuse daemon survives.
   134  		// * When the fuse daemon dies (e.g. during unmount) systemd removes the
   135  		//   scope automatically.
   136  		//
   137  		// systemd-mount is not used because it's too new for older distros
   138  		// (CentOS 7, Debian Jessie).
   139  		mountCmd, mountArgs, mountArgsLogStr = AddSystemdScopeSensitive("systemd-run", target, mountCmd, mountArgs, mountArgsLogStr)
   140  	} else {
   141  		// No systemd-run on the host (or we failed to check it), assume kubelet
   142  		// does not run as a systemd service.
   143  		// No code here, mountCmd and mountArgs are already populated.
   144  	}
   145  
   146  	// Logging with sensitive mount options removed.
   147  	klog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgsLogStr)
   148  	command := exec.Command(mountCmd, mountArgs...)
   149  	output, err := command.CombinedOutput()
   150  	if err != nil {
   151  		klog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n", err, mountCmd, mountArgsLogStr, string(output))
   152  		return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s",
   153  			err, mountCmd, mountArgsLogStr, string(output))
   154  	}
   155  	return err
   156  }
   157  
   158  // detectSystemd returns true if OS runs with systemd as init. When not sure
   159  // (permission errors, ...), it returns false.
   160  // There may be different ways how to detect systemd, this one makes sure that
   161  // systemd-runs (needed by Mount()) works.
   162  func detectSystemd() bool {
   163  	if _, err := exec.LookPath("systemd-run"); err != nil {
   164  		klog.V(2).Infof("Detected OS without systemd")
   165  		return false
   166  	}
   167  	// Try to run systemd-run --scope /bin/true, that should be enough
   168  	// to make sure that systemd is really running and not just installed,
   169  	// which happens when running in a container with a systemd-based image
   170  	// but with different pid 1.
   171  	cmd := exec.Command("systemd-run", "--description=Kubernetes systemd probe", "--scope", "true")
   172  	output, err := cmd.CombinedOutput()
   173  	if err != nil {
   174  		klog.V(2).Infof("Cannot run systemd-run, assuming non-systemd OS")
   175  		klog.V(4).Infof("systemd-run failed with: %v", err)
   176  		klog.V(4).Infof("systemd-run output: %s", string(output))
   177  		return false
   178  	}
   179  	klog.V(2).Infof("Detected OS with systemd")
   180  	return true
   181  }
   182  
   183  // MakeMountArgs makes the arguments to the mount(8) command.
   184  // options MUST not contain sensitive material (like passwords).
   185  func MakeMountArgs(source, target, fstype string, options []string) (mountArgs []string) {
   186  	mountArgs, _ = MakeMountArgsSensitive(source, target, fstype, options, nil /* sensitiveOptions */)
   187  	return mountArgs
   188  }
   189  
   190  // MakeMountArgsSensitive makes the arguments to the mount(8) command.
   191  // sensitiveOptions is an extension of options except they will not be logged (because they may contain sensitive material)
   192  func MakeMountArgsSensitive(source, target, fstype string, options []string, sensitiveOptions []string) (mountArgs []string, mountArgsLogStr string) {
   193  	// Build mount command as follows:
   194  	//   mount [-t $fstype] [-o $options] [$source] $target
   195  	mountArgs = []string{}
   196  	mountArgsLogStr = ""
   197  	if len(fstype) > 0 {
   198  		mountArgs = append(mountArgs, "-t", fstype)
   199  		mountArgsLogStr += strings.Join(mountArgs, " ")
   200  	}
   201  	if len(options) > 0 || len(sensitiveOptions) > 0 {
   202  		combinedOptions := []string{}
   203  		combinedOptions = append(combinedOptions, options...)
   204  		combinedOptions = append(combinedOptions, sensitiveOptions...)
   205  		mountArgs = append(mountArgs, "-o", strings.Join(combinedOptions, ","))
   206  		// exclude sensitiveOptions from log string
   207  		mountArgsLogStr += " -o " + sanitizedOptionsForLogging(options, sensitiveOptions)
   208  	}
   209  	if len(source) > 0 {
   210  		mountArgs = append(mountArgs, source)
   211  		mountArgsLogStr += " " + source
   212  	}
   213  	mountArgs = append(mountArgs, target)
   214  	mountArgsLogStr += " " + target
   215  
   216  	return mountArgs, mountArgsLogStr
   217  }
   218  
   219  // AddSystemdScope adds "system-run --scope" to given command line
   220  // If args contains sensitive material, use AddSystemdScopeSensitive to construct
   221  // a safe to log string.
   222  func AddSystemdScope(systemdRunPath, mountName, command string, args []string) (string, []string) {
   223  	descriptionArg := fmt.Sprintf("--description=Kubernetes transient mount for %s", mountName)
   224  	systemdRunArgs := []string{descriptionArg, "--scope", "--", command}
   225  	return systemdRunPath, append(systemdRunArgs, args...)
   226  }
   227  
   228  // AddSystemdScopeSensitive adds "system-run --scope" to given command line
   229  // It also accepts takes a sanitized string containing mount arguments, mountArgsLogStr,
   230  // and returns the string appended to the systemd command for logging.
   231  func AddSystemdScopeSensitive(systemdRunPath, mountName, command string, args []string, mountArgsLogStr string) (string, []string, string) {
   232  	descriptionArg := fmt.Sprintf("--description=Kubernetes transient mount for %s", mountName)
   233  	systemdRunArgs := []string{descriptionArg, "--scope", "--", command}
   234  	return systemdRunPath, append(systemdRunArgs, args...), strings.Join(systemdRunArgs, " ") + " " + mountArgsLogStr
   235  }
   236  
   237  // Unmount unmounts the target.
   238  func (mounter *Mounter) Unmount(target string) error {
   239  	klog.V(4).Infof("Unmounting %s", target)
   240  	command := exec.Command("umount", target)
   241  	output, err := command.CombinedOutput()
   242  	if err != nil {
   243  		return fmt.Errorf("unmount failed: %v\nUnmounting arguments: %s\nOutput: %s", err, target, string(output))
   244  	}
   245  	return nil
   246  }
   247  
   248  // List returns a list of all mounted filesystems.
   249  func (*Mounter) List() ([]MountPoint, error) {
   250  	return ListProcMounts(procMountsPath)
   251  }
   252  
   253  // IsLikelyNotMountPoint determines if a directory is not a mountpoint.
   254  // It is fast but not necessarily ALWAYS correct. If the path is in fact
   255  // a bind mount from one part of a mount to another it will not be detected.
   256  // It also can not distinguish between mountpoints and symbolic links.
   257  // mkdir /tmp/a /tmp/b; mount --bind /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b")
   258  // will return true. When in fact /tmp/b is a mount point. If this situation
   259  // is of interest to you, don't use this function...
   260  func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
   261  	stat, err := os.Stat(file)
   262  	if err != nil {
   263  		return true, err
   264  	}
   265  	rootStat, err := os.Stat(filepath.Dir(strings.TrimSuffix(file, "/")))
   266  	if err != nil {
   267  		return true, err
   268  	}
   269  	// If the directory has a different device as parent, then it is a mountpoint.
   270  	if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev {
   271  		return false, nil
   272  	}
   273  
   274  	return true, nil
   275  }
   276  
   277  // GetMountRefs finds all mount references to pathname, returns a
   278  // list of paths. Path could be a mountpoint or a normal
   279  // directory (for bind mount).
   280  func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
   281  	pathExists, pathErr := PathExists(pathname)
   282  	if !pathExists {
   283  		return []string{}, nil
   284  	} else if IsCorruptedMnt(pathErr) {
   285  		klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", pathname)
   286  		return []string{}, nil
   287  	} else if pathErr != nil {
   288  		return nil, fmt.Errorf("error checking path %s: %v", pathname, pathErr)
   289  	}
   290  	realpath, err := filepath.EvalSymlinks(pathname)
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  	return SearchMountPoints(realpath, procMountInfoPath)
   295  }
   296  
   297  // checkAndRepairFileSystem checks and repairs filesystems using command fsck.
   298  func (mounter *SafeFormatAndMount) checkAndRepairFilesystem(source string) error {
   299  	klog.V(4).Infof("Checking for issues with fsck on disk: %s", source)
   300  	args := []string{"-a", source}
   301  	out, err := mounter.Exec.Command("fsck", args...).CombinedOutput()
   302  	if err != nil {
   303  		ee, isExitError := err.(utilexec.ExitError)
   304  		switch {
   305  		case err == utilexec.ErrExecutableNotFound:
   306  			klog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.")
   307  		case isExitError && ee.ExitStatus() == fsckErrorsCorrected:
   308  			klog.Infof("Device %s has errors which were corrected by fsck.", source)
   309  		case isExitError && ee.ExitStatus() == fsckErrorsUncorrected:
   310  			return NewMountError(HasFilesystemErrors, "'fsck' found errors on device %s but could not correct them: %s", source, string(out))
   311  		case isExitError && ee.ExitStatus() > fsckErrorsUncorrected:
   312  			klog.Infof("`fsck` error %s", string(out))
   313  		}
   314  	}
   315  	return nil
   316  }
   317  
   318  // formatAndMount uses unix utils to format and mount the given disk
   319  func (mounter *SafeFormatAndMount) formatAndMountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error {
   320  	readOnly := false
   321  	for _, option := range options {
   322  		if option == "ro" {
   323  			readOnly = true
   324  			break
   325  		}
   326  	}
   327  	if !readOnly {
   328  		// Check sensitiveOptions for ro
   329  		for _, option := range sensitiveOptions {
   330  			if option == "ro" {
   331  				readOnly = true
   332  				break
   333  			}
   334  		}
   335  	}
   336  
   337  	options = append(options, "defaults")
   338  	mountErrorValue := UnknownMountError
   339  
   340  	// Check if the disk is already formatted
   341  	existingFormat, err := mounter.GetDiskFormat(source)
   342  	if err != nil {
   343  		return NewMountError(GetDiskFormatFailed, "failed to get disk format of disk %s: %v", source, err)
   344  	}
   345  
   346  	// Use 'ext4' as the default
   347  	if len(fstype) == 0 {
   348  		fstype = "ext4"
   349  	}
   350  
   351  	if existingFormat == "" {
   352  		// Do not attempt to format the disk if mounting as readonly, return an error to reflect this.
   353  		if readOnly {
   354  			return NewMountError(UnformattedReadOnly, "cannot mount unformatted disk %s as we are manipulating it in read-only mode", source)
   355  		}
   356  
   357  		// Disk is unformatted so format it.
   358  		args := []string{source}
   359  		if fstype == "ext4" || fstype == "ext3" {
   360  			args = []string{
   361  				"-F",  // Force flag
   362  				"-m0", // Zero blocks reserved for super-user
   363  				source,
   364  			}
   365  		}
   366  
   367  		klog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args)
   368  		output, err := mounter.Exec.Command("mkfs."+fstype, args...).CombinedOutput()
   369  		if err != nil {
   370  			// Do not log sensitiveOptions only options
   371  			sensitiveOptionsLog := sanitizedOptionsForLogging(options, sensitiveOptions)
   372  			detailedErr := fmt.Sprintf("format of disk %q failed: type:(%q) target:(%q) options:(%q) errcode:(%v) output:(%v) ", source, fstype, target, sensitiveOptionsLog, err, string(output))
   373  			klog.Error(detailedErr)
   374  			return NewMountError(FormatFailed, detailedErr)
   375  		}
   376  
   377  		klog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target)
   378  	} else {
   379  		if fstype != existingFormat {
   380  			// Verify that the disk is formatted with filesystem type we are expecting
   381  			mountErrorValue = FilesystemMismatch
   382  			klog.Warningf("Configured to mount disk %s as %s but current format is %s, things might break", source, existingFormat, fstype)
   383  		}
   384  
   385  		if !readOnly {
   386  			// Run check tools on the disk to fix repairable issues, only do this for formatted volumes requested as rw.
   387  			err := mounter.checkAndRepairFilesystem(source)
   388  			if err != nil {
   389  				return err
   390  			}
   391  		}
   392  	}
   393  
   394  	// Mount the disk
   395  	klog.V(4).Infof("Attempting to mount disk %s in %s format at %s", source, fstype, target)
   396  	if err := mounter.MountSensitive(source, target, fstype, options, sensitiveOptions); err != nil {
   397  		return NewMountError(mountErrorValue, err.Error())
   398  	}
   399  
   400  	return nil
   401  }
   402  
   403  // GetDiskFormat uses 'blkid' to see if the given disk is unformatted
   404  func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) {
   405  	args := []string{"-p", "-s", "TYPE", "-s", "PTTYPE", "-o", "export", disk}
   406  	klog.V(4).Infof("Attempting to determine if disk %q is formatted using blkid with args: (%v)", disk, args)
   407  	dataOut, err := mounter.Exec.Command("blkid", args...).CombinedOutput()
   408  	output := string(dataOut)
   409  	klog.V(4).Infof("Output: %q, err: %v", output, err)
   410  
   411  	if err != nil {
   412  		if exit, ok := err.(utilexec.ExitError); ok {
   413  			if exit.ExitStatus() == 2 {
   414  				// Disk device is unformatted.
   415  				// For `blkid`, if the specified token (TYPE/PTTYPE, etc) was
   416  				// not found, or no (specified) devices could be identified, an
   417  				// exit code of 2 is returned.
   418  				return "", nil
   419  			}
   420  		}
   421  		klog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err)
   422  		return "", err
   423  	}
   424  
   425  	var fstype, pttype string
   426  
   427  	lines := strings.Split(output, "\n")
   428  	for _, l := range lines {
   429  		if len(l) <= 0 {
   430  			// Ignore empty line.
   431  			continue
   432  		}
   433  		cs := strings.Split(l, "=")
   434  		if len(cs) != 2 {
   435  			return "", fmt.Errorf("blkid returns invalid output: %s", output)
   436  		}
   437  		// TYPE is filesystem type, and PTTYPE is partition table type, according
   438  		// to https://www.kernel.org/pub/linux/utils/util-linux/v2.21/libblkid-docs/.
   439  		if cs[0] == "TYPE" {
   440  			fstype = cs[1]
   441  		} else if cs[0] == "PTTYPE" {
   442  			pttype = cs[1]
   443  		}
   444  	}
   445  
   446  	if len(pttype) > 0 {
   447  		klog.V(4).Infof("Disk %s detected partition table type: %s", disk, pttype)
   448  		// Returns a special non-empty string as filesystem type, then kubelet
   449  		// will not format it.
   450  		return "unknown data, probably partitions", nil
   451  	}
   452  
   453  	return fstype, nil
   454  }
   455  
   456  // ListProcMounts is shared with NsEnterMounter
   457  func ListProcMounts(mountFilePath string) ([]MountPoint, error) {
   458  	content, err := utilio.ConsistentRead(mountFilePath, maxListTries)
   459  	if err != nil {
   460  		return nil, err
   461  	}
   462  	return parseProcMounts(content)
   463  }
   464  
   465  func parseProcMounts(content []byte) ([]MountPoint, error) {
   466  	out := []MountPoint{}
   467  	lines := strings.Split(string(content), "\n")
   468  	for _, line := range lines {
   469  		if line == "" {
   470  			// the last split() item is empty string following the last \n
   471  			continue
   472  		}
   473  		fields := strings.Fields(line)
   474  		if len(fields) != expectedNumFieldsPerLine {
   475  			// Do not log line in case it contains sensitive Mount options
   476  			return nil, fmt.Errorf("wrong number of fields (expected %d, got %d)", expectedNumFieldsPerLine, len(fields))
   477  		}
   478  
   479  		mp := MountPoint{
   480  			Device: fields[0],
   481  			Path:   fields[1],
   482  			Type:   fields[2],
   483  			Opts:   strings.Split(fields[3], ","),
   484  		}
   485  
   486  		freq, err := strconv.Atoi(fields[4])
   487  		if err != nil {
   488  			return nil, err
   489  		}
   490  		mp.Freq = freq
   491  
   492  		pass, err := strconv.Atoi(fields[5])
   493  		if err != nil {
   494  			return nil, err
   495  		}
   496  		mp.Pass = pass
   497  
   498  		out = append(out, mp)
   499  	}
   500  	return out, nil
   501  }
   502  
   503  // SearchMountPoints finds all mount references to the source, returns a list of
   504  // mountpoints.
   505  // The source can be a mount point or a normal directory (bind mount). We
   506  // didn't support device because there is no use case by now.
   507  // Some filesystems may share a source name, e.g. tmpfs. And for bind mounting,
   508  // it's possible to mount a non-root path of a filesystem, so we need to use
   509  // root path and major:minor to represent mount source uniquely.
   510  // This implementation is shared between Linux and NsEnterMounter
   511  func SearchMountPoints(hostSource, mountInfoPath string) ([]string, error) {
   512  	mis, err := ParseMountInfo(mountInfoPath)
   513  	if err != nil {
   514  		return nil, err
   515  	}
   516  
   517  	mountID := 0
   518  	rootPath := ""
   519  	major := -1
   520  	minor := -1
   521  
   522  	// Finding the underlying root path and major:minor if possible.
   523  	// We need search in backward order because it's possible for later mounts
   524  	// to overlap earlier mounts.
   525  	for i := len(mis) - 1; i >= 0; i-- {
   526  		if hostSource == mis[i].MountPoint || PathWithinBase(hostSource, mis[i].MountPoint) {
   527  			// If it's a mount point or path under a mount point.
   528  			mountID = mis[i].ID
   529  			rootPath = filepath.Join(mis[i].Root, strings.TrimPrefix(hostSource, mis[i].MountPoint))
   530  			major = mis[i].Major
   531  			minor = mis[i].Minor
   532  			break
   533  		}
   534  	}
   535  
   536  	if rootPath == "" || major == -1 || minor == -1 {
   537  		return nil, fmt.Errorf("failed to get root path and major:minor for %s", hostSource)
   538  	}
   539  
   540  	var refs []string
   541  	for i := range mis {
   542  		if mis[i].ID == mountID {
   543  			// Ignore mount entry for mount source itself.
   544  			continue
   545  		}
   546  		if mis[i].Root == rootPath && mis[i].Major == major && mis[i].Minor == minor {
   547  			refs = append(refs, mis[i].MountPoint)
   548  		}
   549  	}
   550  
   551  	return refs, nil
   552  }
   553  

View as plain text