github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/mountlib/rc.go (about)

     1  package mountlib
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"log"
     7  	"sort"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/rclone/rclone/fs"
    12  	"github.com/rclone/rclone/fs/rc"
    13  	"github.com/rclone/rclone/vfs/vfsflags"
    14  )
    15  
    16  var (
    17  	// mutex to protect all the variables in this block
    18  	mountMu sync.Mutex
    19  	// Mount functions available
    20  	mountFns = map[string]MountFn{}
    21  	// Map of mounted path => MountInfo
    22  	liveMounts = map[string]*MountPoint{}
    23  	// Supported mount types
    24  	supportedMountTypes = []string{"mount", "cmount", "mount2"}
    25  )
    26  
    27  // ResolveMountMethod returns mount function by name
    28  func ResolveMountMethod(mountType string) (string, MountFn) {
    29  	if mountType != "" {
    30  		return mountType, mountFns[mountType]
    31  	}
    32  	for _, mountType := range supportedMountTypes {
    33  		if mountFns[mountType] != nil {
    34  			return mountType, mountFns[mountType]
    35  		}
    36  	}
    37  	return "", nil
    38  }
    39  
    40  // AddRc adds mount and unmount functionality to rc
    41  func AddRc(mountUtilName string, mountFunction MountFn) {
    42  	mountMu.Lock()
    43  	defer mountMu.Unlock()
    44  	// rcMount allows the mount command to be run from rc
    45  	mountFns[mountUtilName] = mountFunction
    46  }
    47  
    48  func init() {
    49  	rc.Add(rc.Call{
    50  		Path:         "mount/mount",
    51  		AuthRequired: true,
    52  		Fn:           mountRc,
    53  		Title:        "Create a new mount point",
    54  		Help: `rclone allows Linux, FreeBSD, macOS and Windows to mount any of
    55  Rclone's cloud storage systems as a file system with FUSE.
    56  
    57  If no mountType is provided, the priority is given as follows: 1. mount 2.cmount 3.mount2
    58  
    59  This takes the following parameters:
    60  
    61  - fs - a remote path to be mounted (required)
    62  - mountPoint: valid path on the local machine (required)
    63  - mountType: one of the values (mount, cmount, mount2) specifies the mount implementation to use
    64  - mountOpt: a JSON object with Mount options in.
    65  - vfsOpt: a JSON object with VFS options in.
    66  
    67  Example:
    68  
    69      rclone rc mount/mount fs=mydrive: mountPoint=/home/<user>/mountPoint
    70      rclone rc mount/mount fs=mydrive: mountPoint=/home/<user>/mountPoint mountType=mount
    71      rclone rc mount/mount fs=TestDrive: mountPoint=/mnt/tmp vfsOpt='{"CacheMode": 2}' mountOpt='{"AllowOther": true}'
    72  
    73  The vfsOpt are as described in options/get and can be seen in the the
    74  "vfs" section when running and the mountOpt can be seen in the "mount" section:
    75  
    76      rclone rc options/get
    77  `,
    78  	})
    79  }
    80  
    81  // mountRc allows the mount command to be run from rc
    82  func mountRc(ctx context.Context, in rc.Params) (out rc.Params, err error) {
    83  	mountPoint, err := in.GetString("mountPoint")
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	vfsOpt := vfsflags.Opt
    89  	err = in.GetStructMissingOK("vfsOpt", &vfsOpt)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	mountOpt := Opt
    95  	err = in.GetStructMissingOK("mountOpt", &mountOpt)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	if mountOpt.Daemon {
   101  		return nil, errors.New("daemon option not supported over the API")
   102  	}
   103  
   104  	mountType, err := in.GetString("mountType")
   105  
   106  	mountMu.Lock()
   107  	defer mountMu.Unlock()
   108  
   109  	if err != nil {
   110  		mountType = ""
   111  	}
   112  	mountType, mountFn := ResolveMountMethod(mountType)
   113  	if mountFn == nil {
   114  		return nil, errors.New("mount option specified is not registered, or is invalid")
   115  	}
   116  
   117  	// Get Fs.fs to be mounted from fs parameter in the params
   118  	fdst, err := rc.GetFs(ctx, in)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	mnt := NewMountPoint(mountFn, mountPoint, fdst, &mountOpt, &vfsOpt)
   124  	_, err = mnt.Mount()
   125  	if err != nil {
   126  		log.Printf("mount FAILED: %v", err)
   127  		return nil, err
   128  	}
   129  	go func() {
   130  		if err = mnt.Wait(); err != nil {
   131  			log.Printf("unmount FAILED: %v", err)
   132  			return
   133  		}
   134  		mountMu.Lock()
   135  		defer mountMu.Unlock()
   136  		delete(liveMounts, mountPoint)
   137  	}()
   138  	// Add mount to list if mount point was successfully created
   139  	liveMounts[mountPoint] = mnt
   140  
   141  	fs.Debugf(nil, "Mount for %s created at %s using %s", fdst.String(), mountPoint, mountType)
   142  	return nil, nil
   143  }
   144  
   145  func init() {
   146  	rc.Add(rc.Call{
   147  		Path:         "mount/unmount",
   148  		AuthRequired: true,
   149  		Fn:           unMountRc,
   150  		Title:        "Unmount selected active mount",
   151  		Help: `
   152  rclone allows Linux, FreeBSD, macOS and Windows to
   153  mount any of Rclone's cloud storage systems as a file system with
   154  FUSE.
   155  
   156  This takes the following parameters:
   157  
   158  - mountPoint: valid path on the local machine where the mount was created (required)
   159  
   160  Example:
   161  
   162      rclone rc mount/unmount mountPoint=/home/<user>/mountPoint
   163  `,
   164  	})
   165  }
   166  
   167  // unMountRc allows the umount command to be run from rc
   168  func unMountRc(_ context.Context, in rc.Params) (out rc.Params, err error) {
   169  	mountPoint, err := in.GetString("mountPoint")
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	mountMu.Lock()
   174  	defer mountMu.Unlock()
   175  	mountInfo, found := liveMounts[mountPoint]
   176  	if !found {
   177  		return nil, errors.New("mount not found")
   178  	}
   179  	if err = mountInfo.Unmount(); err != nil {
   180  		return nil, err
   181  	}
   182  	delete(liveMounts, mountPoint)
   183  	return nil, nil
   184  }
   185  
   186  func init() {
   187  	rc.Add(rc.Call{
   188  		Path:         "mount/types",
   189  		AuthRequired: true,
   190  		Fn:           mountTypesRc,
   191  		Title:        "Show all possible mount types",
   192  		Help: `This shows all possible mount types and returns them as a list.
   193  
   194  This takes no parameters and returns
   195  
   196  - mountTypes: list of mount types
   197  
   198  The mount types are strings like "mount", "mount2", "cmount" and can
   199  be passed to mount/mount as the mountType parameter.
   200  
   201  Eg
   202  
   203      rclone rc mount/types
   204  `,
   205  	})
   206  }
   207  
   208  // mountTypesRc returns a list of available mount types.
   209  func mountTypesRc(_ context.Context, in rc.Params) (out rc.Params, err error) {
   210  	var mountTypes = []string{}
   211  	mountMu.Lock()
   212  	defer mountMu.Unlock()
   213  	for mountType := range mountFns {
   214  		mountTypes = append(mountTypes, mountType)
   215  	}
   216  	sort.Strings(mountTypes)
   217  	return rc.Params{
   218  		"mountTypes": mountTypes,
   219  	}, nil
   220  }
   221  
   222  func init() {
   223  	rc.Add(rc.Call{
   224  		Path:         "mount/listmounts",
   225  		AuthRequired: true,
   226  		Fn:           listMountsRc,
   227  		Title:        "Show current mount points",
   228  		Help: `This shows currently mounted points, which can be used for performing an unmount.
   229  
   230  This takes no parameters and returns
   231  
   232  - mountPoints: list of current mount points
   233  
   234  Eg
   235  
   236      rclone rc mount/listmounts
   237  `,
   238  	})
   239  }
   240  
   241  // MountInfo is a transitional structure for json marshaling
   242  type MountInfo struct {
   243  	Fs         string    `json:"Fs"`
   244  	MountPoint string    `json:"MountPoint"`
   245  	MountedOn  time.Time `json:"MountedOn"`
   246  }
   247  
   248  // listMountsRc returns a list of current mounts sorted by mount path
   249  func listMountsRc(_ context.Context, in rc.Params) (out rc.Params, err error) {
   250  	mountMu.Lock()
   251  	defer mountMu.Unlock()
   252  	var keys []string
   253  	for key := range liveMounts {
   254  		keys = append(keys, key)
   255  	}
   256  	sort.Strings(keys)
   257  	mountPoints := []MountInfo{}
   258  	for _, k := range keys {
   259  		m := liveMounts[k]
   260  		info := MountInfo{
   261  			Fs:         fs.ConfigString(m.Fs),
   262  			MountPoint: m.MountPoint,
   263  			MountedOn:  m.MountedOn,
   264  		}
   265  		mountPoints = append(mountPoints, info)
   266  	}
   267  	return rc.Params{
   268  		"mountPoints": mountPoints,
   269  	}, nil
   270  }
   271  
   272  func init() {
   273  	rc.Add(rc.Call{
   274  		Path:         "mount/unmountall",
   275  		AuthRequired: true,
   276  		Fn:           unmountAll,
   277  		Title:        "Unmount all active mounts",
   278  		Help: `
   279  rclone allows Linux, FreeBSD, macOS and Windows to
   280  mount any of Rclone's cloud storage systems as a file system with
   281  FUSE.
   282  
   283  This takes no parameters and returns error if unmount does not succeed.
   284  
   285  Eg
   286  
   287      rclone rc mount/unmountall
   288  `,
   289  	})
   290  }
   291  
   292  // unmountAll unmounts all the created mounts
   293  func unmountAll(_ context.Context, in rc.Params) (out rc.Params, err error) {
   294  	mountMu.Lock()
   295  	defer mountMu.Unlock()
   296  	for mountPoint, mountInfo := range liveMounts {
   297  		if err = mountInfo.Unmount(); err != nil {
   298  			fs.Debugf(nil, "Couldn't unmount : %s", mountPoint)
   299  			return nil, err
   300  		}
   301  		delete(liveMounts, mountPoint)
   302  	}
   303  	return nil, nil
   304  }