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  }