Fix a bug in the SFTP backend that would cause an error when overwriting existing files

This commit is contained in:
2026-06-13 13:57:50 -05:00
parent fc33bbca54
commit c84d8868d1
2 changed files with 155 additions and 1 deletions

View File

@@ -170,7 +170,7 @@ func (b *Backend) WriteFrom(ctx context.Context, logicalPath string, r io.Reader
return storage.Entry{}, storage.NewError(storage.OpWriteFrom, BackendName, logicalPath, storage.ErrConflict, fmt.Errorf("stream size %d does not match expected size %d", written, opts.Size))
}
if opts.PreferAtomic {
if err := b.client.Rename(writePath, nativePath); err != nil {
if err := renamePromotedFile(b.client, writePath, nativePath, opts.Overwrite); err != nil {
return storage.Entry{}, b.translateError(storage.OpWriteFrom, logicalPath, err)
}
cleanup = false
@@ -178,6 +178,27 @@ func (b *Backend) WriteFrom(ctx context.Context, logicalPath string, r io.Reader
return b.Stat(ctx, logicalPath)
}
type sftpRenamer interface {
PosixRename(oldname, newname string) error
Rename(oldname, newname string) error
Remove(path string) error
}
func renamePromotedFile(client sftpRenamer, oldname, newname string, overwrite bool) error {
if !overwrite {
return client.Rename(oldname, newname)
}
if err := client.PosixRename(oldname, newname); err == nil {
return nil
} else if !isReplaceRenameFallbackError(err) {
return err
}
if err := client.Remove(newname); err != nil && !isNotExist(err) {
return err
}
return client.Rename(oldname, newname)
}
func (b *Backend) Stat(ctx context.Context, logicalPath string) (storage.Entry, error) {
if err := ctx.Err(); err != nil {
return storage.Entry{}, err
@@ -464,6 +485,14 @@ func isNotExist(err error) bool {
return errors.Is(err, fs.ErrNotExist) || errors.Is(err, os.ErrNotExist) || errors.Is(err, sftp.ErrSSHFxNoSuchFile)
}
func isReplaceRenameFallbackError(err error) bool {
if errors.Is(err, sftp.ErrSSHFxFailure) || errors.Is(err, sftp.ErrSSHFxOpUnsupported) {
return true
}
var statusErr *sftp.StatusError
return errors.As(err, &statusErr) && (statusErr.FxCode() == sftp.ErrSSHFxFailure || statusErr.FxCode() == sftp.ErrSSHFxOpUnsupported)
}
func (b *Backend) translateError(op, logicalPath string, err error) error {
kind := storage.ErrUnknown
switch {