github.com/artpar/rclone@v1.67.3/cmd/serve/docker/options.go (about)

     1  package docker
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/artpar/rclone/cmd/mountlib"
     9  	"github.com/artpar/rclone/fs"
    10  	"github.com/artpar/rclone/fs/config/configmap"
    11  	"github.com/artpar/rclone/fs/fspath"
    12  	"github.com/artpar/rclone/fs/rc"
    13  	"github.com/artpar/rclone/vfs/vfscommon"
    14  	"github.com/artpar/rclone/vfs/vfsflags"
    15  
    16  	"github.com/spf13/pflag"
    17  )
    18  
    19  // applyOptions configures volume from request options.
    20  //
    21  // There are 5 special options:
    22  //   - "remote" aka "fs" determines existing remote from config file
    23  //     with a path or on-the-fly remote using the ":backend:" syntax.
    24  //     It is usually named "remote" in documentation but can be aliased as
    25  //     "fs" to avoid confusion with the "remote" option of some backends.
    26  //   - "type" is equivalent to the ":backend:" syntax (optional).
    27  //   - "path" provides explicit on-remote path for "type" (optional).
    28  //   - "mount-type" can be "mount", "cmount" or "mount2", defaults to
    29  //     first found (optional).
    30  //   - "persist" is reserved for future to create remotes persisted
    31  //     in rclone.conf similar to rcd (optional).
    32  //
    33  // Unlike rcd we use the flat naming scheme for mount, vfs and backend
    34  // options without substructures. Dashes, underscores and mixed case
    35  // in option names can be used interchangeably. Option name conflicts
    36  // can be resolved in a manner similar to rclone CLI by adding prefixes:
    37  // "vfs-", primary mount backend type like "sftp-", and so on.
    38  //
    39  // After triaging the options are put in MountOpt, VFSOpt or connect
    40  // string for actual filesystem setup and in volume.Options for saving
    41  // the state.
    42  func (vol *Volume) applyOptions(volOpt VolOpts) error {
    43  	// copy options to override later
    44  	mntOpt := &vol.mnt.MountOpt
    45  	vfsOpt := &vol.mnt.VFSOpt
    46  	*mntOpt = vol.drv.mntOpt
    47  	*vfsOpt = vol.drv.vfsOpt
    48  
    49  	// vol.Options has all options except "remote" and "type"
    50  	vol.Options = VolOpts{}
    51  	vol.fsString = ""
    52  
    53  	var fsName, fsPath, fsType string
    54  	var explicitPath string
    55  	var fsOpt configmap.Simple
    56  
    57  	// parse "remote" or "type"
    58  	for key, str := range volOpt {
    59  		switch key {
    60  		case "":
    61  			continue
    62  		case "remote", "fs":
    63  			if str != "" {
    64  				p, err := fspath.Parse(str)
    65  				if err != nil || p.Name == ":" {
    66  					return fmt.Errorf("cannot parse path %q: %w", str, err)
    67  				}
    68  				fsName, fsPath, fsOpt = p.Name, p.Path, p.Config
    69  				vol.Fs = str
    70  			}
    71  		case "type":
    72  			fsType = str
    73  			vol.Type = str
    74  		case "path":
    75  			explicitPath = str
    76  			vol.Path = str
    77  		default:
    78  			vol.Options[key] = str
    79  		}
    80  	}
    81  
    82  	// find options supported by backend
    83  	if strings.HasPrefix(fsName, ":") {
    84  		fsType = fsName[1:]
    85  		fsName = ""
    86  	}
    87  	if fsType == "" {
    88  		fsType = "local"
    89  		if fsName != "" {
    90  			var ok bool
    91  			fsType, ok = fs.ConfigMap(nil, fsName, nil).Get("type")
    92  			if !ok {
    93  				return fs.ErrorNotFoundInConfigFile
    94  			}
    95  		}
    96  	}
    97  	if explicitPath != "" {
    98  		if fsPath != "" {
    99  			fs.Logf(nil, "Explicit path will override connection string")
   100  		}
   101  		fsPath = explicitPath
   102  	}
   103  	fsInfo, err := fs.Find(fsType)
   104  	if err != nil {
   105  		return fmt.Errorf("unknown filesystem type %q", fsType)
   106  	}
   107  
   108  	// handle remaining options, override fsOpt
   109  	if fsOpt == nil {
   110  		fsOpt = configmap.Simple{}
   111  	}
   112  	opt := rc.Params{}
   113  	for key, val := range vol.Options {
   114  		opt[key] = val
   115  	}
   116  	for key := range opt {
   117  		var ok bool
   118  		var err error
   119  
   120  		switch normalOptName(key) {
   121  		case "persist":
   122  			vol.persist, err = opt.GetBool(key)
   123  			ok = true
   124  		case "mount-type":
   125  			vol.mountType, err = opt.GetString(key)
   126  			ok = true
   127  		}
   128  		if err != nil {
   129  			return fmt.Errorf("cannot parse option %q: %w", key, err)
   130  		}
   131  
   132  		if !ok {
   133  			// try to use as a mount option in mntOpt
   134  			ok, err = getMountOption(mntOpt, opt, key)
   135  			if ok && err != nil {
   136  				return fmt.Errorf("cannot parse mount option %q: %w", key, err)
   137  			}
   138  		}
   139  		if !ok {
   140  			// try as a vfs option in vfsOpt
   141  			ok, err = getVFSOption(vfsOpt, opt, key)
   142  			if ok && err != nil {
   143  				return fmt.Errorf("cannot parse vfs option %q: %w", key, err)
   144  			}
   145  		}
   146  
   147  		if !ok {
   148  			// try as a backend option in fsOpt (backends use "_" instead of "-")
   149  			optWithPrefix := strings.ReplaceAll(normalOptName(key), "-", "_")
   150  			fsOptName := strings.TrimPrefix(optWithPrefix, fsType+"_")
   151  			hasFsPrefix := optWithPrefix != fsOptName
   152  			if !hasFsPrefix || fsInfo.Options.Get(fsOptName) == nil {
   153  				fs.Logf(nil, "Option %q is not supported by backend %q", key, fsType)
   154  				return fmt.Errorf("unsupported backend option %q", key)
   155  			}
   156  			fsOpt[fsOptName], err = opt.GetString(key)
   157  			if err != nil {
   158  				return fmt.Errorf("cannot parse backend option %q: %w", key, err)
   159  			}
   160  		}
   161  	}
   162  
   163  	// build remote string from fsName, fsType, fsOpt, fsPath
   164  	colon := ":"
   165  	comma := ","
   166  	if fsName == "" {
   167  		fsName = ":" + fsType
   168  	}
   169  	connString := fsOpt.String()
   170  	if fsName == "" && fsType == "" {
   171  		colon = ""
   172  		connString = ""
   173  	}
   174  	if connString == "" {
   175  		comma = ""
   176  	}
   177  	vol.fsString = fsName + comma + connString + colon + fsPath
   178  
   179  	return vol.validate()
   180  }
   181  
   182  func getMountOption(mntOpt *mountlib.Options, opt rc.Params, key string) (ok bool, err error) {
   183  	ok = true
   184  	switch normalOptName(key) {
   185  	case "debug-fuse":
   186  		mntOpt.DebugFUSE, err = opt.GetBool(key)
   187  	case "attr-timeout":
   188  		mntOpt.AttrTimeout, err = opt.GetDuration(key)
   189  	case "option":
   190  		mntOpt.ExtraOptions, err = getStringArray(opt, key)
   191  	case "fuse-flag":
   192  		mntOpt.ExtraFlags, err = getStringArray(opt, key)
   193  	case "daemon":
   194  		mntOpt.Daemon, err = opt.GetBool(key)
   195  	case "daemon-timeout":
   196  		mntOpt.DaemonTimeout, err = opt.GetDuration(key)
   197  	case "default-permissions":
   198  		mntOpt.DefaultPermissions, err = opt.GetBool(key)
   199  	case "allow-non-empty":
   200  		mntOpt.AllowNonEmpty, err = opt.GetBool(key)
   201  	case "allow-root":
   202  		mntOpt.AllowRoot, err = opt.GetBool(key)
   203  	case "allow-other":
   204  		mntOpt.AllowOther, err = opt.GetBool(key)
   205  	case "async-read":
   206  		mntOpt.AsyncRead, err = opt.GetBool(key)
   207  	case "max-read-ahead":
   208  		err = getFVarP(&mntOpt.MaxReadAhead, opt, key)
   209  	case "write-back-cache":
   210  		mntOpt.WritebackCache, err = opt.GetBool(key)
   211  	case "volname":
   212  		mntOpt.VolumeName, err = opt.GetString(key)
   213  	case "noappledouble":
   214  		mntOpt.NoAppleDouble, err = opt.GetBool(key)
   215  	case "noapplexattr":
   216  		mntOpt.NoAppleXattr, err = opt.GetBool(key)
   217  	case "network-mode":
   218  		mntOpt.NetworkMode, err = opt.GetBool(key)
   219  	default:
   220  		ok = false
   221  	}
   222  	return
   223  }
   224  
   225  func getVFSOption(vfsOpt *vfscommon.Options, opt rc.Params, key string) (ok bool, err error) {
   226  	var intVal int64
   227  	ok = true
   228  	switch normalOptName(key) {
   229  
   230  	// options prefixed with "vfs-"
   231  	case "vfs-cache-mode":
   232  		err = getFVarP(&vfsOpt.CacheMode, opt, key)
   233  	case "vfs-cache-poll-interval":
   234  		vfsOpt.CachePollInterval, err = opt.GetDuration(key)
   235  	case "vfs-cache-max-age":
   236  		vfsOpt.CacheMaxAge, err = opt.GetDuration(key)
   237  	case "vfs-cache-max-size":
   238  		err = getFVarP(&vfsOpt.CacheMaxSize, opt, key)
   239  	case "vfs-read-chunk-size":
   240  		err = getFVarP(&vfsOpt.ChunkSize, opt, key)
   241  	case "vfs-read-chunk-size-limit":
   242  		err = getFVarP(&vfsOpt.ChunkSizeLimit, opt, key)
   243  	case "vfs-case-insensitive":
   244  		vfsOpt.CaseInsensitive, err = opt.GetBool(key)
   245  	case "vfs-write-wait":
   246  		vfsOpt.WriteWait, err = opt.GetDuration(key)
   247  	case "vfs-read-wait":
   248  		vfsOpt.ReadWait, err = opt.GetDuration(key)
   249  	case "vfs-write-back":
   250  		vfsOpt.WriteBack, err = opt.GetDuration(key)
   251  	case "vfs-read-ahead":
   252  		err = getFVarP(&vfsOpt.ReadAhead, opt, key)
   253  	case "vfs-used-is-size":
   254  		vfsOpt.UsedIsSize, err = opt.GetBool(key)
   255  
   256  	// unprefixed vfs options
   257  	case "no-modtime":
   258  		vfsOpt.NoModTime, err = opt.GetBool(key)
   259  	case "no-checksum":
   260  		vfsOpt.NoChecksum, err = opt.GetBool(key)
   261  	case "dir-cache-time":
   262  		vfsOpt.DirCacheTime, err = opt.GetDuration(key)
   263  	case "poll-interval":
   264  		vfsOpt.PollInterval, err = opt.GetDuration(key)
   265  	case "read-only":
   266  		vfsOpt.ReadOnly, err = opt.GetBool(key)
   267  	case "dir-perms":
   268  		perms := &vfsflags.FileMode{Mode: &vfsOpt.DirPerms}
   269  		err = getFVarP(perms, opt, key)
   270  	case "file-perms":
   271  		perms := &vfsflags.FileMode{Mode: &vfsOpt.FilePerms}
   272  		err = getFVarP(perms, opt, key)
   273  
   274  	// unprefixed unix-only vfs options
   275  	case "umask":
   276  		// GetInt64 doesn't support the `0octal` umask syntax - parse locally
   277  		var strVal string
   278  		if strVal, err = opt.GetString(key); err == nil {
   279  			var longVal int64
   280  			if longVal, err = strconv.ParseInt(strVal, 0, 0); err == nil {
   281  				vfsOpt.Umask = int(longVal)
   282  			}
   283  		}
   284  	case "uid":
   285  		intVal, err = opt.GetInt64(key)
   286  		vfsOpt.UID = uint32(intVal)
   287  	case "gid":
   288  		intVal, err = opt.GetInt64(key)
   289  		vfsOpt.GID = uint32(intVal)
   290  
   291  	// non-vfs options
   292  	default:
   293  		ok = false
   294  	}
   295  	return
   296  }
   297  
   298  func getFVarP(pvalue pflag.Value, opt rc.Params, key string) error {
   299  	str, err := opt.GetString(key)
   300  	if err != nil {
   301  		return err
   302  	}
   303  	return pvalue.Set(str)
   304  }
   305  
   306  func getStringArray(opt rc.Params, key string) ([]string, error) {
   307  	str, err := opt.GetString(key)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  	return strings.Split(str, ","), nil
   312  }
   313  
   314  func normalOptName(key string) string {
   315  	return strings.ReplaceAll(strings.TrimPrefix(strings.ToLower(key), "--"), "_", "-")
   316  }