...

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

Documentation: k8s.io/utils/mount

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // TODO(thockin): This whole pkg is pretty linux-centric.  As soon as we have
    18  // an alternate platform, we will need to abstract further.
    19  
    20  package mount
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	utilexec "k8s.io/utils/exec"
    29  )
    30  
    31  const (
    32  	// Default mount command if mounter path is not specified.
    33  	defaultMountCommand = "mount"
    34  	// Log message where sensitive mount options were removed
    35  	sensitiveOptionsRemoved = "<masked>"
    36  )
    37  
    38  // Interface defines the set of methods to allow for mount operations on a system.
    39  type Interface interface {
    40  	// Mount mounts source to target as fstype with given options.
    41  	// options MUST not contain sensitive material (like passwords).
    42  	Mount(source string, target string, fstype string, options []string) error
    43  	// MountSensitive is the same as Mount() but this method allows
    44  	// sensitiveOptions to be passed in a separate parameter from the normal
    45  	// mount options and ensures the sensitiveOptions are never logged. This
    46  	// method should be used by callers that pass sensitive material (like
    47  	// passwords) as mount options.
    48  	MountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error
    49  	// Unmount unmounts given target.
    50  	Unmount(target string) error
    51  	// List returns a list of all mounted filesystems.  This can be large.
    52  	// On some platforms, reading mounts directly from the OS is not guaranteed
    53  	// consistent (i.e. it could change between chunked reads). This is guaranteed
    54  	// to be consistent.
    55  	List() ([]MountPoint, error)
    56  	// IsLikelyNotMountPoint uses heuristics to determine if a directory
    57  	// is not a mountpoint.
    58  	// It should return ErrNotExist when the directory does not exist.
    59  	// IsLikelyNotMountPoint does NOT properly detect all mountpoint types
    60  	// most notably linux bind mounts and symbolic link. For callers that do not
    61  	// care about such situations, this is a faster alternative to calling List()
    62  	// and scanning that output.
    63  	IsLikelyNotMountPoint(file string) (bool, error)
    64  	// GetMountRefs finds all mount references to pathname, returning a slice of
    65  	// paths. Pathname can be a mountpoint path or a normal	directory
    66  	// (for bind mount). On Linux, pathname is excluded from the slice.
    67  	// For example, if /dev/sdc was mounted at /path/a and /path/b,
    68  	// GetMountRefs("/path/a") would return ["/path/b"]
    69  	// GetMountRefs("/path/b") would return ["/path/a"]
    70  	// On Windows there is no way to query all mount points; as long as pathname is
    71  	// a valid mount, it will be returned.
    72  	GetMountRefs(pathname string) ([]string, error)
    73  }
    74  
    75  // Compile-time check to ensure all Mounter implementations satisfy
    76  // the mount interface.
    77  var _ Interface = &Mounter{}
    78  
    79  // MountPoint represents a single line in /proc/mounts or /etc/fstab.
    80  type MountPoint struct { // nolint: golint
    81  	Device string
    82  	Path   string
    83  	Type   string
    84  	Opts   []string // Opts may contain sensitive mount options (like passwords) and MUST be treated as such (e.g. not logged).
    85  	Freq   int
    86  	Pass   int
    87  }
    88  
    89  type MountErrorType string // nolint: golint
    90  
    91  const (
    92  	FilesystemMismatch  MountErrorType = "FilesystemMismatch"
    93  	HasFilesystemErrors MountErrorType = "HasFilesystemErrors"
    94  	UnformattedReadOnly MountErrorType = "UnformattedReadOnly"
    95  	FormatFailed        MountErrorType = "FormatFailed"
    96  	GetDiskFormatFailed MountErrorType = "GetDiskFormatFailed"
    97  	UnknownMountError   MountErrorType = "UnknownMountError"
    98  )
    99  
   100  type MountError struct { // nolint: golint
   101  	Type    MountErrorType
   102  	Message string
   103  }
   104  
   105  func (mountError MountError) String() string {
   106  	return mountError.Message
   107  }
   108  
   109  func (mountError MountError) Error() string {
   110  	return mountError.Message
   111  }
   112  
   113  func NewMountError(mountErrorValue MountErrorType, format string, args ...interface{}) error {
   114  	mountError := MountError{
   115  		Type:    mountErrorValue,
   116  		Message: fmt.Sprintf(format, args...),
   117  	}
   118  	return mountError
   119  }
   120  
   121  // SafeFormatAndMount probes a device to see if it is formatted.
   122  // Namely it checks to see if a file system is present. If so it
   123  // mounts it otherwise the device is formatted first then mounted.
   124  type SafeFormatAndMount struct {
   125  	Interface
   126  	Exec utilexec.Interface
   127  }
   128  
   129  // FormatAndMount formats the given disk, if needed, and mounts it.
   130  // That is if the disk is not formatted and it is not being mounted as
   131  // read-only it will format it first then mount it. Otherwise, if the
   132  // disk is already formatted or it is being mounted as read-only, it
   133  // will be mounted without formatting.
   134  // options MUST not contain sensitive material (like passwords).
   135  func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, fstype string, options []string) error {
   136  	return mounter.FormatAndMountSensitive(source, target, fstype, options, nil /* sensitiveOptions */)
   137  }
   138  
   139  // FormatAndMountSensitive is the same as FormatAndMount but this method allows
   140  // sensitiveOptions to be passed in a separate parameter from the normal mount
   141  // options and ensures the sensitiveOptions are never logged. This method should
   142  // be used by callers that pass sensitive material (like passwords) as mount
   143  // options.
   144  func (mounter *SafeFormatAndMount) FormatAndMountSensitive(source string, target string, fstype string, options []string, sensitiveOptions []string) error {
   145  	return mounter.formatAndMountSensitive(source, target, fstype, options, sensitiveOptions)
   146  }
   147  
   148  // getMountRefsByDev finds all references to the device provided
   149  // by mountPath; returns a list of paths.
   150  // Note that mountPath should be path after the evaluation of any symblolic links.
   151  func getMountRefsByDev(mounter Interface, mountPath string) ([]string, error) {
   152  	mps, err := mounter.List()
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	// Finding the device mounted to mountPath.
   158  	diskDev := ""
   159  	for i := range mps {
   160  		if mountPath == mps[i].Path {
   161  			diskDev = mps[i].Device
   162  			break
   163  		}
   164  	}
   165  
   166  	// Find all references to the device.
   167  	var refs []string
   168  	for i := range mps {
   169  		if mps[i].Device == diskDev || mps[i].Device == mountPath {
   170  			if mps[i].Path != mountPath {
   171  				refs = append(refs, mps[i].Path)
   172  			}
   173  		}
   174  	}
   175  	return refs, nil
   176  }
   177  
   178  // GetDeviceNameFromMount given a mnt point, find the device from /proc/mounts
   179  // returns the device name, reference count, and error code.
   180  func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) {
   181  	mps, err := mounter.List()
   182  	if err != nil {
   183  		return "", 0, err
   184  	}
   185  
   186  	// Find the device name.
   187  	// FIXME if multiple devices mounted on the same mount path, only the first one is returned.
   188  	device := ""
   189  	// If mountPath is symlink, need get its target path.
   190  	slTarget, err := filepath.EvalSymlinks(mountPath)
   191  	if err != nil {
   192  		slTarget = mountPath
   193  	}
   194  	for i := range mps {
   195  		if mps[i].Path == slTarget {
   196  			device = mps[i].Device
   197  			break
   198  		}
   199  	}
   200  
   201  	// Find all references to the device.
   202  	refCount := 0
   203  	for i := range mps {
   204  		if mps[i].Device == device {
   205  			refCount++
   206  		}
   207  	}
   208  	return device, refCount, nil
   209  }
   210  
   211  // IsNotMountPoint determines if a directory is a mountpoint.
   212  // It should return ErrNotExist when the directory does not exist.
   213  // IsNotMountPoint is more expensive than IsLikelyNotMountPoint.
   214  // IsNotMountPoint detects bind mounts in linux.
   215  // IsNotMountPoint enumerates all the mountpoints using List() and
   216  // the list of mountpoints may be large, then it uses
   217  // isMountPointMatch to evaluate whether the directory is a mountpoint.
   218  func IsNotMountPoint(mounter Interface, file string) (bool, error) {
   219  	// IsLikelyNotMountPoint provides a quick check
   220  	// to determine whether file IS A mountpoint.
   221  	notMnt, notMntErr := mounter.IsLikelyNotMountPoint(file)
   222  	if notMntErr != nil && os.IsPermission(notMntErr) {
   223  		// We were not allowed to do the simple stat() check, e.g. on NFS with
   224  		// root_squash. Fall back to /proc/mounts check below.
   225  		notMnt = true
   226  		notMntErr = nil
   227  	}
   228  	if notMntErr != nil {
   229  		return notMnt, notMntErr
   230  	}
   231  	// identified as mountpoint, so return this fact.
   232  	if notMnt == false {
   233  		return notMnt, nil
   234  	}
   235  
   236  	// Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts.
   237  	resolvedFile, err := filepath.EvalSymlinks(file)
   238  	if err != nil {
   239  		return true, err
   240  	}
   241  
   242  	// check all mountpoints since IsLikelyNotMountPoint
   243  	// is not reliable for some mountpoint types.
   244  	mountPoints, mountPointsErr := mounter.List()
   245  	if mountPointsErr != nil {
   246  		return notMnt, mountPointsErr
   247  	}
   248  	for _, mp := range mountPoints {
   249  		if isMountPointMatch(mp, resolvedFile) {
   250  			notMnt = false
   251  			break
   252  		}
   253  	}
   254  	return notMnt, nil
   255  }
   256  
   257  // MakeBindOpts detects whether a bind mount is being requested and makes the remount options to
   258  // use in case of bind mount, due to the fact that bind mount doesn't respect mount options.
   259  // The list equals:
   260  //
   261  //	options - 'bind' + 'remount' (no duplicate)
   262  func MakeBindOpts(options []string) (bool, []string, []string) {
   263  	bind, bindOpts, bindRemountOpts, _ := MakeBindOptsSensitive(options, nil /* sensitiveOptions */)
   264  	return bind, bindOpts, bindRemountOpts
   265  }
   266  
   267  // MakeBindOptsSensitive is the same as MakeBindOpts but this method allows
   268  // sensitiveOptions to be passed in a separate parameter from the normal mount
   269  // options and ensures the sensitiveOptions are never logged. This method should
   270  // be used by callers that pass sensitive material (like passwords) as mount
   271  // options.
   272  func MakeBindOptsSensitive(options []string, sensitiveOptions []string) (bool, []string, []string, []string) {
   273  	// Because we have an FD opened on the subpath bind mount, the "bind" option
   274  	// needs to be included, otherwise the mount target will error as busy if you
   275  	// remount as readonly.
   276  	//
   277  	// As a consequence, all read only bind mounts will no longer change the underlying
   278  	// volume mount to be read only.
   279  	bindRemountOpts := []string{"bind", "remount"}
   280  	bindRemountSensitiveOpts := []string{}
   281  	bind := false
   282  	bindOpts := []string{"bind"}
   283  
   284  	// _netdev is a userspace mount option and does not automatically get added when
   285  	// bind mount is created and hence we must carry it over.
   286  	if checkForNetDev(options, sensitiveOptions) {
   287  		bindOpts = append(bindOpts, "_netdev")
   288  	}
   289  
   290  	for _, option := range options {
   291  		switch option {
   292  		case "bind":
   293  			bind = true
   294  		case "remount": // Do nothing.
   295  		default:
   296  			bindRemountOpts = append(bindRemountOpts, option)
   297  		}
   298  	}
   299  
   300  	for _, sensitiveOption := range sensitiveOptions {
   301  		switch sensitiveOption {
   302  		case "bind":
   303  			bind = true
   304  		case "remount": // Do nothing.
   305  		default:
   306  			bindRemountSensitiveOpts = append(bindRemountSensitiveOpts, sensitiveOption)
   307  		}
   308  	}
   309  
   310  	return bind, bindOpts, bindRemountOpts, bindRemountSensitiveOpts
   311  }
   312  
   313  func checkForNetDev(options []string, sensitiveOptions []string) bool {
   314  	for _, option := range options {
   315  		if option == "_netdev" {
   316  			return true
   317  		}
   318  	}
   319  	for _, sensitiveOption := range sensitiveOptions {
   320  		if sensitiveOption == "_netdev" {
   321  			return true
   322  		}
   323  	}
   324  	return false
   325  }
   326  
   327  // PathWithinBase checks if give path is within given base directory.
   328  func PathWithinBase(fullPath, basePath string) bool {
   329  	rel, err := filepath.Rel(basePath, fullPath)
   330  	if err != nil {
   331  		return false
   332  	}
   333  	if StartsWithBackstep(rel) {
   334  		// Needed to escape the base path.
   335  		return false
   336  	}
   337  	return true
   338  }
   339  
   340  // StartsWithBackstep checks if the given path starts with a backstep segment.
   341  func StartsWithBackstep(rel string) bool {
   342  	// normalize to / and check for ../
   343  	return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../")
   344  }
   345  
   346  // sanitizedOptionsForLogging will return a comma separated string containing
   347  // options and sensitiveOptions. Each entry in sensitiveOptions will be
   348  // replaced with the string sensitiveOptionsRemoved
   349  // e.g. o1,o2,<masked>,<masked>
   350  func sanitizedOptionsForLogging(options []string, sensitiveOptions []string) string {
   351  	separator := ""
   352  	if len(options) > 0 && len(sensitiveOptions) > 0 {
   353  		separator = ","
   354  	}
   355  
   356  	sensitiveOptionsStart := ""
   357  	sensitiveOptionsEnd := ""
   358  	if len(sensitiveOptions) > 0 {
   359  		sensitiveOptionsStart = strings.Repeat(sensitiveOptionsRemoved+",", len(sensitiveOptions)-1)
   360  		sensitiveOptionsEnd = sensitiveOptionsRemoved
   361  	}
   362  
   363  	return strings.Join(options, ",") +
   364  		separator +
   365  		sensitiveOptionsStart +
   366  		sensitiveOptionsEnd
   367  }
   368  

View as plain text