From c481632a2853eb3540056f13707c2670e38948c6 Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Wed, 7 May 2025 09:37:25 +1000 Subject: [PATCH] Harden read/write part file ordering --- storage/file-piece.go | 6 +++--- storage/file.go | 32 +++++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/storage/file-piece.go b/storage/file-piece.go index 985cca92..7b25fdd3 100644 --- a/storage/file-piece.go +++ b/storage/file-piece.go @@ -64,9 +64,9 @@ func (me *filePieceImpl) Completion() Completion { for i, extent := range me.t.segmentLocater.LocateIter(me.extent()) { noFiles = false file := me.t.files[i] - s, err := os.Stat(file.safeOsPath) + s, err := os.Stat(file.partFilePath()) if errors.Is(err, fs.ErrNotExist) { - s, err = os.Stat(file.partFilePath()) + s, err = os.Stat(file.safeOsPath) } if err != nil { me.logger().Warn( @@ -185,7 +185,7 @@ func (me *filePieceImpl) onFileNotComplete(f file) (err error) { // Ensure the file is writable err = os.Chmod(f.safeOsPath, info.Mode().Perm()|(filePerm&0o222)) if err != nil { - err = fmt.Errorf("setting file to read-only: %w", err) + err = fmt.Errorf("setting file writable: %w", err) return } return diff --git a/storage/file.go b/storage/file.go index 42ef20c5..3cf5f62b 100644 --- a/storage/file.go +++ b/storage/file.go @@ -222,10 +222,13 @@ type fileTorrentImplIO struct { // Returns EOF on short or missing file. func (fst fileTorrentImplIO) readFileAt(file file, b []byte, off int64) (n int, err error) { - f, err := os.Open(file.safeOsPath) - if fst.fts.partFiles() && errors.Is(err, fs.ErrNotExist) { + var f *os.File + if fst.fts.partFiles() { f, err = os.Open(file.partFilePath()) } + if errors.Is(err, fs.ErrNotExist) { + f, err = os.Open(file.safeOsPath) + } if errors.Is(err, fs.ErrNotExist) { // File missing is treated the same as a short file. Should we propagate this through the // interface now that fs.ErrNotExist is a thing? @@ -268,13 +271,32 @@ func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) { return } +func (fst fileTorrentImplIO) openForWrite(file file) (f *os.File, err error) { + p := fst.fts.pathForWrite(file) + f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE, filePerm) + if err == nil { + return + } + if errors.Is(err, fs.ErrNotExist) { + err = os.MkdirAll(filepath.Dir(p), dirPerm) + if err != nil { + return + } + } else if errors.Is(err, fs.ErrPermission) { + err = os.Chmod(p, filePerm) + if err != nil { + return + } + } + f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE, filePerm) + return +} + func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) { // log.Printf("write at %v: %v bytes", off, len(p)) fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(p))}, func(i int, e segments.Extent) bool { - name := fst.fts.pathForWrite(fst.fts.files[i]) - os.MkdirAll(filepath.Dir(name), dirPerm) var f *os.File - f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, filePerm) + f, err = fst.openForWrite(fst.fts.files[i]) if err != nil { return false } -- 2.51.0