| 
									
										
										
										
											2015-03-28 11:50:23 +01:00
										 |  |  | package sftp | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2016-08-28 19:53:18 +02:00
										 |  |  | 	"bufio" | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-09-22 22:28:57 +02:00
										 |  |  | 	"crypto/rand" | 
					
						
							|  |  |  | 	"encoding/hex" | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-12-19 12:39:48 +01:00
										 |  |  | 	"hash" | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	"io" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"os/exec" | 
					
						
							| 
									
										
										
										
											2016-08-11 19:10:51 +02:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-23 14:21:03 +02:00
										 |  |  | 	"github.com/restic/restic/internal/errors" | 
					
						
							| 
									
										
										
										
											2017-07-24 17:42:25 +02:00
										 |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							| 
									
										
										
										
											2017-07-23 14:21:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/backend" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							| 
									
										
										
										
											2016-08-11 19:10:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-16 16:00:21 +01:00
										 |  |  | 	"github.com/cenkalti/backoff/v4" | 
					
						
							| 
									
										
										
										
											2016-08-11 19:10:51 +02:00
										 |  |  | 	"github.com/pkg/sftp" | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-24 20:23:50 +01:00
										 |  |  | // SFTP is a backend in a directory accessed via SFTP. | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | type SFTP struct { | 
					
						
							| 
									
										
										
										
											2015-05-03 16:43:27 +02:00
										 |  |  | 	c *sftp.Client | 
					
						
							|  |  |  | 	p string | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	cmd    *exec.Cmd | 
					
						
							|  |  |  | 	result <-chan error | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	sem *backend.Semaphore | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	backend.Layout | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:06 +02:00
										 |  |  | 	Config | 
					
						
							| 
									
										
										
										
											2022-04-26 19:15:09 +02:00
										 |  |  | 	backend.Modes | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-31 22:51:35 +02:00
										 |  |  | var _ restic.Backend = &SFTP{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | const defaultLayout = "default" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-17 23:14:47 +01:00
										 |  |  | func startClient(program string, args ...string) (*SFTP, error) { | 
					
						
							| 
									
										
										
										
											2017-04-03 21:05:42 +02:00
										 |  |  | 	debug.Log("start client %v %v", program, args) | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	// Connect to a remote host and request the sftp subsystem via the 'ssh' | 
					
						
							|  |  |  | 	// command.  This assumes that passwordless login is correctly configured. | 
					
						
							|  |  |  | 	cmd := exec.Command(program, args...) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:53:18 +02:00
										 |  |  | 	// prefix the errors with the program name | 
					
						
							|  |  |  | 	stderr, err := cmd.StderrPipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 		return nil, errors.Wrap(err, "cmd.StderrPipe") | 
					
						
							| 
									
										
										
										
											2016-08-28 19:53:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		sc := bufio.NewScanner(stderr) | 
					
						
							|  |  |  | 		for sc.Scan() { | 
					
						
							|  |  |  | 			fmt.Fprintf(os.Stderr, "subprocess %v: %v\n", program, sc.Text()) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// get stdin and stdout | 
					
						
							|  |  |  | 	wr, err := cmd.StdinPipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 		return nil, errors.Wrap(err, "cmd.StdinPipe") | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	rd, err := cmd.StdoutPipe() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 		return nil, errors.Wrap(err, "cmd.StdoutPipe") | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-13 21:09:16 +01:00
										 |  |  | 	bg, err := backend.StartForeground(cmd) | 
					
						
							| 
									
										
										
										
											2018-01-17 23:02:47 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 		return nil, errors.Wrap(err, "cmd.Start") | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	// wait in a different goroutine | 
					
						
							|  |  |  | 	ch := make(chan error, 1) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		err := cmd.Wait() | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 		debug.Log("ssh command exited, err %v", err) | 
					
						
							| 
									
										
											  
											
												sftp: persist "ssh command exited" error
If our ssh process has died, not only the next, but all subsequent
calls to clientError() should indicate the error.
restic output when the ssh process is killed with "kill -9":
  Save(<data/afb68adbf9>) returned error, retrying after 253.661803ms: Write: failed to send packet header: write |1: file already closed
  Save(<data/afb68adbf9>) returned error, retrying after 580.752212ms: ssh command exited: signal: killed
  Save(<data/afb68adbf9>) returned error, retrying after 790.150468ms: ssh command exited: signal: killed
  Save(<data/afb68adbf9>) returned error, retrying after 1.769595051s: ssh command exited: signal: killed
  [...]
  error in cleanup handler: ssh command exited: signal: killed
Before this patch:
  Save(<data/de698d934f>) returned error, retrying after 252.84163ms: Write: failed to send packet header: write |1: file already closed
  Save(<data/de698d934f>) returned error, retrying after 660.236963ms: OpenFile: failed to send packet header: write |1: file already closed
  Save(<data/de698d934f>) returned error, retrying after 568.049909ms: OpenFile: failed to send packet header: write |1: file already closed
  Save(<data/de698d934f>) returned error, retrying after 2.428813824s: OpenFile: failed to send packet header: write |1: file already closed
  [...]
  error in cleanup handler: failed to send packet header: write |1: file already closed
											
										 
											2018-05-30 19:28:14 +02:00
										 |  |  | 		for { | 
					
						
							|  |  |  | 			ch <- errors.Wrap(err, "ssh command exited") | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	// open the SFTP session | 
					
						
							|  |  |  | 	client, err := sftp.NewClientPipe(rd, wr) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-21 17:48:36 +02:00
										 |  |  | 		return nil, errors.Errorf("unable to start the sftp session, error: %v", err) | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-17 23:02:47 +01:00
										 |  |  | 	err = bg() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, errors.Wrap(err, "bg") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	return &SFTP{c: client, cmd: cmd, result: ch}, nil | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | // clientError returns an error if the client has exited. Otherwise, nil is | 
					
						
							|  |  |  | // returned immediately. | 
					
						
							|  |  |  | func (r *SFTP) clientError() error { | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case err := <-r.result: | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 		debug.Log("client has exited with err %v", err) | 
					
						
							| 
									
										
										
										
											2020-12-16 16:00:21 +01:00
										 |  |  | 		return backoff.Permanent(err) | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | // Open opens an sftp backend as described by the config by running | 
					
						
							| 
									
										
										
										
											2017-09-23 11:21:27 +02:00
										 |  |  | // "ssh" with the appropriate arguments (or cfg.Command, if set). The function | 
					
						
							|  |  |  | // preExec is run just before, postExec just after starting a program. | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | func Open(ctx context.Context, cfg Config) (*SFTP, error) { | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 	debug.Log("open backend with config %#v", cfg) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	sem, err := backend.NewSemaphore(cfg.Connections) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 	cmd, args, err := buildSSHCommand(cfg) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-17 23:14:47 +01:00
										 |  |  | 	sftp, err := startClient(cmd, args...) | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 		debug.Log("unable to start program: %v", err) | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | 	sftp.Layout, err = backend.ParseLayout(ctx, sftp, cfg.Layout, defaultLayout, cfg.Path) | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:17:50 +02:00
										 |  |  | 	debug.Log("layout: %v\n", sftp.Layout) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-26 19:15:09 +02:00
										 |  |  | 	fi, err := sftp.c.Stat(Join(cfg.Path, backend.Paths.Config)) | 
					
						
							|  |  |  | 	m := backend.DeriveModesFromFileInfo(fi, err) | 
					
						
							|  |  |  | 	debug.Log("using (%03O file, %03O dir) permissions", m.File, m.Dir) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 	sftp.Config = cfg | 
					
						
							|  |  |  | 	sftp.p = cfg.Path | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	sftp.sem = sem | 
					
						
							| 
									
										
										
										
											2022-04-26 19:15:09 +02:00
										 |  |  | 	sftp.Modes = m | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	return sftp, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-07-16 15:10:06 +02:00
										 |  |  | func (r *SFTP) mkdirAllDataSubdirs() error { | 
					
						
							|  |  |  | 	for _, d := range r.Paths() { | 
					
						
							| 
									
										
										
										
											2020-01-01 17:26:38 +01:00
										 |  |  | 		err := r.c.MkdirAll(d) | 
					
						
							| 
									
										
										
										
											2017-07-16 15:10:06 +02:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | // Join combines path components with slashes (according to the sftp spec). | 
					
						
							|  |  |  | func (r *SFTP) Join(p ...string) string { | 
					
						
							|  |  |  | 	return path.Join(p...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ReadDir returns the entries for a directory. | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | func (r *SFTP) ReadDir(ctx context.Context, dir string) ([]os.FileInfo, error) { | 
					
						
							| 
									
										
										
										
											2017-10-28 14:16:27 -04:00
										 |  |  | 	fi, err := r.c.ReadDir(dir) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// sftp client does not specify dir name on error, so add it here | 
					
						
							|  |  |  | 	err = errors.Wrapf(err, "(%v)", dir) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return fi, err | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:17:50 +02:00
										 |  |  | // IsNotExist returns true if the error is caused by a not existing file. | 
					
						
							|  |  |  | func (r *SFTP) IsNotExist(err error) bool { | 
					
						
							| 
									
										
										
										
											2018-01-05 17:51:09 +01:00
										 |  |  | 	err = errors.Cause(err) | 
					
						
							| 
									
										
										
										
											2021-03-10 20:38:22 +01:00
										 |  |  | 	return errors.Is(err, os.ErrNotExist) | 
					
						
							| 
									
										
										
										
											2017-04-10 22:17:50 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | func buildSSHCommand(cfg Config) (cmd string, args []string, err error) { | 
					
						
							|  |  |  | 	if cfg.Command != "" { | 
					
						
							| 
									
										
										
										
											2018-03-13 20:50:37 +01:00
										 |  |  | 		args, err := backend.SplitShellStrings(cfg.Command) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return "", nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return args[0], args[1:], nil | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cmd = "ssh" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-08 16:45:33 +01:00
										 |  |  | 	host, port := cfg.Host, cfg.Port | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	args = []string{host} | 
					
						
							|  |  |  | 	if port != "" { | 
					
						
							|  |  |  | 		args = append(args, "-p", port) | 
					
						
							| 
									
										
										
										
											2016-02-15 19:17:41 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-12-28 18:22:19 +01:00
										 |  |  | 	if cfg.User != "" { | 
					
						
							|  |  |  | 		args = append(args, "-l") | 
					
						
							|  |  |  | 		args = append(args, cfg.User) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	args = append(args, "-s") | 
					
						
							|  |  |  | 	args = append(args, "sftp") | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 	return cmd, args, nil | 
					
						
							| 
									
										
										
										
											2015-12-28 18:22:19 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-23 11:21:27 +02:00
										 |  |  | // Create creates an sftp backend as described by the config by running "ssh" | 
					
						
							|  |  |  | // with the appropriate arguments (or cfg.Command, if set). The function | 
					
						
							|  |  |  | // preExec is run just before, postExec just after starting a program. | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | func Create(ctx context.Context, cfg Config) (*SFTP, error) { | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 	cmd, args, err := buildSSHCommand(cfg) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2017-04-03 21:05:42 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-17 23:14:47 +01:00
										 |  |  | 	sftp, err := startClient(cmd, args...) | 
					
						
							| 
									
										
										
										
											2017-04-03 21:05:42 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 		debug.Log("unable to start program: %v", err) | 
					
						
							| 
									
										
										
										
											2017-04-03 21:05:42 +02:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | 	sftp.Layout, err = backend.ParseLayout(ctx, sftp, cfg.Layout, defaultLayout, cfg.Path) | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-26 19:15:09 +02:00
										 |  |  | 	sftp.Modes = backend.DefaultModes | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-04 20:39:45 +02:00
										 |  |  | 	// test if config file already exists | 
					
						
							| 
									
										
										
										
											2017-04-10 22:41:20 +02:00
										 |  |  | 	_, err = sftp.c.Lstat(Join(cfg.Path, backend.Paths.Config)) | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2015-05-03 16:43:27 +02:00
										 |  |  | 		return nil, errors.New("config file already exists") | 
					
						
							| 
									
										
										
										
											2015-03-14 11:56:45 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-19 18:56:01 +02:00
										 |  |  | 	// create paths for data and refs | 
					
						
							| 
									
										
										
										
											2017-07-16 15:10:06 +02:00
										 |  |  | 	if err = sftp.mkdirAllDataSubdirs(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	err = sftp.Close() | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 		return nil, errors.Wrap(err, "Close") | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-10-07 23:19:26 +02:00
										 |  |  | 	// open backend | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | 	return Open(ctx, cfg) | 
					
						
							| 
									
										
										
										
											2015-12-28 18:22:19 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | func (r *SFTP) Connections() uint { | 
					
						
							|  |  |  | 	return r.Config.Connections | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | // Location returns this backend's location (the directory name). | 
					
						
							|  |  |  | func (r *SFTP) Location() string { | 
					
						
							|  |  |  | 	return r.p | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-19 12:39:48 +01:00
										 |  |  | // Hasher may return a hash function for calculating a content hash for the backend | 
					
						
							|  |  |  | func (r *SFTP) Hasher() hash.Hash { | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-11 19:10:51 +02:00
										 |  |  | // Join joins the given paths and cleans them afterwards. This always uses | 
					
						
							|  |  |  | // forward slashes, which is required by sftp. | 
					
						
							| 
									
										
										
										
											2015-11-02 14:53:34 +01:00
										 |  |  | func Join(parts ...string) string { | 
					
						
							| 
									
										
										
										
											2016-08-11 19:10:51 +02:00
										 |  |  | 	return path.Clean(path.Join(parts...)) | 
					
						
							| 
									
										
										
										
											2015-11-02 14:53:34 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-22 22:28:57 +02:00
										 |  |  | // tempSuffix generates a random string suffix that should be sufficiently long | 
					
						
							|  |  |  | // to avoid accidential conflicts | 
					
						
							|  |  |  | func tempSuffix() string { | 
					
						
							|  |  |  | 	var nonce [16]byte | 
					
						
							|  |  |  | 	_, err := rand.Read(nonce[:]) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		panic(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return hex.EncodeToString(nonce[:]) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | // Save stores data in the backend at the handle. | 
					
						
							| 
									
										
										
										
											2018-03-03 14:20:54 +01:00
										 |  |  | func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error { | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	debug.Log("Save %v", h) | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	if err := r.clientError(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | 	if err := h.Valid(); err != nil { | 
					
						
							| 
									
										
										
										
											2020-12-17 12:47:53 +01:00
										 |  |  | 		return backoff.Permanent(err) | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	filename := r.Filename(h) | 
					
						
							| 
									
										
										
										
											2021-09-22 22:28:57 +02:00
										 |  |  | 	tmpFilename := filename + "-restic-temp-" + tempSuffix() | 
					
						
							| 
									
										
										
										
											2021-03-16 21:35:10 +01:00
										 |  |  | 	dirname := r.Dirname(h) | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	r.sem.GetToken() | 
					
						
							|  |  |  | 	defer r.sem.ReleaseToken() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-03 21:18:49 +02:00
										 |  |  | 	// create new file | 
					
						
							| 
									
										
										
										
											2021-09-22 22:28:57 +02:00
										 |  |  | 	f, err := r.c.OpenFile(tmpFilename, os.O_CREATE|os.O_EXCL|os.O_WRONLY) | 
					
						
							| 
									
										
										
										
											2017-05-03 21:18:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-05 17:51:09 +01:00
										 |  |  | 	if r.IsNotExist(err) { | 
					
						
							|  |  |  | 		// error is caused by a missing directory, try to create it | 
					
						
							| 
									
										
										
										
											2020-01-01 17:26:38 +01:00
										 |  |  | 		mkdirErr := r.c.MkdirAll(r.Dirname(h)) | 
					
						
							| 
									
										
										
										
											2018-01-05 17:51:09 +01:00
										 |  |  | 		if mkdirErr != nil { | 
					
						
							|  |  |  | 			debug.Log("error creating dir %v: %v", r.Dirname(h), mkdirErr) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// try again | 
					
						
							| 
									
										
										
										
											2021-09-22 22:28:57 +02:00
										 |  |  | 			f, err = r.c.OpenFile(tmpFilename, os.O_CREATE|os.O_EXCL|os.O_WRONLY) | 
					
						
							| 
									
										
										
										
											2018-01-05 17:51:09 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-16 21:35:10 +01:00
										 |  |  | 	// pkg/sftp doesn't allow creating with a mode. | 
					
						
							|  |  |  | 	// Chmod while the file is still empty. | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2022-04-26 19:15:09 +02:00
										 |  |  | 		err = f.Chmod(r.Modes.File) | 
					
						
							| 
									
										
										
										
											2021-03-16 21:35:10 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-10 22:17:50 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return errors.Wrap(err, "OpenFile") | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-16 21:35:10 +01:00
										 |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Try not to leave a partial file behind. | 
					
						
							|  |  |  | 		rmErr := r.c.Remove(f.Name()) | 
					
						
							|  |  |  | 		if rmErr != nil { | 
					
						
							|  |  |  | 			debug.Log("sftp: failed to remove broken file %v: %v", | 
					
						
							| 
									
										
										
										
											2021-09-22 22:28:57 +02:00
										 |  |  | 				f.Name(), rmErr) | 
					
						
							| 
									
										
										
										
											2021-03-16 21:35:10 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err = r.checkNoSpace(dirname, rd.Length(), err) | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-01 12:19:48 +01:00
										 |  |  | 	// save data, make sure to use the optimized sftp upload method | 
					
						
							| 
									
										
										
										
											2020-12-18 23:41:29 +01:00
										 |  |  | 	wbytes, err := f.ReadFrom(rd) | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2017-05-11 21:53:57 +02:00
										 |  |  | 		_ = f.Close() | 
					
						
							| 
									
										
										
										
											2017-04-10 22:17:50 +02:00
										 |  |  | 		return errors.Wrap(err, "Write") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-18 23:41:29 +01:00
										 |  |  | 	// sanity check | 
					
						
							|  |  |  | 	if wbytes != rd.Length() { | 
					
						
							|  |  |  | 		_ = f.Close() | 
					
						
							|  |  |  | 		return errors.Errorf("wrote %d bytes instead of the expected %d bytes", wbytes, rd.Length()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:17:50 +02:00
										 |  |  | 	err = f.Close() | 
					
						
							| 
									
										
										
										
											2021-09-22 22:28:57 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return errors.Wrap(err, "Close") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = r.c.Rename(tmpFilename, filename) | 
					
						
							|  |  |  | 	return errors.Wrap(err, "Rename") | 
					
						
							| 
									
										
										
										
											2021-03-16 21:35:10 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // checkNoSpace checks if err was likely caused by lack of available space | 
					
						
							|  |  |  | // on the remote, and if so, makes it permanent. | 
					
						
							|  |  |  | func (r *SFTP) checkNoSpace(dir string, size int64, origErr error) error { | 
					
						
							|  |  |  | 	// The SFTP protocol has a message for ENOSPC, | 
					
						
							|  |  |  | 	// but pkg/sftp doesn't export it and OpenSSH's sftp-server | 
					
						
							|  |  |  | 	// sends FX_FAILURE instead. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	e, ok := origErr.(*sftp.StatusError) | 
					
						
							|  |  |  | 	_, hasExt := r.c.HasExtension("statvfs@openssh.com") | 
					
						
							|  |  |  | 	if !ok || e.FxCode() != sftp.ErrSSHFxFailure || !hasExt { | 
					
						
							|  |  |  | 		return origErr | 
					
						
							| 
									
										
										
										
											2017-04-10 22:40:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-16 21:35:10 +01:00
										 |  |  | 	fsinfo, err := r.c.StatVFS(dir) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		debug.Log("sftp: StatVFS returned %v", err) | 
					
						
							|  |  |  | 		return origErr | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if fsinfo.Favail == 0 || fsinfo.FreeSpace() < uint64(size) { | 
					
						
							|  |  |  | 		err := errors.New("sftp: no space left on device") | 
					
						
							|  |  |  | 		return backoff.Permanent(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return origErr | 
					
						
							| 
									
										
										
										
											2016-01-24 01:15:35 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-16 23:59:16 -05:00
										 |  |  | // Load runs fn with a reader that yields the contents of the file at h at the | 
					
						
							|  |  |  | // given offset. | 
					
						
							|  |  |  | func (r *SFTP) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error { | 
					
						
							|  |  |  | 	return backend.DefaultLoad(ctx, h, length, offset, r.openReader, fn) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | // wrapReader wraps an io.ReadCloser to run an additional function on Close. | 
					
						
							|  |  |  | type wrapReader struct { | 
					
						
							|  |  |  | 	io.ReadCloser | 
					
						
							|  |  |  | 	io.WriterTo | 
					
						
							|  |  |  | 	f func() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (wr *wrapReader) Close() error { | 
					
						
							|  |  |  | 	err := wr.ReadCloser.Close() | 
					
						
							|  |  |  | 	wr.f() | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-16 23:59:16 -05:00
										 |  |  | func (r *SFTP) openReader(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { | 
					
						
							| 
									
										
										
										
											2017-01-23 18:11:10 +01:00
										 |  |  | 	debug.Log("Load %v, length %v, offset %v", h, length, offset) | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 	if err := h.Valid(); err != nil { | 
					
						
							| 
									
										
										
										
											2020-12-17 12:47:53 +01:00
										 |  |  | 		return nil, backoff.Permanent(err) | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if offset < 0 { | 
					
						
							|  |  |  | 		return nil, errors.New("offset is negative") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	r.sem.GetToken() | 
					
						
							| 
									
										
										
										
											2017-04-10 22:26:17 +02:00
										 |  |  | 	f, err := r.c.Open(r.Filename(h)) | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 		r.sem.ReleaseToken() | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if offset > 0 { | 
					
						
							|  |  |  | 		_, err = f.Seek(offset, 0) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 			r.sem.ReleaseToken() | 
					
						
							| 
									
										
										
										
											2017-01-23 16:20:07 +01:00
										 |  |  | 			_ = f.Close() | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	// use custom close wrapper to also provide WriteTo() on the wrapper | 
					
						
							|  |  |  | 	rd := &wrapReader{ | 
					
						
							|  |  |  | 		ReadCloser: f, | 
					
						
							|  |  |  | 		WriterTo:   f, | 
					
						
							|  |  |  | 		f: func() { | 
					
						
							|  |  |  | 			r.sem.ReleaseToken() | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 	if length > 0 { | 
					
						
							| 
									
										
										
										
											2021-01-01 12:19:48 +01:00
										 |  |  | 		// unlimited reads usually use io.Copy which needs WriteTo support at the underlying reader | 
					
						
							|  |  |  | 		// limited reads are usually combined with io.ReadFull which reads all required bytes into a buffer in one go | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 		return backend.LimitReadCloser(rd, int64(length)), nil | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	return rd, nil | 
					
						
							| 
									
										
										
										
											2017-01-22 22:01:12 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | // Stat returns information about a blob. | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | func (r *SFTP) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) { | 
					
						
							| 
									
										
										
										
											2017-01-25 17:48:35 +01:00
										 |  |  | 	debug.Log("Stat(%v)", h) | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	if err := r.clientError(); err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-31 22:51:35 +02:00
										 |  |  | 		return restic.FileInfo{}, err | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	if err := h.Valid(); err != nil { | 
					
						
							| 
									
										
										
										
											2020-12-17 12:47:53 +01:00
										 |  |  | 		return restic.FileInfo{}, backoff.Permanent(err) | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	r.sem.GetToken() | 
					
						
							|  |  |  | 	defer r.sem.ReleaseToken() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:26:17 +02:00
										 |  |  | 	fi, err := r.c.Lstat(r.Filename(h)) | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-31 22:51:35 +02:00
										 |  |  | 		return restic.FileInfo{}, errors.Wrap(err, "Lstat") | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	return restic.FileInfo{Size: fi.Size(), Name: h.Name}, nil | 
					
						
							| 
									
										
										
										
											2016-01-23 23:27:58 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-28 11:50:23 +01:00
										 |  |  | // Test returns true if a blob of the given type and name exists in the backend. | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | func (r *SFTP) Test(ctx context.Context, h restic.Handle) (bool, error) { | 
					
						
							| 
									
										
										
										
											2017-01-25 17:48:35 +01:00
										 |  |  | 	debug.Log("Test(%v)", h) | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	if err := r.clientError(); err != nil { | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	r.sem.GetToken() | 
					
						
							|  |  |  | 	defer r.sem.ReleaseToken() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:26:17 +02:00
										 |  |  | 	_, err := r.c.Lstat(r.Filename(h)) | 
					
						
							| 
									
										
										
										
											2016-08-29 19:18:57 +02:00
										 |  |  | 	if os.IsNotExist(errors.Cause(err)) { | 
					
						
							| 
									
										
										
										
											2016-02-13 19:11:41 +01:00
										 |  |  | 		return false, nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-11-30 15:50:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-13 19:11:41 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2016-08-29 21:54:50 +02:00
										 |  |  | 		return false, errors.Wrap(err, "Lstat") | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-11-30 15:50:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return true, nil | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-28 11:50:23 +01:00
										 |  |  | // Remove removes the content stored at name. | 
					
						
							| 
									
										
										
										
											2017-06-03 17:39:57 +02:00
										 |  |  | func (r *SFTP) Remove(ctx context.Context, h restic.Handle) error { | 
					
						
							| 
									
										
										
										
											2017-01-25 17:48:35 +01:00
										 |  |  | 	debug.Log("Remove(%v)", h) | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	if err := r.clientError(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	r.sem.GetToken() | 
					
						
							|  |  |  | 	defer r.sem.ReleaseToken() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-10 22:26:17 +02:00
										 |  |  | 	return r.c.Remove(r.Filename(h)) | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | // List runs fn for each file in the backend which has the type t. When an | 
					
						
							|  |  |  | // error occurs (or fn returns an error), List stops and returns it. | 
					
						
							|  |  |  | func (r *SFTP) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error { | 
					
						
							| 
									
										
										
										
											2017-04-10 23:31:13 +02:00
										 |  |  | 	debug.Log("List %v", t) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	basedir, subdirs := r.Basedir(t) | 
					
						
							|  |  |  | 	walker := r.c.Walk(basedir) | 
					
						
							| 
									
										
										
										
											2021-08-07 19:56:59 +02:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		r.sem.GetToken() | 
					
						
							|  |  |  | 		ok := walker.Step() | 
					
						
							|  |  |  | 		r.sem.ReleaseToken() | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 		if walker.Err() != nil { | 
					
						
							| 
									
										
										
										
											2018-04-10 21:35:30 +02:00
										 |  |  | 			if r.IsNotExist(walker.Err()) { | 
					
						
							|  |  |  | 				debug.Log("ignoring non-existing directory") | 
					
						
							|  |  |  | 				return nil | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 			return walker.Err() | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2014-11-30 15:50:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 		if walker.Path() == basedir { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if walker.Stat().IsDir() && !subdirs { | 
					
						
							|  |  |  | 			walker.SkipDir() | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fi := walker.Stat() | 
					
						
							|  |  |  | 		if !fi.Mode().IsRegular() { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		debug.Log("send %v\n", path.Base(walker.Path())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rfi := restic.FileInfo{ | 
					
						
							|  |  |  | 			Name: path.Base(walker.Path()), | 
					
						
							|  |  |  | 			Size: fi.Size(), | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ctx.Err() != nil { | 
					
						
							|  |  |  | 			return ctx.Err() | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-03-28 11:50:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 		err := fn(rfi) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ctx.Err() != nil { | 
					
						
							|  |  |  | 			return ctx.Err() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-01-20 19:34:38 +01:00
										 |  |  | 	return ctx.Err() | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | var closeTimeout = 2 * time.Second | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | // Close closes the sftp connection and terminates the underlying command. | 
					
						
							| 
									
										
										
										
											2016-01-24 20:23:50 +01:00
										 |  |  | func (r *SFTP) Close() error { | 
					
						
							| 
									
										
										
										
											2017-10-28 14:16:27 -04:00
										 |  |  | 	debug.Log("Close") | 
					
						
							| 
									
										
										
										
											2016-01-24 20:23:50 +01:00
										 |  |  | 	if r == nil { | 
					
						
							| 
									
										
										
										
											2015-05-01 17:13:03 +02:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-26 22:16:17 +01:00
										 |  |  | 	err := r.c.Close() | 
					
						
							| 
									
										
										
										
											2016-09-27 22:35:08 +02:00
										 |  |  | 	debug.Log("Close returned error %v", err) | 
					
						
							| 
									
										
										
										
											2015-07-05 11:06:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	// wait for closeTimeout before killing the process | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case err := <-r.result: | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	case <-time.After(closeTimeout): | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-24 20:23:50 +01:00
										 |  |  | 	if err := r.cmd.Process.Kill(); err != nil { | 
					
						
							| 
									
										
										
										
											2015-07-05 11:06:28 +02:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-28 19:17:17 +02:00
										 |  |  | 	// get the error, but ignore it | 
					
						
							|  |  |  | 	<-r.result | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2014-10-04 19:20:15 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-10-14 13:38:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | func (r *SFTP) deleteRecursive(ctx context.Context, name string) error { | 
					
						
							|  |  |  | 	entries, err := r.ReadDir(ctx, name) | 
					
						
							| 
									
										
										
										
											2017-10-14 16:08:15 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return errors.Wrap(err, "ReadDir") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, fi := range entries { | 
					
						
							|  |  |  | 		itemName := r.Join(name, fi.Name()) | 
					
						
							|  |  |  | 		if fi.IsDir() { | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | 			err := r.deleteRecursive(ctx, itemName) | 
					
						
							| 
									
										
										
										
											2017-10-14 16:08:15 +02:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return errors.Wrap(err, "ReadDir") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			err = r.c.RemoveDirectory(itemName) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return errors.Wrap(err, "RemoveDirectory") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err := r.c.Remove(itemName) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return errors.Wrap(err, "ReadDir") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-14 13:38:17 +02:00
										 |  |  | // Delete removes all data in the backend. | 
					
						
							| 
									
										
										
										
											2020-09-19 22:01:32 +02:00
										 |  |  | func (r *SFTP) Delete(ctx context.Context) error { | 
					
						
							|  |  |  | 	return r.deleteRecursive(ctx, r.p) | 
					
						
							| 
									
										
										
										
											2017-10-14 13:38:17 +02:00
										 |  |  | } |