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 }