diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go index 71d35a1..9a680bb 100644 --- a/internal/fusefrontend_reverse/rfile.go +++ b/internal/fusefrontend_reverse/rfile.go @@ -5,6 +5,10 @@ import ( "encoding/binary" "io" "os" + "syscall" + + // In newer Go versions, this has moved to just "sync/syncmap". + "golang.org/x/sync/syncmap" "github.com/hanwen/go-fuse/fuse" "github.com/hanwen/go-fuse/fuse/nodefs" @@ -26,6 +30,13 @@ type reverseFile struct { contentEnc *contentenc.ContentEnc } +var inodeTable syncmap.Map + +type derivedIVContainer struct { + id []byte + block0IV []byte +} + func (rfs *ReverseFS) newFile(relPath string, flags uint32) (nodefs.File, fuse.Status) { absPath, err := rfs.abs(rfs.decryptPath(relPath)) if err != nil { @@ -35,16 +46,45 @@ func (rfs *ReverseFS) newFile(relPath string, flags uint32) (nodefs.File, fuse.S if err != nil { return nil, fuse.ToStatus(err) } - id := derivePathIV(relPath, ivPurposeFileID) + var st syscall.Stat_t + err = syscall.Fstat(int(fd.Fd()), &st) + if err != nil { + tlog.Warn.Printf("newFile: Fstat error: %v", err) + return nil, fuse.ToStatus(err) + } + // See if we have that inode number already in the table + // (even if Nlink has dropped to 1) + var derivedIVs derivedIVContainer + v, found := inodeTable.Load(st.Ino) + if found { + tlog.Debug.Printf("ino%d: newFile: found in the inode table", st.Ino) + derivedIVs = v.(derivedIVContainer) + } else { + derivedIVs.id = derivePathIV(relPath, ivPurposeFileID) + derivedIVs.block0IV = derivePathIV(relPath, ivPurposeBlock0IV) + // Nlink > 1 means there is more than one path to this file. + // Store the derived values so we always return the same data, + // regardless of the path that is used to access the file. + // This means that the first path wins. + if st.Nlink > 1 { + v, found = inodeTable.LoadOrStore(st.Ino, derivedIVs) + if found { + // Another thread has stored a different value before we could. + derivedIVs = v.(derivedIVContainer) + } else { + tlog.Debug.Printf("ino%d: newFile: Nlink=%d, stored in the inode table", st.Ino, st.Nlink) + } + } + } header := contentenc.FileHeader{ Version: contentenc.CurrentVersion, - ID: id, + ID: derivedIVs.id, } return &reverseFile{ File: nodefs.NewDefaultFile(), fd: fd, header: header, - block0IV: derivePathIV(relPath, ivPurposeBlock0IV), + block0IV: derivedIVs.block0IV, contentEnc: rfs.contentEnc, }, fuse.OK }