github.com/divyam234/rclone@v1.64.1/fs/mount_helper.go (about) 1 package fs 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "path/filepath" 9 "runtime" 10 "strings" 11 ) 12 13 func init() { 14 // This block is run super-early, before configuration harness kick in 15 if IsMountHelper() { 16 if args, err := convertMountHelperArgs(os.Args); err == nil { 17 os.Args = args 18 } else { 19 log.Fatalf("Failed to parse command line: %v", err) 20 } 21 } 22 } 23 24 // PassDaemonArgsAsEnviron tells how CLI arguments are passed to the daemon 25 // When false, arguments are passed as is, visible in the `ps` output. 26 // When true, arguments are converted into environment variables (more secure). 27 var PassDaemonArgsAsEnviron bool 28 29 // Comma-separated list of mount options to ignore. 30 // Leading and trailing commas are required. 31 const helperIgnoredOpts = ",rw,_netdev,nofail,user,dev,nodev,suid,nosuid,exec,noexec,auto,noauto," 32 33 // Valid option name characters 34 const helperValidOptChars = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 35 36 // Parser errors 37 var ( 38 errHelperBadOption = errors.New("option names may only contain `0-9`, `A-Z`, `a-z`, `-` and `_`") 39 errHelperOptionName = errors.New("option name can't start with `-` or `_`") 40 errHelperEmptyOption = errors.New("option name can't be empty") 41 errHelperQuotedValue = errors.New("unterminated quoted value") 42 errHelperAfterQuote = errors.New("expecting `,` or another quote after a quote") 43 errHelperSyntax = errors.New("syntax error in option string") 44 errHelperEmptyCommand = errors.New("command name can't be empty") 45 errHelperEnvSyntax = errors.New("environment variable must have syntax env.NAME=[VALUE]") 46 ) 47 48 // IsMountHelper returns true if rclone was invoked as mount helper: 49 // as /sbin/mount.rlone (by /bin/mount) 50 // or /usr/bin/rclonefs (by fusermount or directly) 51 func IsMountHelper() bool { 52 if runtime.GOOS == "windows" { 53 return false 54 } 55 me := filepath.Base(os.Args[0]) 56 return me == "mount.rclone" || me == "rclonefs" 57 } 58 59 // convertMountHelperArgs converts "-o" styled mount helper arguments 60 // into usual rclone flags 61 func convertMountHelperArgs(origArgs []string) ([]string, error) { 62 if IsDaemon() { 63 // The arguments have already been converted by the parent 64 return origArgs, nil 65 } 66 67 args := []string{} 68 command := "mount" 69 parseOpts := false 70 gotDaemon := false 71 gotVerbose := false 72 vCount := 0 73 74 for _, arg := range origArgs[1:] { 75 if !parseOpts { 76 switch arg { 77 case "-o", "--opt": 78 parseOpts = true 79 case "-v", "-vv", "-vvv", "-vvvv": 80 vCount += len(arg) - 1 81 case "-h", "--help": 82 args = append(args, "--help") 83 default: 84 if strings.HasPrefix(arg, "-") { 85 return nil, fmt.Errorf("flag %q is not supported in mount mode", arg) 86 } 87 args = append(args, arg) 88 } 89 continue 90 } 91 92 opts, err := parseHelperOptionString(arg) 93 if err != nil { 94 return nil, err 95 } 96 parseOpts = false 97 98 for _, opt := range opts { 99 if strings.Contains(helperIgnoredOpts, ","+opt+",") || strings.HasPrefix(opt, "x-systemd") { 100 continue 101 } 102 103 param, value := opt, "" 104 if idx := strings.Index(opt, "="); idx != -1 { 105 param, value = opt[:idx], opt[idx+1:] 106 } 107 108 // Set environment variables 109 if strings.HasPrefix(param, "env.") { 110 if param = param[4:]; param == "" { 111 return nil, errHelperEnvSyntax 112 } 113 _ = os.Setenv(param, value) 114 continue 115 } 116 117 switch param { 118 // Change command to run 119 case "command": 120 if value == "" { 121 return nil, errHelperEmptyCommand 122 } 123 command = value 124 continue 125 // Flag StartDaemon to pass arguments as environment 126 case "args2env": 127 PassDaemonArgsAsEnviron = true 128 continue 129 // Handle verbosity options 130 case "v", "vv", "vvv", "vvvv": 131 vCount += len(param) 132 continue 133 case "verbose": 134 gotVerbose = true 135 // Don't add --daemon if it was explicitly included 136 case "daemon": 137 gotDaemon = true 138 // Alias for the standard mount option "ro" 139 case "ro": 140 param = "read-only" 141 } 142 143 arg = "--" + strings.ToLower(strings.ReplaceAll(param, "_", "-")) 144 if value != "" { 145 arg += "=" + value 146 } 147 args = append(args, arg) 148 } 149 } 150 if parseOpts { 151 return nil, fmt.Errorf("dangling -o without argument") 152 } 153 154 if vCount > 0 && !gotVerbose { 155 args = append(args, fmt.Sprintf("--verbose=%d", vCount)) 156 } 157 if strings.Contains(command, "mount") && !gotDaemon { 158 // Default to daemonized mount 159 args = append(args, "--daemon") 160 } 161 if len(args) > 0 && args[0] == command { 162 // Remove artefact of repeated conversion 163 args = args[1:] 164 } 165 prepend := []string{origArgs[0], command} 166 return append(prepend, args...), nil 167 } 168 169 // parseHelperOptionString deconstructs the -o value into slice of options 170 // in a way similar to connection strings. 171 // Example: 172 // 173 // param1=value,param2="qvalue",param3='item1,item2',param4="a ""b"" 'c'" 174 // 175 // An error may be returned if the remote name has invalid characters 176 // or the parameters are invalid or the path is empty. 177 // 178 // The algorithm was adapted from fspath.Parse with some modifications: 179 // - allow `-` in option names 180 // - handle special options `x-systemd.X` and `env.X` 181 // - drop support for :backend: and /path 182 func parseHelperOptionString(optString string) (opts []string, err error) { 183 if optString = strings.TrimSpace(optString); optString == "" { 184 return nil, nil 185 } 186 // States for parser 187 const ( 188 stateParam = uint8(iota) 189 stateValue 190 stateQuotedValue 191 stateAfterQuote 192 stateDone 193 ) 194 var ( 195 state = stateParam // current state of parser 196 i int // position in path 197 prev int // previous position in path 198 c rune // current rune under consideration 199 quote rune // kind of quote to end this quoted string 200 param string // current parameter value 201 doubled bool // set if had doubled quotes 202 ) 203 for i, c = range optString + "," { 204 switch state { 205 // Parses param= and param2= 206 case stateParam: 207 switch c { 208 case ',', '=': 209 param = optString[prev:i] 210 if len(param) == 0 { 211 return nil, errHelperEmptyOption 212 } 213 if param[0] == '-' { 214 return nil, errHelperOptionName 215 } 216 prev = i + 1 217 if c == '=' { 218 state = stateValue 219 break 220 } 221 opts = append(opts, param) 222 case '.': 223 if pref := optString[prev:i]; pref != "env" && pref != "x-systemd" { 224 return nil, errHelperBadOption 225 } 226 default: 227 if !strings.ContainsRune(helperValidOptChars, c) { 228 return nil, errHelperBadOption 229 } 230 } 231 case stateValue: 232 switch c { 233 case '\'', '"': 234 if i == prev { 235 quote = c 236 prev = i + 1 237 doubled = false 238 state = stateQuotedValue 239 } 240 case ',': 241 value := optString[prev:i] 242 prev = i + 1 243 opts = append(opts, param+"="+value) 244 state = stateParam 245 } 246 case stateQuotedValue: 247 if c == quote { 248 state = stateAfterQuote 249 } 250 case stateAfterQuote: 251 switch c { 252 case ',': 253 value := optString[prev : i-1] 254 // replace any doubled quotes if there were any 255 if doubled { 256 value = strings.ReplaceAll(value, string(quote)+string(quote), string(quote)) 257 } 258 prev = i + 1 259 opts = append(opts, param+"="+value) 260 state = stateParam 261 case quote: 262 // Here is a doubled quote to indicate a literal quote 263 state = stateQuotedValue 264 doubled = true 265 default: 266 return nil, errHelperAfterQuote 267 } 268 } 269 } 270 271 // Depending on which state we were in when we fell off the 272 // end of the state machine we can return a sensible error. 273 if state == stateParam && prev > len(optString) { 274 state = stateDone 275 } 276 switch state { 277 case stateQuotedValue: 278 return nil, errHelperQuotedValue 279 case stateAfterQuote: 280 return nil, errHelperAfterQuote 281 case stateDone: 282 break 283 default: 284 return nil, errHelperSyntax 285 } 286 return opts, nil 287 }