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 }