diff --git a/src/restic/backend/sftp/config.go b/src/restic/backend/sftp/config.go index 3d029a0dd..9a1ac35de 100644 --- a/src/restic/backend/sftp/config.go +++ b/src/restic/backend/sftp/config.go @@ -11,6 +11,7 @@ import ( // Config collects all information required to connect to an sftp server. type Config struct { User, Host, Dir string + Layout string `option:"layout"` Command string `option:"command"` } diff --git a/src/restic/backend/sftp/sftp.go b/src/restic/backend/sftp/sftp.go index c1b54ba1a..b0b796df6 100644 --- a/src/restic/backend/sftp/sftp.go +++ b/src/restic/backend/sftp/sftp.go @@ -2,8 +2,6 @@ package sftp import ( "bufio" - "crypto/rand" - "encoding/hex" "fmt" "io" "os" @@ -32,10 +30,14 @@ type SFTP struct { cmd *exec.Cmd result <-chan error + + backend.Layout } var _ restic.Backend = &SFTP{} +const defaultLayout = "default" + func startClient(program string, args ...string) (*SFTP, error) { debug.Log("start client %v %v", program, args) // Connect to a remote host and request the sftp subsystem via the 'ssh' @@ -127,6 +129,11 @@ func open(dir string, program string, args ...string) (*SFTP, error) { return nil, err } + l, err := backend.ParseLayout(sftp, cfg.Layout, defaultLayout, cfg.Path) + if err != nil { + return nil, err + } + // test if all necessary dirs and files are there for _, d := range paths(dir) { if _, err := sftp.c.Lstat(d); err != nil { @@ -225,28 +232,6 @@ func (r *SFTP) Location() string { return r.p } -// Return temp directory in correct directory for this backend. -func (r *SFTP) tempFile() (string, *sftp.File, error) { - // choose random suffix - buf := make([]byte, tempfileRandomSuffixLength) - _, err := io.ReadFull(rand.Reader, buf) - if err != nil { - return "", nil, errors.Errorf("unable to read %d random bytes for tempfile name: %v", - tempfileRandomSuffixLength, err) - } - - // construct tempfile name - name := Join(r.p, backend.Paths.Temp, "temp-"+hex.EncodeToString(buf)) - - // create file in temp dir - f, err := r.c.Create(name) - if err != nil { - return "", nil, errors.Errorf("creating tempfile %q failed: %v", name, err) - } - - return name, f, nil -} - func (r *SFTP) mkdirAll(dir string, mode os.FileMode) error { // check if directory already exists fi, err := r.c.Lstat(dir) @@ -349,7 +334,7 @@ func (r *SFTP) dirname(h restic.Handle) string { // Save stores data in the backend at the handle. func (r *SFTP) Save(h restic.Handle, rd io.Reader) (err error) { - debug.Log("save to %v", h) + debug.Log("Save %v", h) if err := r.clientError(); err != nil { return err } @@ -358,6 +343,35 @@ func (r *SFTP) Save(h restic.Handle, rd io.Reader) (err error) { return err } + filename := r.Filename(h) + + // create directories if necessary + if h.Type == restic.DataFile { + err := r.mkdirAll(path.Dir(filename), backend.Modes.Dir) + if err != nil { + return err + } + } + + // test if new file exists + if _, err := r.c.Lstat(filename); err == nil { + return errors.Errorf("Close(): file %v already exists", filename) + } + + err := r.c.Rename(oldname, filename) + if err != nil { + return errors.Wrap(err, "Rename") + } + + // set mode to read-only + fi, err := r.c.Lstat(filename) + if err != nil { + return errors.Wrap(err, "Lstat") + } + + err = r.c.Chmod(filename, fi.Mode()&os.FileMode(^uint32(0222))) + return errors.Wrap(err, "Chmod") + filename, tmpfile, err := r.tempFile() if err != nil { return err