mirror of
https://github.com/octoleo/restic.git
synced 2024-12-22 10:58:55 +00:00
Merge pull request #3345 from greatroar/sftp-enospc
Check for ENOSPC and remove broken files in SFTP
This commit is contained in:
commit
64b00d28b1
8
changelog/unreleased/issue-3336
Normal file
8
changelog/unreleased/issue-3336
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Enhancement: SFTP backend now checks for disk space
|
||||||
|
|
||||||
|
Backing up over SFTP previously spewed multiple generic "failure" messages
|
||||||
|
when the remote disk was full. It now checks for disk space before writing
|
||||||
|
a file and fails immediately with a "no space left on device" message.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/issues/3336
|
||||||
|
https://github.com/restic/restic/pull/3345
|
@ -258,6 +258,7 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
filename := r.Filename(h)
|
filename := r.Filename(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(filename, os.O_CREATE|os.O_EXCL|os.O_WRONLY)
|
||||||
@ -273,10 +274,30 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pkg/sftp doesn't allow creating with a mode.
|
||||||
|
// Chmod while the file is still empty.
|
||||||
|
if err == nil {
|
||||||
|
err = f.Chmod(backend.Modes.File)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "OpenFile")
|
return errors.Wrap(err, "OpenFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
filename, rmErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.checkNoSpace(dirname, rd.Length(), err)
|
||||||
|
}()
|
||||||
|
|
||||||
// save data, make sure to use the optimized sftp upload method
|
// save data, make sure to use the optimized sftp upload method
|
||||||
wbytes, err := f.ReadFrom(rd)
|
wbytes, err := f.ReadFrom(rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -291,11 +312,32 @@ func (r *SFTP) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = f.Close()
|
err = f.Close()
|
||||||
if err != nil {
|
return errors.Wrap(err, "Close")
|
||||||
return errors.Wrap(err, "Close")
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Wrap(r.c.Chmod(filename, backend.Modes.File), "Chmod")
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load runs fn with a reader that yields the contents of the file at h at the
|
// Load runs fn with a reader that yields the contents of the file at h at the
|
||||||
|
Loading…
Reference in New Issue
Block a user