cmd.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. // Package cmd implements the rclone command
  2. //
  3. // It is in a sub package so it's internals can be reused elsewhere
  4. package cmd
  5. // FIXME only attach the remote flags when using a remote???
  6. // would probably mean bringing all the flags in to here? Or define some flagsets in fs...
  7. import (
  8. "context"
  9. "errors"
  10. "fmt"
  11. "log"
  12. "os"
  13. "os/exec"
  14. "path"
  15. "regexp"
  16. "runtime"
  17. "runtime/pprof"
  18. "strconv"
  19. "strings"
  20. "sync"
  21. "time"
  22. "github.com/rclone/rclone/fs"
  23. "github.com/rclone/rclone/fs/accounting"
  24. "github.com/rclone/rclone/fs/cache"
  25. "github.com/rclone/rclone/fs/config/configfile"
  26. "github.com/rclone/rclone/fs/config/configflags"
  27. "github.com/rclone/rclone/fs/config/flags"
  28. "github.com/rclone/rclone/fs/filter"
  29. "github.com/rclone/rclone/fs/filter/filterflags"
  30. "github.com/rclone/rclone/fs/fserrors"
  31. "github.com/rclone/rclone/fs/fspath"
  32. fslog "github.com/rclone/rclone/fs/log"
  33. "github.com/rclone/rclone/fs/rc/rcflags"
  34. "github.com/rclone/rclone/fs/rc/rcserver"
  35. fssync "github.com/rclone/rclone/fs/sync"
  36. "github.com/rclone/rclone/lib/atexit"
  37. "github.com/rclone/rclone/lib/buildinfo"
  38. "github.com/rclone/rclone/lib/exitcode"
  39. "github.com/rclone/rclone/lib/terminal"
  40. "github.com/spf13/cobra"
  41. "github.com/spf13/pflag"
  42. )
  43. // Globals
  44. var (
  45. // Flags
  46. cpuProfile = flags.StringP("cpuprofile", "", "", "Write cpu profile to file", "Debugging")
  47. memProfile = flags.StringP("memprofile", "", "", "Write memory profile to file", "Debugging")
  48. statsInterval = flags.DurationP("stats", "", time.Minute*1, "Interval between printing stats, e.g. 500ms, 60s, 5m (0 to disable)", "Logging")
  49. dataRateUnit = flags.StringP("stats-unit", "", "bytes", "Show data rate in stats as either 'bits' or 'bytes' per second", "Logging")
  50. version bool
  51. // Errors
  52. errorCommandNotFound = errors.New("command not found")
  53. errorUncategorized = errors.New("uncategorized error")
  54. errorNotEnoughArguments = errors.New("not enough arguments")
  55. errorTooManyArguments = errors.New("too many arguments")
  56. )
  57. // ShowVersion prints the version to stdout
  58. func ShowVersion() {
  59. osVersion, osKernel := buildinfo.GetOSVersion()
  60. if osVersion == "" {
  61. osVersion = "unknown"
  62. }
  63. if osKernel == "" {
  64. osKernel = "unknown"
  65. }
  66. linking, tagString := buildinfo.GetLinkingAndTags()
  67. arch := buildinfo.GetArch()
  68. fmt.Printf("rclone %s\n", fs.Version)
  69. fmt.Printf("- os/version: %s\n", osVersion)
  70. fmt.Printf("- os/kernel: %s\n", osKernel)
  71. fmt.Printf("- os/type: %s\n", runtime.GOOS)
  72. fmt.Printf("- os/arch: %s\n", arch)
  73. fmt.Printf("- go/version: %s\n", runtime.Version())
  74. fmt.Printf("- go/linking: %s\n", linking)
  75. fmt.Printf("- go/tags: %s\n", tagString)
  76. }
  77. // NewFsFile creates an Fs from a name but may point to a file.
  78. //
  79. // It returns a string with the file name if points to a file
  80. // otherwise "".
  81. func NewFsFile(remote string) (fs.Fs, string) {
  82. _, fsPath, err := fspath.SplitFs(remote)
  83. if err != nil {
  84. err = fs.CountError(err)
  85. log.Fatalf("Failed to create file system for %q: %v", remote, err)
  86. }
  87. f, err := cache.Get(context.Background(), remote)
  88. switch err {
  89. case fs.ErrorIsFile:
  90. cache.Pin(f) // pin indefinitely since it was on the CLI
  91. return f, path.Base(fsPath)
  92. case nil:
  93. cache.Pin(f) // pin indefinitely since it was on the CLI
  94. return f, ""
  95. default:
  96. err = fs.CountError(err)
  97. log.Fatalf("Failed to create file system for %q: %v", remote, err)
  98. }
  99. return nil, ""
  100. }
  101. // newFsFileAddFilter creates an src Fs from a name
  102. //
  103. // This works the same as NewFsFile however it adds filters to the Fs
  104. // to limit it to a single file if the remote pointed to a file.
  105. func newFsFileAddFilter(remote string) (fs.Fs, string) {
  106. fi := filter.GetConfig(context.Background())
  107. f, fileName := NewFsFile(remote)
  108. if fileName != "" {
  109. if !fi.InActive() {
  110. err := fmt.Errorf("can't limit to single files when using filters: %v", remote)
  111. err = fs.CountError(err)
  112. log.Fatalf(err.Error())
  113. }
  114. // Limit transfers to this file
  115. err := fi.AddFile(fileName)
  116. if err != nil {
  117. err = fs.CountError(err)
  118. log.Fatalf("Failed to limit to single file %q: %v", remote, err)
  119. }
  120. }
  121. return f, fileName
  122. }
  123. // NewFsSrc creates a new src fs from the arguments.
  124. //
  125. // The source can be a file or a directory - if a file then it will
  126. // limit the Fs to a single file.
  127. func NewFsSrc(args []string) fs.Fs {
  128. fsrc, _ := newFsFileAddFilter(args[0])
  129. return fsrc
  130. }
  131. // newFsDir creates an Fs from a name
  132. //
  133. // This must point to a directory
  134. func newFsDir(remote string) fs.Fs {
  135. f, err := cache.Get(context.Background(), remote)
  136. if err != nil {
  137. err = fs.CountError(err)
  138. log.Fatalf("Failed to create file system for %q: %v", remote, err)
  139. }
  140. cache.Pin(f) // pin indefinitely since it was on the CLI
  141. return f
  142. }
  143. // NewFsDir creates a new Fs from the arguments
  144. //
  145. // The argument must point a directory
  146. func NewFsDir(args []string) fs.Fs {
  147. fdst := newFsDir(args[0])
  148. return fdst
  149. }
  150. // NewFsSrcDst creates a new src and dst fs from the arguments
  151. func NewFsSrcDst(args []string) (fs.Fs, fs.Fs) {
  152. fsrc, _ := newFsFileAddFilter(args[0])
  153. fdst := newFsDir(args[1])
  154. return fsrc, fdst
  155. }
  156. // NewFsSrcFileDst creates a new src and dst fs from the arguments
  157. //
  158. // The source may be a file, in which case the source Fs and file name is returned
  159. func NewFsSrcFileDst(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs) {
  160. fsrc, srcFileName = NewFsFile(args[0])
  161. fdst = newFsDir(args[1])
  162. return fsrc, srcFileName, fdst
  163. }
  164. // NewFsSrcDstFiles creates a new src and dst fs from the arguments
  165. // If src is a file then srcFileName and dstFileName will be non-empty
  166. func NewFsSrcDstFiles(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs, dstFileName string) {
  167. fsrc, srcFileName = newFsFileAddFilter(args[0])
  168. // If copying a file...
  169. dstRemote := args[1]
  170. // If file exists then srcFileName != "", however if the file
  171. // doesn't exist then we assume it is a directory...
  172. if srcFileName != "" {
  173. var err error
  174. dstRemote, dstFileName, err = fspath.Split(dstRemote)
  175. if err != nil {
  176. log.Fatalf("Parsing %q failed: %v", args[1], err)
  177. }
  178. if dstRemote == "" {
  179. dstRemote = "."
  180. }
  181. if dstFileName == "" {
  182. log.Fatalf("%q is a directory", args[1])
  183. }
  184. }
  185. fdst, err := cache.Get(context.Background(), dstRemote)
  186. switch err {
  187. case fs.ErrorIsFile:
  188. _ = fs.CountError(err)
  189. log.Fatalf("Source doesn't exist or is a directory and destination is a file")
  190. case nil:
  191. default:
  192. _ = fs.CountError(err)
  193. log.Fatalf("Failed to create file system for destination %q: %v", dstRemote, err)
  194. }
  195. cache.Pin(fdst) // pin indefinitely since it was on the CLI
  196. return
  197. }
  198. // NewFsDstFile creates a new dst fs with a destination file name from the arguments
  199. func NewFsDstFile(args []string) (fdst fs.Fs, dstFileName string) {
  200. dstRemote, dstFileName, err := fspath.Split(args[0])
  201. if err != nil {
  202. log.Fatalf("Parsing %q failed: %v", args[0], err)
  203. }
  204. if dstRemote == "" {
  205. dstRemote = "."
  206. }
  207. if dstFileName == "" {
  208. log.Fatalf("%q is a directory", args[0])
  209. }
  210. fdst = newFsDir(dstRemote)
  211. return
  212. }
  213. // ShowStats returns true if the user added a `--stats` flag to the command line.
  214. //
  215. // This is called by Run to override the default value of the
  216. // showStats passed in.
  217. func ShowStats() bool {
  218. statsIntervalFlag := pflag.Lookup("stats")
  219. return statsIntervalFlag != nil && statsIntervalFlag.Changed
  220. }
  221. // Run the function with stats and retries if required
  222. func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
  223. ci := fs.GetConfig(context.Background())
  224. var cmdErr error
  225. stopStats := func() {}
  226. if !showStats && ShowStats() {
  227. showStats = true
  228. }
  229. if ci.Progress {
  230. stopStats = startProgress()
  231. } else if showStats {
  232. stopStats = StartStats()
  233. }
  234. SigInfoHandler()
  235. for try := 1; try <= ci.Retries; try++ {
  236. cmdErr = f()
  237. cmdErr = fs.CountError(cmdErr)
  238. lastErr := accounting.GlobalStats().GetLastError()
  239. if cmdErr == nil {
  240. cmdErr = lastErr
  241. }
  242. if !Retry || !accounting.GlobalStats().Errored() {
  243. if try > 1 {
  244. fs.Errorf(nil, "Attempt %d/%d succeeded", try, ci.Retries)
  245. }
  246. break
  247. }
  248. if accounting.GlobalStats().HadFatalError() {
  249. fs.Errorf(nil, "Fatal error received - not attempting retries")
  250. break
  251. }
  252. if accounting.GlobalStats().Errored() && !accounting.GlobalStats().HadRetryError() {
  253. fs.Errorf(nil, "Can't retry any of the errors - not attempting retries")
  254. break
  255. }
  256. if retryAfter := accounting.GlobalStats().RetryAfter(); !retryAfter.IsZero() {
  257. d := time.Until(retryAfter)
  258. if d > 0 {
  259. fs.Logf(nil, "Received retry after error - sleeping until %s (%v)", retryAfter.Format(time.RFC3339Nano), d)
  260. time.Sleep(d)
  261. }
  262. }
  263. if lastErr != nil {
  264. fs.Errorf(nil, "Attempt %d/%d failed with %d errors and: %v", try, ci.Retries, accounting.GlobalStats().GetErrors(), lastErr)
  265. } else {
  266. fs.Errorf(nil, "Attempt %d/%d failed with %d errors", try, ci.Retries, accounting.GlobalStats().GetErrors())
  267. }
  268. if try < ci.Retries {
  269. accounting.GlobalStats().ResetErrors()
  270. }
  271. if ci.RetriesInterval > 0 {
  272. time.Sleep(ci.RetriesInterval)
  273. }
  274. }
  275. stopStats()
  276. if showStats && (accounting.GlobalStats().Errored() || *statsInterval > 0) {
  277. accounting.GlobalStats().Log()
  278. }
  279. fs.Debugf(nil, "%d go routines active\n", runtime.NumGoroutine())
  280. if ci.Progress && ci.ProgressTerminalTitle {
  281. // Clear terminal title
  282. terminal.WriteTerminalTitle("")
  283. }
  284. // dump all running go-routines
  285. if ci.Dump&fs.DumpGoRoutines != 0 {
  286. err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
  287. if err != nil {
  288. fs.Errorf(nil, "Failed to dump goroutines: %v", err)
  289. }
  290. }
  291. // dump open files
  292. if ci.Dump&fs.DumpOpenFiles != 0 {
  293. c := exec.Command("lsof", "-p", strconv.Itoa(os.Getpid()))
  294. c.Stdout = os.Stdout
  295. c.Stderr = os.Stderr
  296. err := c.Run()
  297. if err != nil {
  298. fs.Errorf(nil, "Failed to list open files: %v", err)
  299. }
  300. }
  301. // clear cache and shutdown backends
  302. cache.Clear()
  303. if lastErr := accounting.GlobalStats().GetLastError(); cmdErr == nil {
  304. cmdErr = lastErr
  305. }
  306. // Log the final error message and exit
  307. if cmdErr != nil {
  308. nerrs := accounting.GlobalStats().GetErrors()
  309. if nerrs <= 1 {
  310. log.Printf("Failed to %s: %v", cmd.Name(), cmdErr)
  311. } else {
  312. log.Printf("Failed to %s with %d errors: last error was: %v", cmd.Name(), nerrs, cmdErr)
  313. }
  314. }
  315. resolveExitCode(cmdErr)
  316. }
  317. // CheckArgs checks there are enough arguments and prints a message if not
  318. func CheckArgs(MinArgs, MaxArgs int, cmd *cobra.Command, args []string) {
  319. if len(args) < MinArgs {
  320. _ = cmd.Usage()
  321. _, _ = fmt.Fprintf(os.Stderr, "Command %s needs %d arguments minimum: you provided %d non flag arguments: %q\n", cmd.Name(), MinArgs, len(args), args)
  322. resolveExitCode(errorNotEnoughArguments)
  323. } else if len(args) > MaxArgs {
  324. _ = cmd.Usage()
  325. _, _ = fmt.Fprintf(os.Stderr, "Command %s needs %d arguments maximum: you provided %d non flag arguments: %q\n", cmd.Name(), MaxArgs, len(args), args)
  326. resolveExitCode(errorTooManyArguments)
  327. }
  328. }
  329. // StartStats prints the stats every statsInterval
  330. //
  331. // It returns a func which should be called to stop the stats.
  332. func StartStats() func() {
  333. if *statsInterval <= 0 {
  334. return func() {}
  335. }
  336. stopStats := make(chan struct{})
  337. var wg sync.WaitGroup
  338. wg.Add(1)
  339. go func() {
  340. defer wg.Done()
  341. ticker := time.NewTicker(*statsInterval)
  342. for {
  343. select {
  344. case <-ticker.C:
  345. accounting.GlobalStats().Log()
  346. case <-stopStats:
  347. ticker.Stop()
  348. return
  349. }
  350. }
  351. }()
  352. return func() {
  353. close(stopStats)
  354. wg.Wait()
  355. }
  356. }
  357. // initConfig is run by cobra after initialising the flags
  358. func initConfig() {
  359. ctx := context.Background()
  360. ci := fs.GetConfig(ctx)
  361. // Start the logger
  362. fslog.InitLogging()
  363. // Finish parsing any command line flags
  364. configflags.SetFlags(ci)
  365. // Load the config
  366. configfile.Install()
  367. // Start accounting
  368. accounting.Start(ctx)
  369. // Configure console
  370. if ci.NoConsole {
  371. // Hide the console window
  372. terminal.HideConsole()
  373. } else {
  374. // Enable color support on stdout if possible.
  375. // This enables virtual terminal processing on Windows 10,
  376. // adding native support for ANSI/VT100 escape sequences.
  377. terminal.EnableColorsStdout()
  378. }
  379. // Load filters
  380. err := filterflags.Reload(ctx)
  381. if err != nil {
  382. log.Fatalf("Failed to load filters: %v", err)
  383. }
  384. // Write the args for debug purposes
  385. fs.Debugf("rclone", "Version %q starting with parameters %q", fs.Version, os.Args)
  386. // Inform user about systemd log support now that we have a logger
  387. if fslog.Opt.LogSystemdSupport {
  388. fs.Debugf("rclone", "systemd logging support activated")
  389. }
  390. // Start the remote control server if configured
  391. _, err = rcserver.Start(context.Background(), &rcflags.Opt)
  392. if err != nil {
  393. log.Fatalf("Failed to start remote control: %v", err)
  394. }
  395. // Setup CPU profiling if desired
  396. if *cpuProfile != "" {
  397. fs.Infof(nil, "Creating CPU profile %q\n", *cpuProfile)
  398. f, err := os.Create(*cpuProfile)
  399. if err != nil {
  400. err = fs.CountError(err)
  401. log.Fatal(err)
  402. }
  403. err = pprof.StartCPUProfile(f)
  404. if err != nil {
  405. err = fs.CountError(err)
  406. log.Fatal(err)
  407. }
  408. atexit.Register(func() {
  409. pprof.StopCPUProfile()
  410. err := f.Close()
  411. if err != nil {
  412. err = fs.CountError(err)
  413. log.Fatal(err)
  414. }
  415. })
  416. }
  417. // Setup memory profiling if desired
  418. if *memProfile != "" {
  419. atexit.Register(func() {
  420. fs.Infof(nil, "Saving Memory profile %q\n", *memProfile)
  421. f, err := os.Create(*memProfile)
  422. if err != nil {
  423. err = fs.CountError(err)
  424. log.Fatal(err)
  425. }
  426. err = pprof.WriteHeapProfile(f)
  427. if err != nil {
  428. err = fs.CountError(err)
  429. log.Fatal(err)
  430. }
  431. err = f.Close()
  432. if err != nil {
  433. err = fs.CountError(err)
  434. log.Fatal(err)
  435. }
  436. })
  437. }
  438. if m, _ := regexp.MatchString("^(bits|bytes)$", *dataRateUnit); !m {
  439. fs.Errorf(nil, "Invalid unit passed to --stats-unit. Defaulting to bytes.")
  440. ci.DataRateUnit = "bytes"
  441. } else {
  442. ci.DataRateUnit = *dataRateUnit
  443. }
  444. }
  445. func resolveExitCode(err error) {
  446. ci := fs.GetConfig(context.Background())
  447. atexit.Run()
  448. if err == nil {
  449. if ci.ErrorOnNoTransfer {
  450. if accounting.GlobalStats().GetTransfers() == 0 {
  451. os.Exit(exitcode.NoFilesTransferred)
  452. }
  453. }
  454. os.Exit(exitcode.Success)
  455. }
  456. switch {
  457. case errors.Is(err, fs.ErrorDirNotFound):
  458. os.Exit(exitcode.DirNotFound)
  459. case errors.Is(err, fs.ErrorObjectNotFound):
  460. os.Exit(exitcode.FileNotFound)
  461. case errors.Is(err, errorUncategorized):
  462. os.Exit(exitcode.UncategorizedError)
  463. case errors.Is(err, accounting.ErrorMaxTransferLimitReached):
  464. os.Exit(exitcode.TransferExceeded)
  465. case errors.Is(err, fssync.ErrorMaxDurationReached):
  466. os.Exit(exitcode.DurationExceeded)
  467. case fserrors.ShouldRetry(err):
  468. os.Exit(exitcode.RetryError)
  469. case fserrors.IsNoRetryError(err), fserrors.IsNoLowLevelRetryError(err):
  470. os.Exit(exitcode.NoRetryError)
  471. case fserrors.IsFatalError(err):
  472. os.Exit(exitcode.FatalError)
  473. default:
  474. os.Exit(exitcode.UsageError)
  475. }
  476. }
  477. var backendFlags map[string]struct{}
  478. // AddBackendFlags creates flags for all the backend options
  479. func AddBackendFlags() {
  480. backendFlags = map[string]struct{}{}
  481. for _, fsInfo := range fs.Registry {
  482. done := map[string]struct{}{}
  483. for i := range fsInfo.Options {
  484. opt := &fsInfo.Options[i]
  485. // Skip if done already (e.g. with Provider options)
  486. if _, doneAlready := done[opt.Name]; doneAlready {
  487. continue
  488. }
  489. done[opt.Name] = struct{}{}
  490. // Make a flag from each option
  491. name := opt.FlagName(fsInfo.Prefix)
  492. found := pflag.CommandLine.Lookup(name) != nil
  493. if !found {
  494. // Take first line of help only
  495. help := strings.TrimSpace(opt.Help)
  496. if nl := strings.IndexRune(help, '\n'); nl >= 0 {
  497. help = help[:nl]
  498. }
  499. help = strings.TrimRight(strings.TrimSpace(help), ".!?")
  500. if opt.IsPassword {
  501. help += " (obscured)"
  502. }
  503. flag := pflag.CommandLine.VarPF(opt, name, opt.ShortOpt, help)
  504. flags.SetDefaultFromEnv(pflag.CommandLine, name)
  505. if _, isBool := opt.Default.(bool); isBool {
  506. flag.NoOptDefVal = "true"
  507. }
  508. // Hide on the command line if requested
  509. if opt.Hide&fs.OptionHideCommandLine != 0 {
  510. flag.Hidden = true
  511. }
  512. backendFlags[name] = struct{}{}
  513. } else {
  514. fs.Errorf(nil, "Not adding duplicate flag --%s", name)
  515. }
  516. // flag.Hidden = true
  517. }
  518. }
  519. }
  520. // Main runs rclone interpreting flags and commands out of os.Args
  521. func Main() {
  522. setupRootCommand(Root)
  523. AddBackendFlags()
  524. if err := Root.Execute(); err != nil {
  525. if strings.HasPrefix(err.Error(), "unknown command") && selfupdateEnabled {
  526. Root.PrintErrf("You could use '%s selfupdate' to get latest features.\n\n", Root.CommandPath())
  527. }
  528. log.Fatalf("Fatal error: %v", err)
  529. }
  530. }