github.com/canonical/ubuntu-image@v0.0.0-20240430122802-2202fe98b290/internal/statemachine/mount_helper.go (about) 1 package statemachine 2 3 import ( 4 "fmt" 5 "os" 6 "os/exec" 7 "path/filepath" 8 "strings" 9 ) 10 11 type mountPoint struct { 12 src string 13 path string 14 basePath string // basePath + relpath = path 15 relpath string 16 typ string 17 opts []string 18 bind bool 19 } 20 21 // getMountCmd returns mount/umount commands to mount the given mountpoint 22 // If the mountpoint does not exist, it will be created. 23 func (m *mountPoint) getMountCmd() (mountCmds, umountCmds []*exec.Cmd, err error) { 24 if m.bind && len(m.typ) > 0 { 25 return nil, nil, fmt.Errorf("invalid mount arguments. Cannot use --bind and -t at the same time.") 26 } 27 28 targetPath := filepath.Join(m.basePath, m.relpath) 29 mountCmd := execCommand("mount") 30 31 if len(m.typ) > 0 { 32 mountCmd.Args = append(mountCmd.Args, "-t", m.typ) 33 } 34 35 if m.bind { 36 mountCmd.Args = append(mountCmd.Args, "--bind") 37 } 38 39 mountCmd.Args = append(mountCmd.Args, m.src) 40 if len(m.opts) > 0 { 41 mountCmd.Args = append(mountCmd.Args, "-o", strings.Join(m.opts, ",")) 42 } 43 mountCmd.Args = append(mountCmd.Args, targetPath) 44 45 if _, err := os.Stat(targetPath); err != nil { 46 err := osMkdirAll(targetPath, 0755) 47 if err != nil && !os.IsExist(err) { 48 return nil, nil, fmt.Errorf("Error creating mountpoint \"%s\": \"%s\"", targetPath, err.Error()) 49 } 50 } 51 52 umountCmds = getUnmountCmd(targetPath) 53 54 return []*exec.Cmd{mountCmd}, umountCmds, nil 55 } 56 57 // getUnmountCmd generates unmount commands from a path 58 func getUnmountCmd(targetPath string) []*exec.Cmd { 59 return []*exec.Cmd{ 60 execCommand("mount", "--make-rprivate", targetPath), 61 execCommand("umount", "--recursive", targetPath), 62 } 63 } 64 65 // teardownMount executed teardown commands after making sure every mountpoints matching the given path 66 // are listed and will be properly unmounted 67 func teardownMount(path string, mountPoints []*mountPoint, teardownCmds []*exec.Cmd, err error, debug bool) error { 68 addedUmountCmds, errAddedUmount := umountAddedMountPointsCmds(path, mountPoints) 69 if errAddedUmount != nil { 70 err = fmt.Errorf("%s\n%s", err, errAddedUmount) 71 } 72 teardownCmds = append(addedUmountCmds, teardownCmds...) 73 74 return execTeardownCmds(teardownCmds, debug, err) 75 } 76 77 // umountAddedMountPointsCmds generates umount commands for newly added mountpoints 78 func umountAddedMountPointsCmds(path string, mountPoints []*mountPoint) (umountCmds []*exec.Cmd, err error) { 79 currentMountPoints, err := listMounts(path) 80 if err != nil { 81 return nil, err 82 } 83 newMountPoints := diffMountPoints(mountPoints, currentMountPoints) 84 if len(newMountPoints) > 0 { 85 for _, m := range newMountPoints { 86 umountCmds = append(umountCmds, getUnmountCmd(m.path)...) 87 } 88 } 89 90 return umountCmds, nil 91 } 92 93 // diffMountPoints compares 2 lists of mountpoint and returns the added ones 94 func diffMountPoints(olds []*mountPoint, currents []*mountPoint) (added []*mountPoint) { 95 for _, m := range currents { 96 found := false 97 for _, o := range olds { 98 if equalMountPoints(*m, *o) { 99 found = true 100 } 101 } 102 if !found { 103 added = append(added, m) 104 } 105 } 106 107 return added 108 } 109 110 // equalMountPoints compare 2 mountpoints. Since mountPoints go object can be either 111 // created by hand or parsed from /proc/self/mount, we need to compare strictly on the fields 112 // useful to identify a unique mountpoint from the point of view of the OS. 113 func equalMountPoints(a, b mountPoint) bool { 114 if len(a.path) == 0 { 115 a.path = filepath.Join(a.basePath, a.relpath) 116 } 117 if len(b.path) == 0 { 118 b.path = filepath.Join(b.basePath, b.relpath) 119 } 120 121 return a.src == b.src && a.path == b.path && a.typ == b.typ 122 } 123 124 // listMounts returns mountpoints matching the given path from /proc/self/mounts 125 func listMounts(path string) ([]*mountPoint, error) { 126 procMounts := "/proc/self/mounts" 127 f, err := osReadFile(procMounts) 128 if err != nil { 129 return nil, err 130 } 131 132 return parseMounts(string(f), path) 133 } 134 135 // parseMounts list existing mounts and submounts in the current path 136 // The returned splice is already inverted so unmount can be called on it 137 // without further modification. 138 func parseMounts(procMount string, path string) ([]*mountPoint, error) { 139 mountPoints := []*mountPoint{} 140 mountLines := strings.Split(procMount, "\n") 141 142 for _, line := range mountLines { 143 if line == "" { 144 continue 145 } 146 147 fields := strings.Fields(line) 148 mountPath := fields[1] 149 150 if len(path) != 0 && !strings.HasPrefix(mountPath, path) { 151 continue 152 } 153 154 m := &mountPoint{ 155 src: fields[0], 156 path: mountPath, 157 typ: fields[2], 158 opts: strings.Split(fields[3], ","), 159 } 160 mountPoints = append([]*mountPoint{m}, mountPoints...) 161 } 162 163 return mountPoints, nil 164 }