1package main 2 3import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "syscall" 13 "time" 14 15 "github.com/restic/restic/internal/backend" 16 "github.com/restic/restic/internal/backend/azure" 17 "github.com/restic/restic/internal/backend/b2" 18 "github.com/restic/restic/internal/backend/gs" 19 "github.com/restic/restic/internal/backend/local" 20 "github.com/restic/restic/internal/backend/location" 21 "github.com/restic/restic/internal/backend/rclone" 22 "github.com/restic/restic/internal/backend/rest" 23 "github.com/restic/restic/internal/backend/s3" 24 "github.com/restic/restic/internal/backend/sftp" 25 "github.com/restic/restic/internal/backend/swift" 26 "github.com/restic/restic/internal/cache" 27 "github.com/restic/restic/internal/debug" 28 "github.com/restic/restic/internal/fs" 29 "github.com/restic/restic/internal/limiter" 30 "github.com/restic/restic/internal/options" 31 "github.com/restic/restic/internal/repository" 32 "github.com/restic/restic/internal/restic" 33 "github.com/restic/restic/internal/textfile" 34 "github.com/restic/restic/internal/ui/termstatus" 35 36 "github.com/restic/restic/internal/errors" 37 38 "os/exec" 39 40 "golang.org/x/crypto/ssh/terminal" 41) 42 43var version = "0.12.1" 44 45// TimeFormat is the format used for all timestamps printed by restic. 46const TimeFormat = "2006-01-02 15:04:05" 47 48type backendWrapper func(r restic.Backend) (restic.Backend, error) 49 50// GlobalOptions hold all global options for restic. 51type GlobalOptions struct { 52 Repo string 53 RepositoryFile string 54 PasswordFile string 55 PasswordCommand string 56 KeyHint string 57 Quiet bool 58 Verbose int 59 NoLock bool 60 JSON bool 61 CacheDir string 62 NoCache bool 63 CACerts []string 64 TLSClientCert string 65 CleanupCache bool 66 67 LimitUploadKb int 68 LimitDownloadKb int 69 70 ctx context.Context 71 password string 72 stdout io.Writer 73 stderr io.Writer 74 75 backendTestHook, backendInnerTestHook backendWrapper 76 77 // verbosity is set as follows: 78 // 0 means: don't print any messages except errors, this is used when --quiet is specified 79 // 1 is the default: print essential messages 80 // 2 means: print more messages, report minor things, this is used when --verbose is specified 81 // 3 means: print very detailed debug messages, this is used when --verbose=2 is specified 82 verbosity uint 83 84 Options []string 85 86 extended options.Options 87} 88 89var globalOptions = GlobalOptions{ 90 stdout: os.Stdout, 91 stderr: os.Stderr, 92} 93 94var isReadingPassword bool 95 96func init() { 97 var cancel context.CancelFunc 98 globalOptions.ctx, cancel = context.WithCancel(context.Background()) 99 AddCleanupHandler(func() error { 100 cancel() 101 return nil 102 }) 103 104 f := cmdRoot.PersistentFlags() 105 f.StringVarP(&globalOptions.Repo, "repo", "r", os.Getenv("RESTIC_REPOSITORY"), "`repository` to backup to or restore from (default: $RESTIC_REPOSITORY)") 106 f.StringVarP(&globalOptions.RepositoryFile, "repository-file", "", os.Getenv("RESTIC_REPOSITORY_FILE"), "`file` to read the repository location from (default: $RESTIC_REPOSITORY_FILE)") 107 f.StringVarP(&globalOptions.PasswordFile, "password-file", "p", os.Getenv("RESTIC_PASSWORD_FILE"), "`file` to read the repository password from (default: $RESTIC_PASSWORD_FILE)") 108 f.StringVarP(&globalOptions.KeyHint, "key-hint", "", os.Getenv("RESTIC_KEY_HINT"), "`key` ID of key to try decrypting first (default: $RESTIC_KEY_HINT)") 109 f.StringVarP(&globalOptions.PasswordCommand, "password-command", "", os.Getenv("RESTIC_PASSWORD_COMMAND"), "shell `command` to obtain the repository password from (default: $RESTIC_PASSWORD_COMMAND)") 110 f.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "do not output comprehensive progress report") 111 f.CountVarP(&globalOptions.Verbose, "verbose", "v", "be verbose (specify multiple times or a level using --verbose=`n`, max level/times is 3)") 112 f.BoolVar(&globalOptions.NoLock, "no-lock", false, "do not lock the repository, this allows some operations on read-only repositories") 113 f.BoolVarP(&globalOptions.JSON, "json", "", false, "set output mode to JSON for commands that support it") 114 f.StringVar(&globalOptions.CacheDir, "cache-dir", "", "set the cache `directory`. (default: use system default cache directory)") 115 f.BoolVar(&globalOptions.NoCache, "no-cache", false, "do not use a local cache") 116 f.StringSliceVar(&globalOptions.CACerts, "cacert", nil, "`file` to load root certificates from (default: use system certificates)") 117 f.StringVar(&globalOptions.TLSClientCert, "tls-client-cert", "", "path to a `file` containing PEM encoded TLS client certificate and private key") 118 f.BoolVar(&globalOptions.CleanupCache, "cleanup-cache", false, "auto remove old cache directories") 119 f.IntVar(&globalOptions.LimitUploadKb, "limit-upload", 0, "limits uploads to a maximum rate in KiB/s. (default: unlimited)") 120 f.IntVar(&globalOptions.LimitDownloadKb, "limit-download", 0, "limits downloads to a maximum rate in KiB/s. (default: unlimited)") 121 f.StringSliceVarP(&globalOptions.Options, "option", "o", []string{}, "set extended option (`key=value`, can be specified multiple times)") 122 // Use our "generate" command instead of the cobra provided "completion" command 123 cmdRoot.CompletionOptions.DisableDefaultCmd = true 124 125 restoreTerminal() 126} 127 128// checkErrno returns nil when err is set to syscall.Errno(0), since this is no 129// error condition. 130func checkErrno(err error) error { 131 e, ok := err.(syscall.Errno) 132 if !ok { 133 return err 134 } 135 136 if e == 0 { 137 return nil 138 } 139 140 return err 141} 142 143func stdinIsTerminal() bool { 144 return terminal.IsTerminal(int(os.Stdin.Fd())) 145} 146 147func stdoutIsTerminal() bool { 148 // mintty on windows can use pipes which behave like a posix terminal, 149 // but which are not a terminal handle 150 return terminal.IsTerminal(int(os.Stdout.Fd())) || stdoutCanUpdateStatus() 151} 152 153func stdoutCanUpdateStatus() bool { 154 return termstatus.CanUpdateStatus(os.Stdout.Fd()) 155} 156 157func stdoutTerminalWidth() int { 158 w, _, err := terminal.GetSize(int(os.Stdout.Fd())) 159 if err != nil { 160 return 0 161 } 162 return w 163} 164 165// restoreTerminal installs a cleanup handler that restores the previous 166// terminal state on exit. This handler is only intended to restore the 167// terminal configuration if restic exits after receiving a signal. A regular 168// program execution must revert changes to the terminal configuration itself. 169// The terminal configuration is only restored while reading a password. 170func restoreTerminal() { 171 if !terminal.IsTerminal(int(os.Stdout.Fd())) { 172 return 173 } 174 175 fd := int(os.Stdout.Fd()) 176 state, err := terminal.GetState(fd) 177 if err != nil { 178 fmt.Fprintf(os.Stderr, "unable to get terminal state: %v\n", err) 179 return 180 } 181 182 AddCleanupHandler(func() error { 183 // Restoring the terminal configuration while restic runs in the 184 // background, causes restic to get stopped on unix systems with 185 // a SIGTTOU signal. Thus only restore the terminal settings if 186 // they might have been modified, which is the case while reading 187 // a password. 188 if !isReadingPassword { 189 return nil 190 } 191 err := checkErrno(terminal.Restore(fd, state)) 192 if err != nil { 193 fmt.Fprintf(os.Stderr, "unable to restore terminal state: %v\n", err) 194 } 195 return err 196 }) 197} 198 199// ClearLine creates a platform dependent string to clear the current 200// line, so it can be overwritten. ANSI sequences are not supported on 201// current windows cmd shell. 202func ClearLine() string { 203 if runtime.GOOS == "windows" { 204 if w := stdoutTerminalWidth(); w > 0 { 205 return strings.Repeat(" ", w-1) + "\r" 206 } 207 return "" 208 } 209 return "\x1b[2K" 210} 211 212// Printf writes the message to the configured stdout stream. 213func Printf(format string, args ...interface{}) { 214 _, err := fmt.Fprintf(globalOptions.stdout, format, args...) 215 if err != nil { 216 fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err) 217 } 218} 219 220// Print writes the message to the configured stdout stream. 221func Print(args ...interface{}) { 222 _, err := fmt.Fprint(globalOptions.stdout, args...) 223 if err != nil { 224 fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err) 225 } 226} 227 228// Println writes the message to the configured stdout stream. 229func Println(args ...interface{}) { 230 _, err := fmt.Fprintln(globalOptions.stdout, args...) 231 if err != nil { 232 fmt.Fprintf(os.Stderr, "unable to write to stdout: %v\n", err) 233 } 234} 235 236// Verbosef calls Printf to write the message when the verbose flag is set. 237func Verbosef(format string, args ...interface{}) { 238 if globalOptions.verbosity >= 1 { 239 Printf(format, args...) 240 } 241} 242 243// Verboseff calls Printf to write the message when the verbosity is >= 2 244func Verboseff(format string, args ...interface{}) { 245 if globalOptions.verbosity >= 2 { 246 Printf(format, args...) 247 } 248} 249 250// PrintProgress wraps fmt.Printf to handle the difference in writing progress 251// information to terminals and non-terminal stdout 252func PrintProgress(format string, args ...interface{}) { 253 var ( 254 message string 255 carriageControl string 256 ) 257 message = fmt.Sprintf(format, args...) 258 259 if !(strings.HasSuffix(message, "\r") || strings.HasSuffix(message, "\n")) { 260 if stdoutCanUpdateStatus() { 261 carriageControl = "\r" 262 } else { 263 carriageControl = "\n" 264 } 265 message = fmt.Sprintf("%s%s", message, carriageControl) 266 } 267 268 if stdoutCanUpdateStatus() { 269 message = fmt.Sprintf("%s%s", ClearLine(), message) 270 } 271 272 fmt.Print(message) 273} 274 275// Warnf writes the message to the configured stderr stream. 276func Warnf(format string, args ...interface{}) { 277 _, err := fmt.Fprintf(globalOptions.stderr, format, args...) 278 if err != nil { 279 fmt.Fprintf(os.Stderr, "unable to write to stderr: %v\n", err) 280 } 281} 282 283// Exitf uses Warnf to write the message and then terminates the process with 284// the given exit code. 285func Exitf(exitcode int, format string, args ...interface{}) { 286 if !(strings.HasSuffix(format, "\n")) { 287 format += "\n" 288 } 289 290 Warnf(format, args...) 291 Exit(exitcode) 292} 293 294// resolvePassword determines the password to be used for opening the repository. 295func resolvePassword(opts GlobalOptions, envStr string) (string, error) { 296 if opts.PasswordFile != "" && opts.PasswordCommand != "" { 297 return "", errors.Fatalf("Password file and command are mutually exclusive options") 298 } 299 if opts.PasswordCommand != "" { 300 args, err := backend.SplitShellStrings(opts.PasswordCommand) 301 if err != nil { 302 return "", err 303 } 304 cmd := exec.Command(args[0], args[1:]...) 305 cmd.Stderr = os.Stderr 306 output, err := cmd.Output() 307 if err != nil { 308 return "", err 309 } 310 return (strings.TrimSpace(string(output))), nil 311 } 312 if opts.PasswordFile != "" { 313 s, err := textfile.Read(opts.PasswordFile) 314 if os.IsNotExist(errors.Cause(err)) { 315 return "", errors.Fatalf("%s does not exist", opts.PasswordFile) 316 } 317 return strings.TrimSpace(string(s)), errors.Wrap(err, "Readfile") 318 } 319 320 if pwd := os.Getenv(envStr); pwd != "" { 321 return pwd, nil 322 } 323 324 return "", nil 325} 326 327// readPassword reads the password from the given reader directly. 328func readPassword(in io.Reader) (password string, err error) { 329 sc := bufio.NewScanner(in) 330 sc.Scan() 331 332 return sc.Text(), errors.Wrap(err, "Scan") 333} 334 335// readPasswordTerminal reads the password from the given reader which must be a 336// tty. Prompt is printed on the writer out before attempting to read the 337// password. 338func readPasswordTerminal(in *os.File, out io.Writer, prompt string) (password string, err error) { 339 fmt.Fprint(out, prompt) 340 isReadingPassword = true 341 buf, err := terminal.ReadPassword(int(in.Fd())) 342 isReadingPassword = false 343 fmt.Fprintln(out) 344 if err != nil { 345 return "", errors.Wrap(err, "ReadPassword") 346 } 347 348 password = string(buf) 349 return password, nil 350} 351 352// ReadPassword reads the password from a password file, the environment 353// variable RESTIC_PASSWORD or prompts the user. 354func ReadPassword(opts GlobalOptions, prompt string) (string, error) { 355 if opts.password != "" { 356 return opts.password, nil 357 } 358 359 var ( 360 password string 361 err error 362 ) 363 364 if stdinIsTerminal() { 365 password, err = readPasswordTerminal(os.Stdin, os.Stderr, prompt) 366 } else { 367 password, err = readPassword(os.Stdin) 368 Verbosef("reading repository password from stdin\n") 369 } 370 371 if err != nil { 372 return "", errors.Wrap(err, "unable to read password") 373 } 374 375 if len(password) == 0 { 376 return "", errors.Fatal("an empty password is not a password") 377 } 378 379 return password, nil 380} 381 382// ReadPasswordTwice calls ReadPassword two times and returns an error when the 383// passwords don't match. 384func ReadPasswordTwice(gopts GlobalOptions, prompt1, prompt2 string) (string, error) { 385 pw1, err := ReadPassword(gopts, prompt1) 386 if err != nil { 387 return "", err 388 } 389 if stdinIsTerminal() { 390 pw2, err := ReadPassword(gopts, prompt2) 391 if err != nil { 392 return "", err 393 } 394 395 if pw1 != pw2 { 396 return "", errors.Fatal("passwords do not match") 397 } 398 } 399 400 return pw1, nil 401} 402 403func ReadRepo(opts GlobalOptions) (string, error) { 404 if opts.Repo == "" && opts.RepositoryFile == "" { 405 return "", errors.Fatal("Please specify repository location (-r or --repository-file)") 406 } 407 408 repo := opts.Repo 409 if opts.RepositoryFile != "" { 410 if repo != "" { 411 return "", errors.Fatal("Options -r and --repository-file are mutually exclusive, please specify only one") 412 } 413 414 s, err := textfile.Read(opts.RepositoryFile) 415 if os.IsNotExist(errors.Cause(err)) { 416 return "", errors.Fatalf("%s does not exist", opts.RepositoryFile) 417 } 418 if err != nil { 419 return "", err 420 } 421 422 repo = strings.TrimSpace(string(s)) 423 } 424 425 return repo, nil 426} 427 428const maxKeys = 20 429 430// OpenRepository reads the password and opens the repository. 431func OpenRepository(opts GlobalOptions) (*repository.Repository, error) { 432 repo, err := ReadRepo(opts) 433 if err != nil { 434 return nil, err 435 } 436 437 be, err := open(repo, opts, opts.extended) 438 if err != nil { 439 return nil, err 440 } 441 442 be = backend.NewRetryBackend(be, 10, func(msg string, err error, d time.Duration) { 443 Warnf("%v returned error, retrying after %v: %v\n", msg, d, err) 444 }) 445 446 // wrap backend if a test specified a hook 447 if opts.backendTestHook != nil { 448 be, err = opts.backendTestHook(be) 449 if err != nil { 450 return nil, err 451 } 452 } 453 454 s := repository.New(be) 455 456 passwordTriesLeft := 1 457 if stdinIsTerminal() && opts.password == "" { 458 passwordTriesLeft = 3 459 } 460 461 for ; passwordTriesLeft > 0; passwordTriesLeft-- { 462 opts.password, err = ReadPassword(opts, "enter password for repository: ") 463 if err != nil && passwordTriesLeft > 1 { 464 opts.password = "" 465 fmt.Printf("%s. Try again\n", err) 466 } 467 if err != nil { 468 continue 469 } 470 471 err = s.SearchKey(opts.ctx, opts.password, maxKeys, opts.KeyHint) 472 if err != nil && passwordTriesLeft > 1 { 473 opts.password = "" 474 fmt.Printf("%s. Try again\n", err) 475 } 476 } 477 if err != nil { 478 if errors.IsFatal(err) { 479 return nil, err 480 } 481 return nil, errors.Fatalf("%s", err) 482 } 483 484 if stdoutIsTerminal() && !opts.JSON { 485 id := s.Config().ID 486 if len(id) > 8 { 487 id = id[:8] 488 } 489 if !opts.JSON { 490 Verbosef("repository %v opened successfully, password is correct\n", id) 491 } 492 } 493 494 if opts.NoCache { 495 return s, nil 496 } 497 498 c, err := cache.New(s.Config().ID, opts.CacheDir) 499 if err != nil { 500 Warnf("unable to open cache: %v\n", err) 501 return s, nil 502 } 503 504 if c.Created && !opts.JSON && stdoutIsTerminal() { 505 Verbosef("created new cache in %v\n", c.Base) 506 } 507 508 // start using the cache 509 s.UseCache(c) 510 511 oldCacheDirs, err := cache.Old(c.Base) 512 if err != nil { 513 Warnf("unable to find old cache directories: %v", err) 514 } 515 516 // nothing more to do if no old cache dirs could be found 517 if len(oldCacheDirs) == 0 { 518 return s, nil 519 } 520 521 // cleanup old cache dirs if instructed to do so 522 if opts.CleanupCache { 523 Printf("removing %d old cache dirs from %v\n", len(oldCacheDirs), c.Base) 524 525 for _, item := range oldCacheDirs { 526 dir := filepath.Join(c.Base, item.Name()) 527 err = fs.RemoveAll(dir) 528 if err != nil { 529 Warnf("unable to remove %v: %v\n", dir, err) 530 } 531 } 532 } else { 533 if stdoutIsTerminal() { 534 Verbosef("found %d old cache directories in %v, run `restic cache --cleanup` to remove them\n", 535 len(oldCacheDirs), c.Base) 536 } 537 } 538 539 return s, nil 540} 541 542func parseConfig(loc location.Location, opts options.Options) (interface{}, error) { 543 // only apply options for a particular backend here 544 opts = opts.Extract(loc.Scheme) 545 546 switch loc.Scheme { 547 case "local": 548 cfg := loc.Config.(local.Config) 549 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 550 return nil, err 551 } 552 553 debug.Log("opening local repository at %#v", cfg) 554 return cfg, nil 555 556 case "sftp": 557 cfg := loc.Config.(sftp.Config) 558 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 559 return nil, err 560 } 561 562 debug.Log("opening sftp repository at %#v", cfg) 563 return cfg, nil 564 565 case "s3": 566 cfg := loc.Config.(s3.Config) 567 if cfg.KeyID == "" { 568 cfg.KeyID = os.Getenv("AWS_ACCESS_KEY_ID") 569 } 570 571 if cfg.Secret == "" { 572 cfg.Secret = os.Getenv("AWS_SECRET_ACCESS_KEY") 573 } 574 575 if cfg.Region == "" { 576 cfg.Region = os.Getenv("AWS_DEFAULT_REGION") 577 } 578 579 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 580 return nil, err 581 } 582 583 debug.Log("opening s3 repository at %#v", cfg) 584 return cfg, nil 585 586 case "gs": 587 cfg := loc.Config.(gs.Config) 588 if cfg.ProjectID == "" { 589 cfg.ProjectID = os.Getenv("GOOGLE_PROJECT_ID") 590 } 591 592 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 593 return nil, err 594 } 595 596 debug.Log("opening gs repository at %#v", cfg) 597 return cfg, nil 598 599 case "azure": 600 cfg := loc.Config.(azure.Config) 601 if cfg.AccountName == "" { 602 cfg.AccountName = os.Getenv("AZURE_ACCOUNT_NAME") 603 } 604 605 if cfg.AccountKey == "" { 606 cfg.AccountKey = os.Getenv("AZURE_ACCOUNT_KEY") 607 } 608 609 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 610 return nil, err 611 } 612 613 debug.Log("opening gs repository at %#v", cfg) 614 return cfg, nil 615 616 case "swift": 617 cfg := loc.Config.(swift.Config) 618 619 if err := swift.ApplyEnvironment("", &cfg); err != nil { 620 return nil, err 621 } 622 623 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 624 return nil, err 625 } 626 627 debug.Log("opening swift repository at %#v", cfg) 628 return cfg, nil 629 630 case "b2": 631 cfg := loc.Config.(b2.Config) 632 633 if cfg.AccountID == "" { 634 cfg.AccountID = os.Getenv("B2_ACCOUNT_ID") 635 } 636 637 if cfg.AccountID == "" { 638 return nil, errors.Fatalf("unable to open B2 backend: Account ID ($B2_ACCOUNT_ID) is empty") 639 } 640 641 if cfg.Key == "" { 642 cfg.Key = os.Getenv("B2_ACCOUNT_KEY") 643 } 644 645 if cfg.Key == "" { 646 return nil, errors.Fatalf("unable to open B2 backend: Key ($B2_ACCOUNT_KEY) is empty") 647 } 648 649 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 650 return nil, err 651 } 652 653 debug.Log("opening b2 repository at %#v", cfg) 654 return cfg, nil 655 case "rest": 656 cfg := loc.Config.(rest.Config) 657 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 658 return nil, err 659 } 660 661 debug.Log("opening rest repository at %#v", cfg) 662 return cfg, nil 663 case "rclone": 664 cfg := loc.Config.(rclone.Config) 665 if err := opts.Apply(loc.Scheme, &cfg); err != nil { 666 return nil, err 667 } 668 669 debug.Log("opening rest repository at %#v", cfg) 670 return cfg, nil 671 } 672 673 return nil, errors.Fatalf("invalid backend: %q", loc.Scheme) 674} 675 676// Open the backend specified by a location config. 677func open(s string, gopts GlobalOptions, opts options.Options) (restic.Backend, error) { 678 debug.Log("parsing location %v", location.StripPassword(s)) 679 loc, err := location.Parse(s) 680 if err != nil { 681 return nil, errors.Fatalf("parsing repository location failed: %v", err) 682 } 683 684 var be restic.Backend 685 686 cfg, err := parseConfig(loc, opts) 687 if err != nil { 688 return nil, err 689 } 690 691 tropts := backend.TransportOptions{ 692 RootCertFilenames: globalOptions.CACerts, 693 TLSClientCertKeyFilename: globalOptions.TLSClientCert, 694 } 695 rt, err := backend.Transport(tropts) 696 if err != nil { 697 return nil, err 698 } 699 700 // wrap the transport so that the throughput via HTTP is limited 701 lim := limiter.NewStaticLimiter(gopts.LimitUploadKb, gopts.LimitDownloadKb) 702 rt = lim.Transport(rt) 703 704 switch loc.Scheme { 705 case "local": 706 be, err = local.Open(globalOptions.ctx, cfg.(local.Config)) 707 case "sftp": 708 be, err = sftp.Open(globalOptions.ctx, cfg.(sftp.Config)) 709 case "s3": 710 be, err = s3.Open(globalOptions.ctx, cfg.(s3.Config), rt) 711 case "gs": 712 be, err = gs.Open(cfg.(gs.Config), rt) 713 case "azure": 714 be, err = azure.Open(cfg.(azure.Config), rt) 715 case "swift": 716 be, err = swift.Open(cfg.(swift.Config), rt) 717 case "b2": 718 be, err = b2.Open(globalOptions.ctx, cfg.(b2.Config), rt) 719 case "rest": 720 be, err = rest.Open(cfg.(rest.Config), rt) 721 case "rclone": 722 be, err = rclone.Open(cfg.(rclone.Config), lim) 723 724 default: 725 return nil, errors.Fatalf("invalid backend: %q", loc.Scheme) 726 } 727 728 if err != nil { 729 return nil, errors.Fatalf("unable to open repo at %v: %v", location.StripPassword(s), err) 730 } 731 732 // wrap backend if a test specified an inner hook 733 if gopts.backendInnerTestHook != nil { 734 be, err = gopts.backendInnerTestHook(be) 735 if err != nil { 736 return nil, err 737 } 738 } 739 740 if loc.Scheme == "local" || loc.Scheme == "sftp" { 741 // wrap the backend in a LimitBackend so that the throughput is limited 742 be = limiter.LimitBackend(be, lim) 743 } 744 745 // check if config is there 746 fi, err := be.Stat(globalOptions.ctx, restic.Handle{Type: restic.ConfigFile}) 747 if err != nil { 748 return nil, errors.Fatalf("unable to open config file: %v\nIs there a repository at the following location?\n%v", err, location.StripPassword(s)) 749 } 750 751 if fi.Size == 0 { 752 return nil, errors.New("config file has zero size, invalid repository?") 753 } 754 755 return be, nil 756} 757 758// Create the backend specified by URI. 759func create(s string, opts options.Options) (restic.Backend, error) { 760 debug.Log("parsing location %v", s) 761 loc, err := location.Parse(s) 762 if err != nil { 763 return nil, err 764 } 765 766 cfg, err := parseConfig(loc, opts) 767 if err != nil { 768 return nil, err 769 } 770 771 tropts := backend.TransportOptions{ 772 RootCertFilenames: globalOptions.CACerts, 773 TLSClientCertKeyFilename: globalOptions.TLSClientCert, 774 } 775 rt, err := backend.Transport(tropts) 776 if err != nil { 777 return nil, err 778 } 779 780 switch loc.Scheme { 781 case "local": 782 return local.Create(globalOptions.ctx, cfg.(local.Config)) 783 case "sftp": 784 return sftp.Create(globalOptions.ctx, cfg.(sftp.Config)) 785 case "s3": 786 return s3.Create(globalOptions.ctx, cfg.(s3.Config), rt) 787 case "gs": 788 return gs.Create(cfg.(gs.Config), rt) 789 case "azure": 790 return azure.Create(cfg.(azure.Config), rt) 791 case "swift": 792 return swift.Open(cfg.(swift.Config), rt) 793 case "b2": 794 return b2.Create(globalOptions.ctx, cfg.(b2.Config), rt) 795 case "rest": 796 return rest.Create(globalOptions.ctx, cfg.(rest.Config), rt) 797 case "rclone": 798 return rclone.Create(globalOptions.ctx, cfg.(rclone.Config)) 799 } 800 801 debug.Log("invalid repository scheme: %v", s) 802 return nil, errors.Fatalf("invalid scheme %q", loc.Scheme) 803} 804