github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/cmd/mount/mount.go (about)

     1  // Package mount implents a FUSE mounting system for rclone remotes.
     2  
     3  // +build linux darwin freebsd
     4  
     5  package mount
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"os/signal"
    11  	"syscall"
    12  
    13  	"bazil.org/fuse"
    14  	fusefs "bazil.org/fuse/fs"
    15  	"github.com/ncw/rclone/cmd/mountlib"
    16  	"github.com/ncw/rclone/fs"
    17  	"github.com/ncw/rclone/lib/atexit"
    18  	"github.com/ncw/rclone/vfs"
    19  	"github.com/ncw/rclone/vfs/vfsflags"
    20  	"github.com/okzk/sdnotify"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  func init() {
    25  	mountlib.NewMountCommand("mount", Mount)
    26  }
    27  
    28  // mountOptions configures the options from the command line flags
    29  func mountOptions(device string) (options []fuse.MountOption) {
    30  	options = []fuse.MountOption{
    31  		fuse.MaxReadahead(uint32(mountlib.MaxReadAhead)),
    32  		fuse.Subtype("rclone"),
    33  		fuse.FSName(device),
    34  		fuse.VolumeName(mountlib.VolumeName),
    35  
    36  		// Options from benchmarking in the fuse module
    37  		//fuse.MaxReadahead(64 * 1024 * 1024),
    38  		//fuse.AsyncRead(), - FIXME this causes
    39  		// ReadFileHandle.Read error: read /home/files/ISOs/xubuntu-15.10-desktop-amd64.iso: bad file descriptor
    40  		// which is probably related to errors people are having
    41  		//fuse.WritebackCache(),
    42  	}
    43  	if mountlib.NoAppleDouble {
    44  		options = append(options, fuse.NoAppleDouble())
    45  	}
    46  	if mountlib.NoAppleXattr {
    47  		options = append(options, fuse.NoAppleXattr())
    48  	}
    49  	if mountlib.AllowNonEmpty {
    50  		options = append(options, fuse.AllowNonEmptyMount())
    51  	}
    52  	if mountlib.AllowOther {
    53  		options = append(options, fuse.AllowOther())
    54  	}
    55  	if mountlib.AllowRoot {
    56  		options = append(options, fuse.AllowRoot())
    57  	}
    58  	if mountlib.DefaultPermissions {
    59  		options = append(options, fuse.DefaultPermissions())
    60  	}
    61  	if vfsflags.Opt.ReadOnly {
    62  		options = append(options, fuse.ReadOnly())
    63  	}
    64  	if mountlib.WritebackCache {
    65  		options = append(options, fuse.WritebackCache())
    66  	}
    67  	if mountlib.DaemonTimeout != 0 {
    68  		options = append(options, fuse.DaemonTimeout(fmt.Sprint(int(mountlib.DaemonTimeout.Seconds()))))
    69  	}
    70  	if len(mountlib.ExtraOptions) > 0 {
    71  		fs.Errorf(nil, "-o/--option not supported with this FUSE backend")
    72  	}
    73  	if len(mountlib.ExtraOptions) > 0 {
    74  		fs.Errorf(nil, "--fuse-flag not supported with this FUSE backend")
    75  	}
    76  	return options
    77  }
    78  
    79  // mount the file system
    80  //
    81  // The mount point will be ready when this returns.
    82  //
    83  // returns an error, and an error channel for the serve process to
    84  // report an error when fusermount is called.
    85  func mount(f fs.Fs, mountpoint string) (*vfs.VFS, <-chan error, func() error, error) {
    86  	fs.Debugf(f, "Mounting on %q", mountpoint)
    87  	c, err := fuse.Mount(mountpoint, mountOptions(f.Name()+":"+f.Root())...)
    88  	if err != nil {
    89  		return nil, nil, nil, err
    90  	}
    91  
    92  	filesys := NewFS(f)
    93  	server := fusefs.New(c, nil)
    94  
    95  	// Serve the mount point in the background returning error to errChan
    96  	errChan := make(chan error, 1)
    97  	go func() {
    98  		err := server.Serve(filesys)
    99  		closeErr := c.Close()
   100  		if err == nil {
   101  			err = closeErr
   102  		}
   103  		errChan <- err
   104  	}()
   105  
   106  	// check if the mount process has an error to report
   107  	<-c.Ready
   108  	if err := c.MountError; err != nil {
   109  		return nil, nil, nil, err
   110  	}
   111  
   112  	unmount := func() error {
   113  		// Shutdown the VFS
   114  		filesys.VFS.Shutdown()
   115  		return fuse.Unmount(mountpoint)
   116  	}
   117  
   118  	return filesys.VFS, errChan, unmount, nil
   119  }
   120  
   121  // Mount mounts the remote at mountpoint.
   122  //
   123  // If noModTime is set then it
   124  func Mount(f fs.Fs, mountpoint string) error {
   125  	if mountlib.DebugFUSE {
   126  		fuse.Debug = func(msg interface{}) {
   127  			fs.Debugf("fuse", "%v", msg)
   128  		}
   129  	}
   130  
   131  	// Mount it
   132  	FS, errChan, unmount, err := mount(f, mountpoint)
   133  	if err != nil {
   134  		return errors.Wrap(err, "failed to mount FUSE fs")
   135  	}
   136  
   137  	sigInt := make(chan os.Signal, 1)
   138  	signal.Notify(sigInt, syscall.SIGINT, syscall.SIGTERM)
   139  	sigHup := make(chan os.Signal, 1)
   140  	signal.Notify(sigHup, syscall.SIGHUP)
   141  	atexit.IgnoreSignals()
   142  
   143  	if err := sdnotify.Ready(); err != nil && err != sdnotify.ErrSdNotifyNoSocket {
   144  		return errors.Wrap(err, "failed to notify systemd")
   145  	}
   146  
   147  waitloop:
   148  	for {
   149  		select {
   150  		// umount triggered outside the app
   151  		case err = <-errChan:
   152  			break waitloop
   153  		// Program abort: umount
   154  		case <-sigInt:
   155  			err = unmount()
   156  			break waitloop
   157  		// user sent SIGHUP to clear the cache
   158  		case <-sigHup:
   159  			root, err := FS.Root()
   160  			if err != nil {
   161  				fs.Errorf(f, "Error reading root: %v", err)
   162  			} else {
   163  				root.ForgetAll()
   164  			}
   165  		}
   166  	}
   167  
   168  	_ = sdnotify.Stopping()
   169  	if err != nil {
   170  		return errors.Wrap(err, "failed to umount FUSE fs")
   171  	}
   172  
   173  	return nil
   174  }