2016-08-04 23:18:56 +02:00
|
|
|
// Package cmd implemnts the rclone command
|
|
|
|
//
|
|
|
|
// It is in a sub package so it's internals can be re-used elsewhere
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
// FIXME only attach the remote flags when using a remote???
|
|
|
|
// would probably mean bringing all the flags in to here? Or define some flagsets in fs...
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
2018-04-11 15:16:55 +02:00
|
|
|
"os/exec"
|
2016-08-04 23:18:56 +02:00
|
|
|
"path"
|
2016-11-22 05:04:05 +01:00
|
|
|
"regexp"
|
2016-08-04 23:18:56 +02:00
|
|
|
"runtime"
|
|
|
|
"runtime/pprof"
|
2018-04-11 15:16:55 +02:00
|
|
|
"strconv"
|
2018-05-14 19:06:57 +02:00
|
|
|
"strings"
|
2016-08-04 23:18:56 +02:00
|
|
|
"time"
|
|
|
|
|
2017-11-15 06:32:00 +01:00
|
|
|
"github.com/pkg/errors"
|
2016-08-04 23:18:56 +02:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
|
|
|
|
"github.com/ncw/rclone/fs"
|
2018-01-12 17:30:54 +01:00
|
|
|
"github.com/ncw/rclone/fs/accounting"
|
|
|
|
"github.com/ncw/rclone/fs/config/configflags"
|
|
|
|
"github.com/ncw/rclone/fs/config/flags"
|
|
|
|
"github.com/ncw/rclone/fs/filter"
|
|
|
|
"github.com/ncw/rclone/fs/filter/filterflags"
|
|
|
|
"github.com/ncw/rclone/fs/fserrors"
|
|
|
|
"github.com/ncw/rclone/fs/fspath"
|
|
|
|
fslog "github.com/ncw/rclone/fs/log"
|
2018-03-05 12:44:16 +01:00
|
|
|
"github.com/ncw/rclone/fs/rc"
|
|
|
|
"github.com/ncw/rclone/fs/rc/rcflags"
|
2018-01-25 11:10:21 +01:00
|
|
|
"github.com/ncw/rclone/lib/atexit"
|
2016-08-04 23:18:56 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Globals
|
|
|
|
var (
|
|
|
|
// Flags
|
2018-06-08 17:12:24 +02:00
|
|
|
cpuProfile = flags.StringP("cpuprofile", "", "", "Write cpu profile to file")
|
|
|
|
memProfile = flags.StringP("memprofile", "", "", "Write memory profile to file")
|
|
|
|
statsInterval = flags.DurationP("stats", "", time.Minute*1, "Interval between printing stats, e.g 500ms, 60s, 5m. (0 to disable)")
|
|
|
|
dataRateUnit = flags.StringP("stats-unit", "", "bytes", "Show data rate in stats as either 'bits' or 'bytes'/s")
|
|
|
|
version bool
|
|
|
|
retries = flags.IntP("retries", "", 3, "Retry operations this many times if they fail")
|
|
|
|
retriesInterval = flags.DurationP("retries-sleep", "", 0, "Interval between retrying operations if they fail, e.g 500ms, 60s, 5m. (0 to disable)")
|
2017-11-15 06:32:00 +01:00
|
|
|
// Errors
|
|
|
|
errorCommandNotFound = errors.New("command not found")
|
|
|
|
errorUncategorized = errors.New("uncategorized error")
|
|
|
|
errorNotEnoughArguments = errors.New("not enough arguments")
|
|
|
|
errorTooManyArguents = errors.New("too many arguments")
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
exitCodeSuccess = iota
|
|
|
|
exitCodeUsageError
|
|
|
|
exitCodeUncategorizedError
|
|
|
|
exitCodeDirNotFound
|
|
|
|
exitCodeFileNotFound
|
|
|
|
exitCodeRetryError
|
|
|
|
exitCodeNoRetryError
|
|
|
|
exitCodeFatalError
|
2018-05-03 16:04:30 +02:00
|
|
|
exitCodeTransferExceeded
|
2016-08-04 23:18:56 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Root is the main rclone command
|
|
|
|
var Root = &cobra.Command{
|
|
|
|
Use: "rclone",
|
|
|
|
Short: "Sync files and directories to and from local and remote object stores - " + fs.Version,
|
|
|
|
Long: `
|
|
|
|
Rclone is a command line program to sync files and directories to and
|
2017-05-25 00:24:46 +02:00
|
|
|
from various cloud storage systems and using file transfer services, such as:
|
2016-08-04 23:18:56 +02:00
|
|
|
|
2017-07-09 00:31:58 +02:00
|
|
|
* Amazon Drive
|
2016-08-04 23:18:56 +02:00
|
|
|
* Amazon S3
|
2017-07-09 00:31:58 +02:00
|
|
|
* Backblaze B2
|
|
|
|
* Box
|
2016-08-04 23:18:56 +02:00
|
|
|
* Dropbox
|
2017-07-09 00:31:58 +02:00
|
|
|
* FTP
|
2016-08-04 23:18:56 +02:00
|
|
|
* Google Cloud Storage
|
2017-07-09 00:31:58 +02:00
|
|
|
* Google Drive
|
|
|
|
* HTTP
|
2016-08-04 23:18:56 +02:00
|
|
|
* Hubic
|
2018-08-07 20:51:12 +02:00
|
|
|
* Jottacloud
|
2017-10-15 12:45:39 +02:00
|
|
|
* Mega
|
2017-07-25 16:18:13 +02:00
|
|
|
* Microsoft Azure Blob Storage
|
2017-07-09 00:31:58 +02:00
|
|
|
* Microsoft OneDrive
|
2017-07-17 07:36:45 +02:00
|
|
|
* OpenDrive
|
2017-07-09 00:31:58 +02:00
|
|
|
* Openstack Swift / Rackspace cloud files / Memset Memstore
|
2017-09-19 17:09:43 +02:00
|
|
|
* pCloud
|
2017-06-26 01:47:54 +02:00
|
|
|
* QingStor
|
2017-05-25 00:24:46 +02:00
|
|
|
* SFTP
|
2017-10-02 21:29:23 +02:00
|
|
|
* Webdav / Owncloud / Nextcloud
|
2017-07-09 00:31:58 +02:00
|
|
|
* Yandex Disk
|
2016-08-04 23:18:56 +02:00
|
|
|
* The local filesystem
|
|
|
|
|
|
|
|
Features
|
|
|
|
|
|
|
|
* MD5/SHA1 hashes checked at all times for file integrity
|
|
|
|
* Timestamps preserved on files
|
|
|
|
* Partial syncs supported on a whole file basis
|
|
|
|
* Copy mode to just copy new/changed files
|
|
|
|
* Sync (one way) mode to make a directory identical
|
|
|
|
* Check mode to check for file hash equality
|
|
|
|
* Can sync to and from network, eg two different cloud accounts
|
|
|
|
|
|
|
|
See the home page for installation, usage, documentation, changelog
|
|
|
|
and configuration walkthroughs.
|
|
|
|
|
2017-03-29 14:38:34 +02:00
|
|
|
* https://rclone.org/
|
2016-08-04 23:18:56 +02:00
|
|
|
`,
|
2017-02-20 17:33:45 +01:00
|
|
|
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
2017-02-20 17:36:25 +01:00
|
|
|
fs.Debugf("rclone", "Version %q finishing with parameters %q", fs.Version, os.Args)
|
2018-01-25 11:10:21 +01:00
|
|
|
atexit.Run()
|
2017-02-20 17:33:45 +01:00
|
|
|
},
|
2016-08-05 18:12:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// runRoot implements the main rclone command with no subcommands
|
|
|
|
func runRoot(cmd *cobra.Command, args []string) {
|
|
|
|
if version {
|
|
|
|
ShowVersion()
|
2017-11-15 06:32:00 +01:00
|
|
|
resolveExitCode(nil)
|
2016-08-05 18:12:27 +02:00
|
|
|
} else {
|
|
|
|
_ = Root.Usage()
|
2018-05-22 10:41:13 +02:00
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "Command not found.\n")
|
2017-11-15 06:32:00 +01:00
|
|
|
resolveExitCode(errorCommandNotFound)
|
2016-08-05 18:12:27 +02:00
|
|
|
}
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
2018-01-12 17:30:54 +01:00
|
|
|
// Add global flags
|
|
|
|
configflags.AddFlags(pflag.CommandLine)
|
|
|
|
filterflags.AddFlags(pflag.CommandLine)
|
2018-03-05 12:44:16 +01:00
|
|
|
rcflags.AddFlags(pflag.CommandLine)
|
2018-01-12 17:30:54 +01:00
|
|
|
|
2016-08-05 18:12:27 +02:00
|
|
|
Root.Run = runRoot
|
2016-08-04 23:18:56 +02:00
|
|
|
Root.Flags().BoolVarP(&version, "version", "V", false, "Print the version number")
|
|
|
|
cobra.OnInitialize(initConfig)
|
|
|
|
}
|
|
|
|
|
2016-08-05 18:12:27 +02:00
|
|
|
// ShowVersion prints the version to stdout
|
|
|
|
func ShowVersion() {
|
2016-08-04 23:18:56 +02:00
|
|
|
fmt.Printf("rclone %s\n", fs.Version)
|
2017-08-04 15:25:20 +02:00
|
|
|
fmt.Printf("- os/arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
|
|
|
fmt.Printf("- go version: %s\n", runtime.Version())
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 18:58:16 +02:00
|
|
|
// NewFsFile creates a Fs from a name but may point to a file.
|
2016-08-04 23:18:56 +02:00
|
|
|
//
|
2016-10-23 18:34:17 +02:00
|
|
|
// It returns a string with the file name if points to a file
|
2018-05-07 18:58:16 +02:00
|
|
|
// otherwise "".
|
2018-03-29 09:10:19 +02:00
|
|
|
func NewFsFile(remote string) (fs.Fs, string) {
|
2018-05-14 19:06:57 +02:00
|
|
|
_, _, fsPath, err := fs.ParseRemote(remote)
|
2016-08-04 23:18:56 +02:00
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
log.Fatalf("Failed to create file system for %q: %v", remote, err)
|
|
|
|
}
|
2018-05-14 19:06:57 +02:00
|
|
|
f, err := fs.NewFs(remote)
|
2016-10-23 18:34:17 +02:00
|
|
|
switch err {
|
|
|
|
case fs.ErrorIsFile:
|
|
|
|
return f, path.Base(fsPath)
|
|
|
|
case nil:
|
|
|
|
return f, ""
|
|
|
|
default:
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-10-23 18:34:17 +02:00
|
|
|
log.Fatalf("Failed to create file system for %q: %v", remote, err)
|
|
|
|
}
|
|
|
|
return nil, ""
|
|
|
|
}
|
|
|
|
|
2018-05-07 18:58:16 +02:00
|
|
|
// newFsFileAddFilter creates a src Fs from a name
|
2016-10-23 18:34:17 +02:00
|
|
|
//
|
2018-05-07 18:58:16 +02:00
|
|
|
// This works the same as NewFsFile however it adds filters to the Fs
|
|
|
|
// to limit it to a single file if the remote pointed to a file.
|
|
|
|
func newFsFileAddFilter(remote string) (fs.Fs, string) {
|
2018-03-29 09:10:19 +02:00
|
|
|
f, fileName := NewFsFile(remote)
|
2016-10-23 18:34:17 +02:00
|
|
|
if fileName != "" {
|
2018-01-12 17:30:54 +01:00
|
|
|
if !filter.Active.InActive() {
|
2017-11-15 06:32:00 +01:00
|
|
|
err := errors.Errorf("Can't limit to single files when using filters: %v", remote)
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2017-11-15 06:32:00 +01:00
|
|
|
log.Fatalf(err.Error())
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
// Limit transfers to this file
|
2018-01-12 17:30:54 +01:00
|
|
|
err := filter.Active.AddFile(fileName)
|
2016-10-23 18:34:17 +02:00
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-10-23 18:34:17 +02:00
|
|
|
log.Fatalf("Failed to limit to single file %q: %v", remote, err)
|
|
|
|
}
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
2016-10-23 18:34:17 +02:00
|
|
|
return f, fileName
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 18:58:16 +02:00
|
|
|
// NewFsSrc creates a new src fs from the arguments.
|
|
|
|
//
|
|
|
|
// The source can be a file or a directory - if a file then it will
|
|
|
|
// limit the Fs to a single file.
|
|
|
|
func NewFsSrc(args []string) fs.Fs {
|
|
|
|
fsrc, _ := newFsFileAddFilter(args[0])
|
|
|
|
return fsrc
|
|
|
|
}
|
|
|
|
|
|
|
|
// newFsDir creates an Fs from a name
|
2016-08-04 23:18:56 +02:00
|
|
|
//
|
|
|
|
// This must point to a directory
|
2018-05-07 18:58:16 +02:00
|
|
|
func newFsDir(remote string) fs.Fs {
|
2016-08-04 23:18:56 +02:00
|
|
|
f, err := fs.NewFs(remote)
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
log.Fatalf("Failed to create file system for %q: %v", remote, err)
|
|
|
|
}
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
2018-05-07 18:58:16 +02:00
|
|
|
// NewFsDir creates a new Fs from the arguments
|
|
|
|
//
|
|
|
|
// The argument must point a directory
|
|
|
|
func NewFsDir(args []string) fs.Fs {
|
|
|
|
fdst := newFsDir(args[0])
|
|
|
|
return fdst
|
|
|
|
}
|
|
|
|
|
2016-08-04 23:18:56 +02:00
|
|
|
// NewFsSrcDst creates a new src and dst fs from the arguments
|
|
|
|
func NewFsSrcDst(args []string) (fs.Fs, fs.Fs) {
|
2018-05-07 18:58:16 +02:00
|
|
|
fsrc, _ := newFsFileAddFilter(args[0])
|
|
|
|
fdst := newFsDir(args[1])
|
2016-08-06 01:07:36 +02:00
|
|
|
return fsrc, fdst
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 19:13:17 +02:00
|
|
|
// NewFsSrcFileDst creates a new src and dst fs from the arguments
|
|
|
|
//
|
|
|
|
// The source may be a file, in which case the source Fs and file name is returned
|
|
|
|
func NewFsSrcFileDst(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs) {
|
|
|
|
fsrc, srcFileName = NewFsFile(args[0])
|
|
|
|
fdst = newFsDir(args[1])
|
|
|
|
return fsrc, srcFileName, fdst
|
|
|
|
}
|
|
|
|
|
2016-10-23 18:34:17 +02:00
|
|
|
// NewFsSrcDstFiles creates a new src and dst fs from the arguments
|
|
|
|
// If src is a file then srcFileName and dstFileName will be non-empty
|
|
|
|
func NewFsSrcDstFiles(args []string) (fsrc fs.Fs, srcFileName string, fdst fs.Fs, dstFileName string) {
|
2018-05-07 18:58:16 +02:00
|
|
|
fsrc, srcFileName = newFsFileAddFilter(args[0])
|
2016-10-23 18:34:17 +02:00
|
|
|
// If copying a file...
|
|
|
|
dstRemote := args[1]
|
2017-12-15 12:37:31 +01:00
|
|
|
// If file exists then srcFileName != "", however if the file
|
|
|
|
// doesn't exist then we assume it is a directory...
|
2016-10-23 18:34:17 +02:00
|
|
|
if srcFileName != "" {
|
2018-06-18 11:39:00 +02:00
|
|
|
dstRemote, dstFileName = fspath.Split(dstRemote)
|
2016-10-23 18:34:17 +02:00
|
|
|
if dstRemote == "" {
|
2017-02-22 22:24:04 +01:00
|
|
|
dstRemote = "."
|
|
|
|
}
|
|
|
|
if dstFileName == "" {
|
|
|
|
log.Fatalf("%q is a directory", args[1])
|
2016-10-23 18:34:17 +02:00
|
|
|
}
|
|
|
|
}
|
2017-12-15 12:37:31 +01:00
|
|
|
fdst, err := fs.NewFs(dstRemote)
|
|
|
|
switch err {
|
|
|
|
case fs.ErrorIsFile:
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2017-12-15 12:37:31 +01:00
|
|
|
log.Fatalf("Source doesn't exist or is a directory and destination is a file")
|
|
|
|
case nil:
|
|
|
|
default:
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2017-12-15 12:37:31 +01:00
|
|
|
log.Fatalf("Failed to create file system for destination %q: %v", dstRemote, err)
|
|
|
|
}
|
2016-10-23 18:34:17 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-03 21:42:35 +02:00
|
|
|
// NewFsDstFile creates a new dst fs with a destination file name from the arguments
|
|
|
|
func NewFsDstFile(args []string) (fdst fs.Fs, dstFileName string) {
|
2018-06-18 11:39:00 +02:00
|
|
|
dstRemote, dstFileName := fspath.Split(args[0])
|
2017-08-03 21:42:35 +02:00
|
|
|
if dstRemote == "" {
|
|
|
|
dstRemote = "."
|
|
|
|
}
|
|
|
|
if dstFileName == "" {
|
|
|
|
log.Fatalf("%q is a directory", args[0])
|
|
|
|
}
|
2018-05-07 18:58:16 +02:00
|
|
|
fdst = newFsDir(dstRemote)
|
2017-08-03 21:42:35 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-15 18:40:17 +01:00
|
|
|
// ShowStats returns true if the user added a `--stats` flag to the command line.
|
|
|
|
//
|
|
|
|
// This is called by Run to override the default value of the
|
|
|
|
// showStats passed in.
|
|
|
|
func ShowStats() bool {
|
|
|
|
statsIntervalFlag := pflag.Lookup("stats")
|
|
|
|
return statsIntervalFlag != nil && statsIntervalFlag.Changed
|
|
|
|
}
|
|
|
|
|
2016-08-04 23:18:56 +02:00
|
|
|
// Run the function with stats and retries if required
|
2016-12-04 17:52:24 +01:00
|
|
|
func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
|
2016-08-04 23:18:56 +02:00
|
|
|
var err error
|
2016-12-04 17:52:24 +01:00
|
|
|
var stopStats chan struct{}
|
2016-12-15 18:40:17 +01:00
|
|
|
if !showStats && ShowStats() {
|
|
|
|
showStats = true
|
|
|
|
}
|
2018-06-30 17:05:31 +02:00
|
|
|
if fs.Config.Progress {
|
|
|
|
stopStats = startProgress()
|
|
|
|
} else if showStats {
|
2016-12-01 09:49:47 +01:00
|
|
|
stopStats = StartStats()
|
2016-12-04 17:52:24 +01:00
|
|
|
}
|
2018-05-13 20:24:56 +02:00
|
|
|
SigInfoHandler()
|
2016-08-04 23:18:56 +02:00
|
|
|
for try := 1; try <= *retries; try++ {
|
|
|
|
err = f()
|
2018-01-12 17:30:54 +01:00
|
|
|
if !Retry || (err == nil && !accounting.Stats.Errored()) {
|
2016-09-12 16:42:57 +02:00
|
|
|
if try > 1 {
|
2017-02-09 12:01:20 +01:00
|
|
|
fs.Errorf(nil, "Attempt %d/%d succeeded", try, *retries)
|
2016-09-12 16:42:57 +02:00
|
|
|
}
|
2016-08-04 23:18:56 +02:00
|
|
|
break
|
|
|
|
}
|
2018-01-12 17:30:54 +01:00
|
|
|
if fserrors.IsFatalError(err) {
|
2017-02-09 12:01:20 +01:00
|
|
|
fs.Errorf(nil, "Fatal error received - not attempting retries")
|
2016-08-04 23:18:56 +02:00
|
|
|
break
|
|
|
|
}
|
2018-01-12 17:30:54 +01:00
|
|
|
if fserrors.IsNoRetryError(err) {
|
2017-02-09 12:01:20 +01:00
|
|
|
fs.Errorf(nil, "Can't retry this error - not attempting retries")
|
2016-08-04 23:18:56 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.Errorf(nil, "Attempt %d/%d failed with %d errors and: %v", try, *retries, accounting.Stats.GetErrors(), err)
|
2016-08-04 23:18:56 +02:00
|
|
|
} else {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.Errorf(nil, "Attempt %d/%d failed with %d errors", try, *retries, accounting.Stats.GetErrors())
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
if try < *retries {
|
2018-01-12 17:30:54 +01:00
|
|
|
accounting.Stats.ResetErrors()
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
2018-06-08 17:12:24 +02:00
|
|
|
if *retriesInterval > 0 {
|
|
|
|
time.Sleep(*retriesInterval)
|
|
|
|
}
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
2016-12-04 17:52:24 +01:00
|
|
|
if showStats {
|
|
|
|
close(stopStats)
|
|
|
|
}
|
2016-08-04 23:18:56 +02:00
|
|
|
if err != nil {
|
2017-11-15 06:32:00 +01:00
|
|
|
log.Printf("Failed to %s: %v", cmd.Name(), err)
|
|
|
|
resolveExitCode(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
2018-01-12 17:30:54 +01:00
|
|
|
if showStats && (accounting.Stats.Errored() || *statsInterval > 0) {
|
|
|
|
accounting.Stats.Log()
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
2018-03-09 18:15:48 +01:00
|
|
|
fs.Debugf(nil, "%d go routines active\n", runtime.NumGoroutine())
|
2018-04-11 15:16:55 +02:00
|
|
|
|
|
|
|
// dump all running go-routines
|
|
|
|
if fs.Config.Dump&fs.DumpGoRoutines != 0 {
|
|
|
|
err = pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(nil, "Failed to dump goroutines: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// dump open files
|
|
|
|
if fs.Config.Dump&fs.DumpOpenFiles != 0 {
|
|
|
|
c := exec.Command("lsof", "-p", strconv.Itoa(os.Getpid()))
|
|
|
|
c.Stdout = os.Stdout
|
|
|
|
c.Stderr = os.Stderr
|
|
|
|
err = c.Run()
|
|
|
|
if err != nil {
|
|
|
|
fs.Errorf(nil, "Failed to list open files: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-12 17:30:54 +01:00
|
|
|
if accounting.Stats.Errored() {
|
|
|
|
resolveExitCode(accounting.Stats.GetLastError())
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CheckArgs checks there are enough arguments and prints a message if not
|
|
|
|
func CheckArgs(MinArgs, MaxArgs int, cmd *cobra.Command, args []string) {
|
|
|
|
if len(args) < MinArgs {
|
|
|
|
_ = cmd.Usage()
|
2018-05-22 10:41:13 +02:00
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "Command %s needs %d arguments minimum\n", cmd.Name(), MinArgs)
|
2017-11-15 06:32:00 +01:00
|
|
|
// os.Exit(1)
|
|
|
|
resolveExitCode(errorNotEnoughArguments)
|
2016-08-04 23:18:56 +02:00
|
|
|
} else if len(args) > MaxArgs {
|
|
|
|
_ = cmd.Usage()
|
2018-05-22 10:41:13 +02:00
|
|
|
_, _ = fmt.Fprintf(os.Stderr, "Command %s needs %d arguments maximum\n", cmd.Name(), MaxArgs)
|
2017-11-15 06:32:00 +01:00
|
|
|
// os.Exit(1)
|
|
|
|
resolveExitCode(errorTooManyArguents)
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-01 09:49:47 +01:00
|
|
|
// StartStats prints the stats every statsInterval
|
2016-08-04 23:18:56 +02:00
|
|
|
//
|
|
|
|
// It returns a channel which should be closed to stop the stats.
|
2016-12-01 09:49:47 +01:00
|
|
|
func StartStats() chan struct{} {
|
2016-08-04 23:18:56 +02:00
|
|
|
stopStats := make(chan struct{})
|
|
|
|
if *statsInterval > 0 {
|
|
|
|
go func() {
|
|
|
|
ticker := time.NewTicker(*statsInterval)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2018-01-12 17:30:54 +01:00
|
|
|
accounting.Stats.Log()
|
2016-08-04 23:18:56 +02:00
|
|
|
case <-stopStats:
|
|
|
|
ticker.Stop()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
return stopStats
|
|
|
|
}
|
|
|
|
|
|
|
|
// initConfig is run by cobra after initialising the flags
|
|
|
|
func initConfig() {
|
2017-02-10 14:28:06 +01:00
|
|
|
// Start the logger
|
2018-01-12 17:30:54 +01:00
|
|
|
fslog.InitLogging()
|
|
|
|
|
|
|
|
// Finish parsing any command line flags
|
|
|
|
configflags.SetFlags()
|
2016-08-04 23:18:56 +02:00
|
|
|
|
2018-01-12 17:30:54 +01:00
|
|
|
// Load filters
|
|
|
|
var err error
|
|
|
|
filter.Active, err = filter.NewFilter(&filterflags.Opt)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to load filters: %v", err)
|
|
|
|
}
|
2016-08-04 23:18:56 +02:00
|
|
|
|
|
|
|
// Write the args for debug purposes
|
2017-02-09 12:01:20 +01:00
|
|
|
fs.Debugf("rclone", "Version %q starting with parameters %q", fs.Version, os.Args)
|
2016-08-04 23:18:56 +02:00
|
|
|
|
2018-03-05 12:44:16 +01:00
|
|
|
// Start the remote control if configured
|
|
|
|
rc.Start(&rcflags.Opt)
|
|
|
|
|
2016-08-04 23:18:56 +02:00
|
|
|
// Setup CPU profiling if desired
|
|
|
|
if *cpuProfile != "" {
|
2017-02-09 18:08:51 +01:00
|
|
|
fs.Infof(nil, "Creating CPU profile %q\n", *cpuProfile)
|
2016-08-04 23:18:56 +02:00
|
|
|
f, err := os.Create(*cpuProfile)
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
err = pprof.StartCPUProfile(f)
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2018-01-25 11:10:21 +01:00
|
|
|
atexit.Register(func() {
|
2017-02-20 17:33:45 +01:00
|
|
|
pprof.StopCPUProfile()
|
|
|
|
})
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Setup memory profiling if desired
|
|
|
|
if *memProfile != "" {
|
2018-01-25 11:10:21 +01:00
|
|
|
atexit.Register(func() {
|
2017-02-09 18:08:51 +01:00
|
|
|
fs.Infof(nil, "Saving Memory profile %q\n", *memProfile)
|
2016-08-04 23:18:56 +02:00
|
|
|
f, err := os.Create(*memProfile)
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
err = pprof.WriteHeapProfile(f)
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
err = f.Close()
|
|
|
|
if err != nil {
|
2018-01-12 17:30:54 +01:00
|
|
|
fs.CountError(err)
|
2016-08-04 23:18:56 +02:00
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2017-02-20 17:33:45 +01:00
|
|
|
})
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
2016-11-22 05:04:05 +01:00
|
|
|
|
|
|
|
if m, _ := regexp.MatchString("^(bits|bytes)$", *dataRateUnit); m == false {
|
2017-02-09 12:01:20 +01:00
|
|
|
fs.Errorf(nil, "Invalid unit passed to --stats-unit. Defaulting to bytes.")
|
2016-11-22 05:04:05 +01:00
|
|
|
fs.Config.DataRateUnit = "bytes"
|
|
|
|
} else {
|
|
|
|
fs.Config.DataRateUnit = *dataRateUnit
|
|
|
|
}
|
2016-08-04 23:18:56 +02:00
|
|
|
}
|
2017-11-15 06:32:00 +01:00
|
|
|
|
|
|
|
func resolveExitCode(err error) {
|
2018-05-12 11:40:44 +02:00
|
|
|
atexit.Run()
|
2017-11-15 06:32:00 +01:00
|
|
|
if err == nil {
|
|
|
|
os.Exit(exitCodeSuccess)
|
|
|
|
}
|
|
|
|
|
2018-05-03 16:04:30 +02:00
|
|
|
_, unwrapped := fserrors.Cause(err)
|
2017-11-15 06:32:00 +01:00
|
|
|
|
|
|
|
switch {
|
2018-05-03 16:04:30 +02:00
|
|
|
case unwrapped == fs.ErrorDirNotFound:
|
2017-11-15 06:32:00 +01:00
|
|
|
os.Exit(exitCodeDirNotFound)
|
2018-05-03 16:04:30 +02:00
|
|
|
case unwrapped == fs.ErrorObjectNotFound:
|
2017-11-15 06:32:00 +01:00
|
|
|
os.Exit(exitCodeFileNotFound)
|
2018-05-03 16:04:30 +02:00
|
|
|
case unwrapped == errorUncategorized:
|
2017-11-15 06:32:00 +01:00
|
|
|
os.Exit(exitCodeUncategorizedError)
|
2018-05-03 16:04:30 +02:00
|
|
|
case unwrapped == accounting.ErrorMaxTransferLimitReached:
|
|
|
|
os.Exit(exitCodeTransferExceeded)
|
2018-01-12 17:30:54 +01:00
|
|
|
case fserrors.ShouldRetry(err):
|
2017-11-15 06:32:00 +01:00
|
|
|
os.Exit(exitCodeRetryError)
|
2018-01-12 17:30:54 +01:00
|
|
|
case fserrors.IsNoRetryError(err):
|
2017-11-15 06:32:00 +01:00
|
|
|
os.Exit(exitCodeNoRetryError)
|
2018-01-12 17:30:54 +01:00
|
|
|
case fserrors.IsFatalError(err):
|
2017-11-15 06:32:00 +01:00
|
|
|
os.Exit(exitCodeFatalError)
|
|
|
|
default:
|
|
|
|
os.Exit(exitCodeUsageError)
|
|
|
|
}
|
|
|
|
}
|
2018-05-14 19:06:57 +02:00
|
|
|
|
|
|
|
// AddBackendFlags creates flags for all the backend options
|
|
|
|
func AddBackendFlags() {
|
|
|
|
for _, fsInfo := range fs.Registry {
|
|
|
|
done := map[string]struct{}{}
|
|
|
|
for i := range fsInfo.Options {
|
|
|
|
opt := &fsInfo.Options[i]
|
|
|
|
// Skip if done already (eg with Provider options)
|
|
|
|
if _, doneAlready := done[opt.Name]; doneAlready {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
done[opt.Name] = struct{}{}
|
|
|
|
// Make a flag from each option
|
|
|
|
name := strings.Replace(opt.Name, "_", "-", -1) // convert snake_case to kebab-case
|
|
|
|
if !opt.NoPrefix {
|
|
|
|
name = fsInfo.Prefix + "-" + name
|
|
|
|
}
|
|
|
|
found := pflag.CommandLine.Lookup(name) != nil
|
|
|
|
if !found {
|
|
|
|
// Take first line of help only
|
|
|
|
help := strings.TrimSpace(opt.Help)
|
|
|
|
if nl := strings.IndexRune(help, '\n'); nl >= 0 {
|
|
|
|
help = help[:nl]
|
|
|
|
}
|
|
|
|
help = strings.TrimSpace(help)
|
|
|
|
flag := pflag.CommandLine.VarPF(opt, name, string(opt.ShortOpt), help)
|
2018-07-18 16:43:57 +02:00
|
|
|
if _, isBool := opt.Default.(bool); isBool {
|
2018-05-14 19:06:57 +02:00
|
|
|
flag.NoOptDefVal = "true"
|
|
|
|
}
|
|
|
|
// Hide on the command line if requested
|
|
|
|
if opt.Hide&fs.OptionHideCommandLine != 0 {
|
|
|
|
flag.Hidden = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fs.Errorf(nil, "Not adding duplicate flag --%s", name)
|
|
|
|
}
|
|
|
|
//flag.Hidden = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Main runs rclone interpreting flags and commands out of os.Args
|
|
|
|
func Main() {
|
|
|
|
AddBackendFlags()
|
|
|
|
if err := Root.Execute(); err != nil {
|
|
|
|
log.Fatalf("Fatal error: %v", err)
|
|
|
|
}
|
|
|
|
}
|