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