mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
FIX (local storage): Add fallback for file movement via renaming to support cross-device movement
This commit is contained in:
@@ -2,11 +2,13 @@ package local_storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -108,7 +110,7 @@ func (l *LocalStorage) SaveFile(
|
||||
}
|
||||
|
||||
// Move the file from temp to backups directory
|
||||
if err = os.Rename(tempFilePath, finalPath); err != nil {
|
||||
if err = moveFile(tempFilePath, finalPath); err != nil {
|
||||
logger.Error(
|
||||
"Failed to move file from temp to backups",
|
||||
"fileName",
|
||||
@@ -197,6 +199,52 @@ func (l *LocalStorage) EncryptSensitiveData(encryptor encryption.FieldEncryptor)
|
||||
func (l *LocalStorage) Update(incoming *LocalStorage) {
|
||||
}
|
||||
|
||||
// moveFile moves a file from src to dst. It first attempts os.Rename for efficiency.
|
||||
// If rename fails with a cross-device link error (EXDEV), it falls back to copy-then-delete.
|
||||
// This happens when users mount temp and backups directories as separate Docker volumes
|
||||
// (e.g., on Unraid with split volume mapping), causing them to reside on different filesystems.
|
||||
func moveFile(src, dst string) error {
|
||||
err := os.Rename(src, dst)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var linkErr *os.LinkError
|
||||
if !errors.As(err, &linkErr) || !errors.Is(linkErr.Err, syscall.EXDEV) {
|
||||
return err
|
||||
}
|
||||
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open source file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = srcFile.Close()
|
||||
}()
|
||||
|
||||
dstFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create destination file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = dstFile.Close()
|
||||
}()
|
||||
|
||||
if _, err = io.Copy(dstFile, srcFile); err != nil {
|
||||
return fmt.Errorf("failed to copy file: %w", err)
|
||||
}
|
||||
|
||||
if err = dstFile.Sync(); err != nil {
|
||||
return fmt.Errorf("failed to sync destination file: %w", err)
|
||||
}
|
||||
|
||||
if err = os.Remove(src); err != nil {
|
||||
return fmt.Errorf("failed to remove source file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyWithContext(ctx context.Context, dst io.Writer, src io.Reader) (int64, error) {
|
||||
buf := make([]byte, localChunkSize)
|
||||
var written int64
|
||||
|
||||
Reference in New Issue
Block a user