github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/core/fusermount/fusermount.go (about) 1 // Copyright 2018-2019 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 //go:build linux 6 // +build linux 7 8 // fusermount is a very limited replacement for the C fusermount. It 9 // is invoked by other programs, or interactively only to unmount. 10 // 11 // Synopsis: 12 // 13 // fusermount [-u|--unmount] [-z|--lazy] [-v|--verbose] <mountpoint> 14 // 15 // For mounting, per the FUSE model, the environment variable 16 // _FUSE_COMMFD must have the value of a file descriptor variable on 17 // which we pass the fuse fd. 18 // 19 // There is some checking we don't do, e.g. for the number of active 20 // mount points. Last time I checked, that's the kind of stuff 21 // kernels do. 22 // 23 // Description: 24 // 25 // invoke fuse mount operations 26 package main 27 28 import ( 29 "fmt" 30 "log" 31 "os" 32 "path/filepath" 33 "strconv" 34 "syscall" 35 36 flag "github.com/spf13/pflag" 37 38 "golang.org/x/sys/unix" 39 ) 40 41 const ( 42 // CommFD is the environment variable which contains the comms fd. 43 CommFD = "_FUSE_COMMFD" 44 fuseDev = "/dev/fuse" 45 ) 46 47 var ( 48 unmount = flag.BoolP("unmount", "u", false, "unmount") 49 lazy = flag.BoolP("lazy", "z", false, "lazy unmount") 50 verbose = flag.BoolP("verbose", "v", false, "verbose") 51 debug = func(string, ...interface{}) {} 52 mpt string 53 ) 54 55 const help = "usage: fusermount [-u|--unmount] [-z|--lazy] [-v|--verbose] <mountpoint>" 56 57 func usage() { 58 log.Fatalf(help) 59 } 60 61 func umount(n string) error { 62 // we're not doing all the folderol of standard 63 // fusermount for euid() == 0. 64 // Let's see how that works out. 65 flags := 0 66 if *lazy { 67 flags |= unix.MNT_DETACH 68 } 69 70 // TODO: anything we need here if unit.Getuid() == 0. 71 // So far there is nothing. 72 err := unix.Unmount(n, flags) 73 return err 74 } 75 76 func openFUSE() (int, error) { 77 return unix.Open("/dev/fuse", unix.O_RDWR, 0) 78 } 79 80 // MountPointOK performs validation on the mountpoint. 81 // Bury all your magic in here. 82 func MountPointOK(mpt string) error { 83 // We wait until we can drop privs to test the mpt 84 // parameter, since ability to walk the path can 85 // differ for root and the real user id. 86 if err := dropPrivs(); err != nil { 87 return err 88 } 89 defer restorePrivs() 90 mpt = filepath.Clean(mpt) 91 r, err := filepath.EvalSymlinks(mpt) 92 if err != nil { 93 return err 94 } 95 if r != mpt { 96 return fmt.Errorf("resolved path %q and mountpoint %q are not the same", r, mpt) 97 } 98 // I'm not sure why fusermount wants to open the mountpoint, so let's mot for now. 99 // And, for now, directories only? We don't see a current need to mount 100 // FUSE on any other type of file. 101 if err := os.Chdir(mpt); err != nil { 102 return err 103 } 104 105 return nil 106 } 107 108 func getCommFD() (int, error) { 109 commfd, ok := os.LookupEnv(CommFD) 110 if !ok { 111 return -1, fmt.Errorf(CommFD + "was not set and this program can't be used interactively") 112 } 113 debug("CommFD %v", commfd) 114 115 cfd, err := strconv.Atoi(commfd) 116 if err != nil { 117 return -1, fmt.Errorf("%s: %v", CommFD, err) 118 } 119 debug("CFD is %v", cfd) 120 var st unix.Stat_t 121 if err := unix.Fstat(cfd, &st); err != nil { 122 return -1, fmt.Errorf("_FUSE_COMMFD: %d: %v", cfd, err) 123 } 124 debug("cfd stat is %v", st) 125 126 return cfd, nil 127 } 128 129 func doMount(fd int) error { 130 flags := uintptr(unix.MS_NODEV | unix.MS_NOSUID) 131 // From the kernel: 132 // if (!d->fd_present || !d->rootmode_present || 133 // !d->user_id_present || !d->group_id_present) 134 // return 0; 135 // Yeah. You get EINVAL if any one of these is not set. 136 // Docs? what? Docs? 137 return unix.Mount("nodev", ".", "fuse", flags, fmt.Sprintf("rootmode=%o,user_id=0,group_id=0,fd=%d", unix.S_IFDIR, fd)) 138 } 139 140 // returnResult returns the result from earlier operations. 141 // It is called with the control fd, a FUSE fd, and an error. 142 // If the error is not nil, then we are shutting down the cfd; 143 // If it is nil then we try to send the fd back. 144 // We return either e or the error result and e 145 func returnResult(cfd, ffd int, e error) error { 146 if e != nil { 147 if err := unix.Shutdown(cfd, unix.SHUT_RDWR); err != nil { 148 return fmt.Errorf("shutting down after failed mount with %v: %v", e, err) 149 } 150 return e 151 } 152 oob := unix.UnixRights(int(ffd)) 153 if err := unix.Sendmsg(cfd, []byte(""), oob, nil, 0); err != nil { 154 return fmt.Errorf("%s: %d: %v", CommFD, cfd, err) 155 } 156 return nil 157 } 158 159 func main() { 160 flag.Parse() 161 162 if *verbose { 163 debug = log.Printf 164 } 165 166 if len(flag.Args()) != 1 { 167 usage() 168 } 169 mpt = flag.Arg(0) 170 debug("mpt %v", mpt) 171 172 // We let "ability to open /dev/fuse" stand in as an indicator or 173 // "we support FUSE". 174 FuseFD, err := openFUSE() 175 if err != nil { 176 log.Printf("%v", err) 177 os.Exit(int(syscall.ENOENT)) 178 } 179 debug("FuseFD %v", FuseFD) 180 181 // Bad design. All they had to do was make a -z and -u and have 182 // them both mean unmount. Oh well. 183 if *lazy && !*unmount { 184 log.Fatalf("-z can only be used with -u") 185 } 186 187 // Fuse has to be seen to be believed. 188 // The only interactive use of fusermount is to unmount 189 if *unmount { 190 if err := umount(mpt); err != nil { 191 log.Fatal(err) 192 } 193 return 194 } 195 196 if err := MountPointOK(mpt); err != nil { 197 log.Fatal(err) 198 } 199 200 if err := preMount(); err != nil { 201 log.Fatal(err) 202 } 203 204 cfd, err := getCommFD() 205 if err != nil { 206 log.Fatal(err) 207 } 208 209 if err := doMount(FuseFD); err != nil { 210 log.Fatal(err) 211 } 212 213 if err := returnResult(cfd, FuseFD, err); err != nil { 214 log.Fatal(err) 215 } 216 }