mirror of https://github.com/restic/restic.git
Merge pull request #3524 from MichaelEischer/atomic-sftp
sftp: Implement atomic uploads
This commit is contained in:
commit
a350625554
|
@ -0,0 +1,10 @@
|
||||||
|
Enhancement: Atomic uploads for SFTP
|
||||||
|
|
||||||
|
The SFTP backend did not upload files atomically. An interrupted upload could
|
||||||
|
leave an incomplete file behind which could prevent restic from accessing the
|
||||||
|
repository.
|
||||||
|
|
||||||
|
Uploads in the SFTP backend are now done atomically.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/issues/3003
|
||||||
|
https://github.com/restic/restic/pull/3524
|
|
@ -3,6 +3,8 @@ package sftp
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
@ -252,6 +254,17 @@ func Join(parts ...string) string {
|
||||||
return path.Clean(path.Join(parts...))
|
return path.Clean(path.Join(parts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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[:])
|
||||||
|
}
|
||||||
|
|
||||||
// Save stores data in the backend at the handle.
|
// Save stores data in the backend at the handle.
|
||||||
func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
|
func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
|
||||||
debug.Log("Save %v", h)
|
debug.Log("Save %v", h)
|
||||||
|
@ -264,10 +277,11 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := r.Filename(h)
|
filename := r.Filename(h)
|
||||||
|
tmpFilename := filename + "-restic-temp-" + tempSuffix()
|
||||||
dirname := r.Dirname(h)
|
dirname := r.Dirname(h)
|
||||||
|
|
||||||
// create new file
|
// create new file
|
||||||
f, err := r.c.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY)
|
f, err := r.c.OpenFile(tmpFilename, os.O_CREATE|os.O_EXCL|os.O_WRONLY)
|
||||||
|
|
||||||
if r.IsNotExist(err) {
|
if r.IsNotExist(err) {
|
||||||
// error is caused by a missing directory, try to create it
|
// error is caused by a missing directory, try to create it
|
||||||
|
@ -276,7 +290,7 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
||||||
debug.Log("error creating dir %v: %v", r.Dirname(h), mkdirErr)
|
debug.Log("error creating dir %v: %v", r.Dirname(h), mkdirErr)
|
||||||
} else {
|
} else {
|
||||||
// try again
|
// try again
|
||||||
f, err = r.c.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY)
|
f, err = r.c.OpenFile(tmpFilename, os.O_CREATE|os.O_EXCL|os.O_WRONLY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +312,7 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
||||||
rmErr := r.c.Remove(f.Name())
|
rmErr := r.c.Remove(f.Name())
|
||||||
if rmErr != nil {
|
if rmErr != nil {
|
||||||
debug.Log("sftp: failed to remove broken file %v: %v",
|
debug.Log("sftp: failed to remove broken file %v: %v",
|
||||||
filename, rmErr)
|
f.Name(), rmErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.checkNoSpace(dirname, rd.Length(), err)
|
err = r.checkNoSpace(dirname, rd.Length(), err)
|
||||||
|
@ -318,7 +332,12 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
||||||
}
|
}
|
||||||
|
|
||||||
err = f.Close()
|
err = f.Close()
|
||||||
return errors.Wrap(err, "Close")
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Close")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.c.Rename(tmpFilename, filename)
|
||||||
|
return errors.Wrap(err, "Rename")
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNoSpace checks if err was likely caused by lack of available space
|
// checkNoSpace checks if err was likely caused by lack of available space
|
||||||
|
|
Loading…
Reference in New Issue