From 61c0098ae6c1e4971373fbcc9767cff6de83e8a9 Mon Sep 17 00:00:00 2001 From: puc9 <51006296+puc9@users.noreply.github.com> Date: Tue, 9 May 2023 18:16:49 -0700 Subject: [PATCH] Close input file so SafeMove can delete it (#3714) * Close input file so SafeMove can delete it This is happening on Windows and over the network but at the end of SafeMove it fails the move with an error that it can't remove the input because it is in use. It turns out it is in use by the SafeMove itself :) * Copy the src file mod time --- pkg/fsutil/file.go | 62 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/pkg/fsutil/file.go b/pkg/fsutil/file.go index 7d91679fe..1bf982666 100644 --- a/pkg/fsutil/file.go +++ b/pkg/fsutil/file.go @@ -11,29 +11,55 @@ import ( "github.com/stashapp/stash/pkg/logger" ) +// CopyFile copies the contents of the file at srcpath to a regular file at dstpath. +// It will copy the last modified timestamp +// If dstpath already exists the function will fail. +func CopyFile(srcpath, dstpath string) (err error) { + r, err := os.Open(srcpath) + if err != nil { + return err + } + + w, err := os.OpenFile(dstpath, os.O_CREATE|os.O_EXCL, 0666) + if err != nil { + r.Close() // We need to close the input file as the defer below would not be called. + return err + } + + defer func() { + r.Close() // ok to ignore error: file was opened read-only. + e := w.Close() + // Report the error from w.Close, if any. + // But do so only if there isn't already an outgoing error. + if e != nil && err == nil { + err = e + } + // Copy modified time + if err == nil { + // io.Copy succeeded, we should fix the dstpath timestamp + srcFileInfo, e := os.Stat(srcpath) + if e != nil { + err = e + return + } + + e = os.Chtimes(dstpath, srcFileInfo.ModTime(), srcFileInfo.ModTime()) + if e != nil { + err = e + } + } + }() + + _, err = io.Copy(w, r) + return err +} + // SafeMove attempts to move the file with path src to dest using os.Rename. If this fails, then it copies src to dest, then deletes src. func SafeMove(src, dst string) error { err := os.Rename(src, dst) if err != nil { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - - out, err := os.Create(dst) - if err != nil { - return err - } - defer out.Close() - - _, err = io.Copy(out, in) - if err != nil { - return err - } - - err = out.Close() + err = CopyFile(src, dst) if err != nil { return err }