diff --git a/internal/postgresql/postgresql.go b/internal/postgresql/postgresql.go index 7160e94ce..b37516848 100644 --- a/internal/postgresql/postgresql.go +++ b/internal/postgresql/postgresql.go @@ -326,30 +326,14 @@ func (p *Manager) moveWal() (err error) { } // We use tmpPath here first and (if needed) mv tmpPath to desiredPath when all is copied. // This allows stolon-keeper to re-read symlink dest and continue should stolon-keeper be restarted while copying. - log.Debugf("creating %s", tmpPath) - if err = os.MkdirAll(tmpPath, 0700); err != nil && !os.IsExist(err) { - log.Errorf("could not create new dest folder %s: %e", tmpPath, err) + if err = moveDirRecursive(curPath, tmpPath); err != nil { return err } - log.Debugf("moving WAL files from %s to %s", curPath, tmpPath) - var entries []fs.FileInfo - if entries, err = ioutil.ReadDir(curPath); err != nil { - log.Errorf("could not read contents of folder %s: %e", curPath, err) - return err - } else { - for _, entry := range entries { - srcEntry := filepath.Join(curPath, entry.Name()) - dstEntry := filepath.Join(tmpPath, entry.Name()) - log.Debugf("moving %s to %s", srcEntry, dstEntry) - if err = os.Rename(srcEntry, dstEntry); err != nil { - log.Errorf("could not move %s to %s: %e", srcEntry, dstEntry, err) - return err - } - } - } var symlinkStat fs.FileInfo - if symlinkStat, err = os.Lstat(symlinkPath); err != nil { + if symlinkStat, err = os.Lstat(symlinkPath); errors.Is(err, os.ErrNotExist) { + // File or folder already removed + } else if err != nil { log.Errorf("could not get info on current pg_wal folder/symlink %s: %e", symlinkPath, err) return err } else if symlinkStat.Mode()&os.ModeSymlink != 0 { diff --git a/internal/postgresql/utils.go b/internal/postgresql/utils.go index 166f758ae..de9adde7c 100644 --- a/internal/postgresql/utils.go +++ b/internal/postgresql/utils.go @@ -18,10 +18,16 @@ import ( "bufio" "context" "database/sql" + "errors" "fmt" + "io" + "io/fs" + "io/ioutil" + "path/filepath" "regexp" "strconv" "strings" + "syscall" "github.com/sorintlab/stolon/internal/common" @@ -556,3 +562,77 @@ func WalFileNameNoTimeLine(name string) (string, error) { } return name[8:24], nil } + +func moveFile(sourcePath, destPath string) error { + // using os.Rename is faster when on same filesystem + if err := os.Rename(sourcePath, destPath); err == nil { + return nil + } + // Error. Let's try to write + inputFile, err := os.Open(sourcePath) + if err != nil { + return fmt.Errorf("Couldn't open source file: %s", err) + } + inFileStat, err := inputFile.Stat() + if err != nil { + return err + } + flag := os.O_WRONLY | os.O_CREATE | os.O_TRUNC + perm := inFileStat.Mode() & os.ModePerm + outputFile, err := os.OpenFile(destPath, flag, perm) + if err != nil { + return err + } + defer outputFile.Close() + _, err = io.Copy(outputFile, inputFile) + inputFile.Close() + if err != nil { + return fmt.Errorf("Writing to output file failed: %s", err) + } + // The copy was successful, so now delete the original file + err = os.Remove(sourcePath) + if err != nil { + return fmt.Errorf("Failed removing original file: %s", err) + } + return nil +} + +func moveDirRecursive(src string, dest string) error { + log.Infof("Moving %s to %s", src, dest) + if stat, err := os.Stat(src); err != nil { + log.Errorf("could not get stat of %s: %e", src, err) + return err + } else if stat.IsDir() { + // Make the dir if it doesn't exist + if _, err := os.Stat(dest); errors.Is(err, os.ErrNotExist) { + if err := os.MkdirAll(dest, stat.Mode() & os.ModePerm); err != nil { + return err + } + } else if err != nil { + log.Errorf("could not get stat of %s: %e", dest, err) + return err + } + // Copy all files and folders in this folder + var entries []fs.FileInfo + if entries, err = ioutil.ReadDir(src); err != nil { + log.Errorf("could not read contents of folder %s: %e", src, err) + return err + } else { + for _, entry := range entries { + srcEntry := filepath.Join(src, entry.Name()) + dstEntry := filepath.Join(dest, entry.Name()) + if err := moveDirRecursive(srcEntry, dstEntry); err != nil { + return err + } + } + } + // Remove this folder, which is now supposedly empty + if err := syscall.Rmdir(src); err != nil { + log.Errorf("could not remove folder %s: %e", src, err) + return err + } + } else { + return moveFile(src, dest) + } + return nil +}