github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/mount/mount_linux.go (about)

     1  // Copyright 2012-2020 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package mount implements mounting, moving, and unmounting file systems.
     6  package mount
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  
    14  	"golang.org/x/sys/unix"
    15  )
    16  
    17  // Most commonly used mount flags.
    18  const (
    19  	MS_RDONLY   = unix.MS_RDONLY
    20  	MS_BIND     = unix.MS_BIND
    21  	MS_LAZYTIME = unix.MS_LAZYTIME
    22  	MS_NOEXEC   = unix.MS_NOEXEC
    23  	MS_NOSUID   = unix.MS_NOSUID
    24  	MS_NOUSER   = unix.MS_NOUSER
    25  	MS_RELATIME = unix.MS_RELATIME
    26  	MS_SYNC     = unix.MS_SYNC
    27  	MS_NOATIME  = unix.MS_NOATIME
    28  
    29  	ReadOnly = unix.MS_RDONLY | unix.MS_NOATIME
    30  )
    31  
    32  // Unmount flags.
    33  const (
    34  	MNT_FORCE  = unix.MNT_FORCE
    35  	MNT_DETACH = unix.MNT_DETACH
    36  )
    37  
    38  // Mounter is a device that can be attached at a file system path.
    39  type Mounter interface {
    40  	// DevName returns the name of the device.
    41  	DevName() string
    42  	// Mount attaches the device at path.
    43  	Mount(path string, flags uintptr, opts ...func() error) (*MountPoint, error)
    44  }
    45  
    46  // MountPoint represents a mounted file system.
    47  type MountPoint struct {
    48  	Path   string
    49  	Device string
    50  	FSType string
    51  	Flags  uintptr
    52  	Data   string
    53  }
    54  
    55  // String implements fmt.Stringer.
    56  func (mp *MountPoint) String() string {
    57  	return fmt.Sprintf("MountPoint(path=%s, device=%s, fs=%s, flags=%#x, data=%s)", mp.Path, mp.Device, mp.FSType, mp.Flags, mp.Data)
    58  }
    59  
    60  // Unmount unmounts a file system that was previously mounted.
    61  func (mp *MountPoint) Unmount(flags uintptr) error {
    62  	if err := unix.Unmount(mp.Path, int(flags)); err != nil {
    63  		return &os.PathError{
    64  			Op:   "unmount",
    65  			Path: mp.Path,
    66  			Err:  fmt.Errorf("flags %#x: %v", flags, err),
    67  		}
    68  	}
    69  	return nil
    70  }
    71  
    72  // Mount attaches the fsType file system at path.
    73  //
    74  // dev is the device to mount (this is often the path of a block device, name
    75  // of a file, or a placeholder string). data usually contains arguments for the
    76  // specific file system.
    77  //
    78  // opts is usually empty, but if you want, e.g., to pre-create the mountpoint,
    79  // you can call Mount with a mkdirall, e.g.
    80  // mount.Mount("none", dst, fstype, "", 0,
    81  //
    82  //	func() error { return os.MkdirAll(dst, 0o666)})
    83  func Mount(dev, path, fsType, data string, flags uintptr, opts ...func() error) (*MountPoint, error) {
    84  	for _, f := range opts {
    85  		if err := f(); err != nil {
    86  			return nil, err
    87  		}
    88  	}
    89  
    90  	if err := unix.Mount(dev, path, fsType, flags, data); err != nil {
    91  		return nil, &os.PathError{
    92  			Op:   "mount",
    93  			Path: path,
    94  			Err:  fmt.Errorf("from device %q (fs type %s, flags %#x): %v", dev, fsType, flags, err),
    95  		}
    96  	}
    97  	return &MountPoint{
    98  		Path:   path,
    99  		Device: dev,
   100  		FSType: fsType,
   101  		Data:   data,
   102  		Flags:  flags,
   103  	}, nil
   104  }
   105  
   106  // TryMount tries to mount a device on the given mountpoint, trying in order
   107  // the supported block device file systems on the system.
   108  func TryMount(device, path, data string, flags uintptr, opts ...func() error) (*MountPoint, error) {
   109  	fstype, extraflags, err := FSFromBlock(device)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	return Mount(device, path, fstype, data, flags|extraflags, opts...)
   115  }
   116  
   117  // Unmount detaches any file system mounted at path.
   118  //
   119  // force forces an unmount regardless of currently open or otherwise used files
   120  // within the file system to be unmounted.
   121  //
   122  // lazy disallows future uses of any files below path -- i.e. it hides the file
   123  // system mounted at path, but the file system itself is still active and any
   124  // currently open files can continue to be used. When all references to files
   125  // from this file system are gone, the file system will actually be unmounted.
   126  func Unmount(path string, force, lazy bool) error {
   127  	flags := unix.UMOUNT_NOFOLLOW
   128  	if len(path) == 0 {
   129  		return errors.New("path cannot be empty")
   130  	}
   131  	if force && lazy {
   132  		return errors.New("MNT_FORCE and MNT_DETACH (lazy unmount) cannot both be set")
   133  	}
   134  	if force {
   135  		flags |= unix.MNT_FORCE
   136  	}
   137  	if lazy {
   138  		flags |= unix.MNT_DETACH
   139  	}
   140  	if err := unix.Unmount(path, flags); err != nil {
   141  		return fmt.Errorf("umount %q flags %x: %v", path, flags, err)
   142  	}
   143  	return nil
   144  }
   145  
   146  // Pool keeps track of multiple MountPoint.
   147  type Pool struct {
   148  	// List of items mounted by this pool.
   149  	MountPoints []*MountPoint
   150  	// Temporary directory which contains sub-directories for mounts.
   151  	tmpDir string
   152  }
   153  
   154  // Mount mounts a file system using Mounter and returns the MountPoint. If the
   155  // device has already been mounted, it is not mounted again.
   156  //
   157  // Note the pool is keyed on Mounter.DevName() alone meaning DevName is used to
   158  // determine whether it has already been mounted.
   159  func (p *Pool) Mount(mounter Mounter, flags uintptr) (*MountPoint, error) {
   160  	for _, m := range p.MountPoints {
   161  		if m.Device == filepath.Join("/dev", mounter.DevName()) {
   162  			return m, nil
   163  		}
   164  	}
   165  
   166  	// Create temporary directory if one does not already exist.
   167  	if p.tmpDir == "" {
   168  		tmpDir, err := os.MkdirTemp("", "u-root-mounts")
   169  		if err != nil {
   170  			return nil, fmt.Errorf("cannot create tmpdir: %v", err)
   171  		}
   172  		p.tmpDir = tmpDir
   173  	}
   174  
   175  	path := filepath.Join(p.tmpDir, mounter.DevName())
   176  	os.MkdirAll(path, 0o777)
   177  	m, err := mounter.Mount(path, flags)
   178  	if err != nil {
   179  		// unix.Rmdir is used (instead of os.RemoveAll) because it
   180  		// fails when the directory is non-empty. It would be a bit
   181  		// dangerous to use os.RemoveAll because it could accidentally
   182  		// delete everything in a mount.
   183  		unix.Rmdir(path)
   184  		return nil, err
   185  	}
   186  	p.MountPoints = append(p.MountPoints, m)
   187  	return m, err
   188  }
   189  
   190  // Add adds MountPoints to the pool.
   191  func (p *Pool) Add(m ...*MountPoint) {
   192  	p.MountPoints = append(p.MountPoints, m...)
   193  }
   194  
   195  // UnmountAll umounts all the mountpoints from the pool. This makes a
   196  // best-effort attempt to unmount everything and cleanup temporary directories.
   197  // If this function fails, it can be re-tried.
   198  func (p *Pool) UnmountAll(flags uintptr) error {
   199  	// Errors get concatenated together here.
   200  	var returnErr error
   201  
   202  	for _, m := range p.MountPoints {
   203  		if err := m.Unmount(flags); err != nil {
   204  			if returnErr == nil {
   205  				returnErr = fmt.Errorf("(Unmount) %s", err.Error())
   206  			} else {
   207  				returnErr = fmt.Errorf("%w; (Unmount) %s", returnErr, err.Error())
   208  			}
   209  		}
   210  
   211  		// unix.Rmdir is used (instead of os.RemoveAll) because it
   212  		// fails when the directory is non-empty. It would be a bit
   213  		// dangerous to use os.RemoveAll because it could accidentally
   214  		// delete everything in a mount.
   215  		unix.Rmdir(m.Path)
   216  	}
   217  
   218  	if returnErr == nil && p.tmpDir != "" {
   219  		if err := unix.Rmdir(p.tmpDir); err != nil {
   220  			if returnErr == nil {
   221  				returnErr = fmt.Errorf("(Rmdir) %s", err.Error())
   222  			} else {
   223  				returnErr = fmt.Errorf("%w; (Rmdir) %s", returnErr, err.Error())
   224  			}
   225  		}
   226  		p.tmpDir = ""
   227  	}
   228  
   229  	return returnErr
   230  }