e43b5ce5e5
This is possible now that we no longer support go1.12 and brings rclone into line with standard practices in the Go world. This also removes errors.New and errors.Errorf from lib/errors and prefers the stdlib errors package over lib/errors.
314 lines
8.7 KiB
Go
314 lines
8.7 KiB
Go
package docker
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/config/configmap"
|
|
"github.com/rclone/rclone/fs/fspath"
|
|
"github.com/rclone/rclone/fs/rc"
|
|
"github.com/rclone/rclone/vfs/vfscommon"
|
|
"github.com/rclone/rclone/vfs/vfsflags"
|
|
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
// applyOptions configures volume from request options.
|
|
//
|
|
// There are 5 special options:
|
|
// - "remote" aka "fs" determines existing remote from config file
|
|
// with a path or on-the-fly remote using the ":backend:" syntax.
|
|
// It is usually named "remote" in documentation but can be aliased as
|
|
// "fs" to avoid confusion with the "remote" option of some backends.
|
|
// - "type" is equivalent to the ":backend:" syntax (optional).
|
|
// - "path" provides explicit on-remote path for "type" (optional).
|
|
// - "mount-type" can be "mount", "cmount" or "mount2", defaults to
|
|
// first found (optional).
|
|
// - "persist" is reserved for future to create remotes persisted
|
|
// in rclone.conf similar to rcd (optional).
|
|
//
|
|
// Unlike rcd we use the flat naming scheme for mount, vfs and backend
|
|
// options without substructures. Dashes, underscores and mixed case
|
|
// in option names can be used interchangeably. Option name conflicts
|
|
// can be resolved in a manner similar to rclone CLI by adding prefixes:
|
|
// "vfs-", primary mount backend type like "sftp-", and so on.
|
|
//
|
|
// After triaging the options are put in MountOpt, VFSOpt or connect
|
|
// string for actual filesystem setup and in volume.Options for saving
|
|
// the state.
|
|
func (vol *Volume) applyOptions(volOpt VolOpts) error {
|
|
// copy options to override later
|
|
mntOpt := &vol.mnt.MountOpt
|
|
vfsOpt := &vol.mnt.VFSOpt
|
|
*mntOpt = vol.drv.mntOpt
|
|
*vfsOpt = vol.drv.vfsOpt
|
|
|
|
// vol.Options has all options except "remote" and "type"
|
|
vol.Options = VolOpts{}
|
|
vol.fsString = ""
|
|
|
|
var fsName, fsPath, fsType string
|
|
var explicitPath string
|
|
var fsOpt configmap.Simple
|
|
|
|
// parse "remote" or "type"
|
|
for key, str := range volOpt {
|
|
switch key {
|
|
case "":
|
|
continue
|
|
case "remote", "fs":
|
|
p, err := fspath.Parse(str)
|
|
if err != nil || p.Name == ":" {
|
|
return fmt.Errorf("cannot parse path %q: %w", str, err)
|
|
}
|
|
fsName, fsPath, fsOpt = p.Name, p.Path, p.Config
|
|
vol.Fs = str
|
|
case "type":
|
|
fsType = str
|
|
vol.Type = str
|
|
case "path":
|
|
explicitPath = str
|
|
vol.Path = str
|
|
default:
|
|
vol.Options[key] = str
|
|
}
|
|
}
|
|
|
|
// find options supported by backend
|
|
if strings.HasPrefix(fsName, ":") {
|
|
fsType = fsName[1:]
|
|
fsName = ""
|
|
}
|
|
if fsType == "" {
|
|
fsType = "local"
|
|
if fsName != "" {
|
|
var ok bool
|
|
fsType, ok = fs.ConfigMap(nil, fsName, nil).Get("type")
|
|
if !ok {
|
|
return fs.ErrorNotFoundInConfigFile
|
|
}
|
|
}
|
|
}
|
|
if explicitPath != "" {
|
|
if fsPath != "" {
|
|
fs.Logf(nil, "Explicit path will override connection string")
|
|
}
|
|
fsPath = explicitPath
|
|
}
|
|
fsInfo, err := fs.Find(fsType)
|
|
if err != nil {
|
|
return fmt.Errorf("unknown filesystem type %q", fsType)
|
|
}
|
|
|
|
// handle remaining options, override fsOpt
|
|
if fsOpt == nil {
|
|
fsOpt = configmap.Simple{}
|
|
}
|
|
opt := rc.Params{}
|
|
for key, val := range vol.Options {
|
|
opt[key] = val
|
|
}
|
|
for key := range opt {
|
|
var ok bool
|
|
var err error
|
|
|
|
switch normalOptName(key) {
|
|
case "persist":
|
|
vol.persist, err = opt.GetBool(key)
|
|
ok = true
|
|
case "mount-type":
|
|
vol.mountType, err = opt.GetString(key)
|
|
ok = true
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("cannot parse option %q: %w", key, err)
|
|
}
|
|
|
|
if !ok {
|
|
// try to use as a mount option in mntOpt
|
|
ok, err = getMountOption(mntOpt, opt, key)
|
|
if ok && err != nil {
|
|
return fmt.Errorf("cannot parse mount option %q: %w", key, err)
|
|
}
|
|
}
|
|
if !ok {
|
|
// try as a vfs option in vfsOpt
|
|
ok, err = getVFSOption(vfsOpt, opt, key)
|
|
if ok && err != nil {
|
|
return fmt.Errorf("cannot parse vfs option %q: %w", key, err)
|
|
}
|
|
}
|
|
|
|
if !ok {
|
|
// try as a backend option in fsOpt (backends use "_" instead of "-")
|
|
optWithPrefix := strings.ReplaceAll(normalOptName(key), "-", "_")
|
|
fsOptName := strings.TrimPrefix(optWithPrefix, fsType+"_")
|
|
hasFsPrefix := optWithPrefix != fsOptName
|
|
if !hasFsPrefix || fsInfo.Options.Get(fsOptName) == nil {
|
|
fs.Logf(nil, "Option %q is not supported by backend %q", key, fsType)
|
|
return fmt.Errorf("unsupported backend option %q", key)
|
|
}
|
|
fsOpt[fsOptName], err = opt.GetString(key)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot parse backend option %q: %w", key, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// build remote string from fsName, fsType, fsOpt, fsPath
|
|
colon := ":"
|
|
comma := ","
|
|
if fsName == "" {
|
|
fsName = ":" + fsType
|
|
}
|
|
connString := fsOpt.String()
|
|
if fsName == "" && fsType == "" {
|
|
colon = ""
|
|
connString = ""
|
|
}
|
|
if connString == "" {
|
|
comma = ""
|
|
}
|
|
vol.fsString = fsName + comma + connString + colon + fsPath
|
|
|
|
return vol.validate()
|
|
}
|
|
|
|
func getMountOption(mntOpt *mountlib.Options, opt rc.Params, key string) (ok bool, err error) {
|
|
ok = true
|
|
switch normalOptName(key) {
|
|
case "debug-fuse":
|
|
mntOpt.DebugFUSE, err = opt.GetBool(key)
|
|
case "attr-timeout":
|
|
mntOpt.AttrTimeout, err = opt.GetDuration(key)
|
|
case "option":
|
|
mntOpt.ExtraOptions, err = getStringArray(opt, key)
|
|
case "fuse-flag":
|
|
mntOpt.ExtraFlags, err = getStringArray(opt, key)
|
|
case "daemon":
|
|
mntOpt.Daemon, err = opt.GetBool(key)
|
|
case "daemon-timeout":
|
|
mntOpt.DaemonTimeout, err = opt.GetDuration(key)
|
|
case "default-permissions":
|
|
mntOpt.DefaultPermissions, err = opt.GetBool(key)
|
|
case "allow-non-empty":
|
|
mntOpt.AllowNonEmpty, err = opt.GetBool(key)
|
|
case "allow-root":
|
|
mntOpt.AllowRoot, err = opt.GetBool(key)
|
|
case "allow-other":
|
|
mntOpt.AllowOther, err = opt.GetBool(key)
|
|
case "async-read":
|
|
mntOpt.AsyncRead, err = opt.GetBool(key)
|
|
case "max-read-ahead":
|
|
err = getFVarP(&mntOpt.MaxReadAhead, opt, key)
|
|
case "write-back-cache":
|
|
mntOpt.WritebackCache, err = opt.GetBool(key)
|
|
case "volname":
|
|
mntOpt.VolumeName, err = opt.GetString(key)
|
|
case "noappledouble":
|
|
mntOpt.NoAppleDouble, err = opt.GetBool(key)
|
|
case "noapplexattr":
|
|
mntOpt.NoAppleXattr, err = opt.GetBool(key)
|
|
case "network-mode":
|
|
mntOpt.NetworkMode, err = opt.GetBool(key)
|
|
default:
|
|
ok = false
|
|
}
|
|
return
|
|
}
|
|
|
|
func getVFSOption(vfsOpt *vfscommon.Options, opt rc.Params, key string) (ok bool, err error) {
|
|
var intVal int64
|
|
ok = true
|
|
switch normalOptName(key) {
|
|
|
|
// options prefixed with "vfs-"
|
|
case "vfs-cache-mode":
|
|
err = getFVarP(&vfsOpt.CacheMode, opt, key)
|
|
case "vfs-cache-poll-interval":
|
|
vfsOpt.CachePollInterval, err = opt.GetDuration(key)
|
|
case "vfs-cache-max-age":
|
|
vfsOpt.CacheMaxAge, err = opt.GetDuration(key)
|
|
case "vfs-cache-max-size":
|
|
err = getFVarP(&vfsOpt.CacheMaxSize, opt, key)
|
|
case "vfs-read-chunk-size":
|
|
err = getFVarP(&vfsOpt.ChunkSize, opt, key)
|
|
case "vfs-read-chunk-size-limit":
|
|
err = getFVarP(&vfsOpt.ChunkSizeLimit, opt, key)
|
|
case "vfs-case-insensitive":
|
|
vfsOpt.CaseInsensitive, err = opt.GetBool(key)
|
|
case "vfs-write-wait":
|
|
vfsOpt.WriteWait, err = opt.GetDuration(key)
|
|
case "vfs-read-wait":
|
|
vfsOpt.ReadWait, err = opt.GetDuration(key)
|
|
case "vfs-write-back":
|
|
vfsOpt.WriteBack, err = opt.GetDuration(key)
|
|
case "vfs-read-ahead":
|
|
err = getFVarP(&vfsOpt.ReadAhead, opt, key)
|
|
case "vfs-used-is-size":
|
|
vfsOpt.UsedIsSize, err = opt.GetBool(key)
|
|
|
|
// unprefixed vfs options
|
|
case "no-modtime":
|
|
vfsOpt.NoModTime, err = opt.GetBool(key)
|
|
case "no-checksum":
|
|
vfsOpt.NoChecksum, err = opt.GetBool(key)
|
|
case "dir-cache-time":
|
|
vfsOpt.DirCacheTime, err = opt.GetDuration(key)
|
|
case "poll-interval":
|
|
vfsOpt.PollInterval, err = opt.GetDuration(key)
|
|
case "read-only":
|
|
vfsOpt.ReadOnly, err = opt.GetBool(key)
|
|
case "dir-perms":
|
|
perms := &vfsflags.FileMode{Mode: &vfsOpt.DirPerms}
|
|
err = getFVarP(perms, opt, key)
|
|
case "file-perms":
|
|
perms := &vfsflags.FileMode{Mode: &vfsOpt.FilePerms}
|
|
err = getFVarP(perms, opt, key)
|
|
|
|
// unprefixed unix-only vfs options
|
|
case "umask":
|
|
// GetInt64 doesn't support the `0octal` umask syntax - parse locally
|
|
var strVal string
|
|
if strVal, err = opt.GetString(key); err == nil {
|
|
var longVal int64
|
|
if longVal, err = strconv.ParseInt(strVal, 0, 0); err == nil {
|
|
vfsOpt.Umask = int(longVal)
|
|
}
|
|
}
|
|
case "uid":
|
|
intVal, err = opt.GetInt64(key)
|
|
vfsOpt.UID = uint32(intVal)
|
|
case "gid":
|
|
intVal, err = opt.GetInt64(key)
|
|
vfsOpt.GID = uint32(intVal)
|
|
|
|
// non-vfs options
|
|
default:
|
|
ok = false
|
|
}
|
|
return
|
|
}
|
|
|
|
func getFVarP(pvalue pflag.Value, opt rc.Params, key string) error {
|
|
str, err := opt.GetString(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return pvalue.Set(str)
|
|
}
|
|
|
|
func getStringArray(opt rc.Params, key string) ([]string, error) {
|
|
str, err := opt.GetString(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return strings.Split(str, ","), nil
|
|
}
|
|
|
|
func normalOptName(key string) string {
|
|
return strings.ReplaceAll(strings.TrimPrefix(strings.ToLower(key), "--"), "_", "-")
|
|
}
|