aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/fusefrontend/file.go16
-rw-r--r--internal/fusefrontend/file_lock.go29
-rw-r--r--internal/syscallcompat/sys_darwin.go4
-rw-r--r--internal/syscallcompat/sys_linux.go4
4 files changed, 53 insertions, 0 deletions
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go
index 64c6ca0..40dce6d 100644
--- a/internal/fusefrontend/file.go
+++ b/internal/fusefrontend/file.go
@@ -14,6 +14,8 @@ import (
"sync"
"syscall"
+ "golang.org/x/sys/unix"
+
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
@@ -90,6 +92,13 @@ func (f *File) readFileID() ([]byte, error) {
// and not only the header. A header-only file will be considered empty.
// This makes File ID poisoning more difficult.
readLen := contentenc.HeaderLen + 1
+ if f.rootNode.args.SharedStorage {
+ // With -sharedstorage, we consider a header-only file as valid, because
+ // another gocryptfs process may have either:
+ // 1) just created the header, and not written further data yet.
+ // 2) truncated the file down to just the header.
+ readLen = contentenc.HeaderLen
+ }
buf := make([]byte, readLen)
n, err := f.fd.ReadAt(buf, 0)
if err != nil {
@@ -266,18 +275,25 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
//
// If the file ID is not cached, read it from disk
if f.fileTableEntry.ID == nil {
+ if err := f.LockSharedStorage(unix.F_WRLCK, 0, contentenc.HeaderLen); err != nil {
+ return 0, fs.ToErrno(err)
+ }
var err error
fileID, err := f.readFileID()
// Write a new file header if the file is empty
if err == io.EOF {
fileID, err = f.createHeader()
fileWasEmpty = true
+ // Having the unlock three times is ugly. But every other way I tried is even uglier.
+ f.LockSharedStorage(unix.F_UNLCK, 0, contentenc.HeaderLen)
} else if err != nil {
// Other errors mean readFileID() found a corrupt header
tlog.Warn.Printf("doWrite %d: corrupt header: %v", f.qIno.Ino, err)
+ f.LockSharedStorage(unix.F_UNLCK, 0, contentenc.HeaderLen)
return 0, syscall.EIO
}
if err != nil {
+ f.LockSharedStorage(unix.F_UNLCK, 0, contentenc.HeaderLen)
return 0, fs.ToErrno(err)
}
f.fileTableEntry.ID = fileID
diff --git a/internal/fusefrontend/file_lock.go b/internal/fusefrontend/file_lock.go
new file mode 100644
index 0000000..6f92cfe
--- /dev/null
+++ b/internal/fusefrontend/file_lock.go
@@ -0,0 +1,29 @@
+package fusefrontend
+
+import (
+ "golang.org/x/sys/unix"
+
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
+)
+
+// SharedStorageLock conveniently wraps F_OFD_SETLKW
+// See https://man7.org/linux/man-pages/man2/fcntl.2.html -> "Open file description locks (non-POSIX)"
+//
+// lkType is one of:
+// * unix.F_RDLCK (shared read lock)
+// * unix.F_WRLCK (exclusive write lock)
+// * unix.F_UNLCK (unlock)
+//
+// This function is a no-op if args.SharedStorage == false.
+func (f *File) LockSharedStorage(lkType int16, lkStart int64, lkLen int64) error {
+ if !f.rootNode.args.SharedStorage {
+ return nil
+ }
+ lk := unix.Flock_t{
+ Type: lkType,
+ Whence: unix.SEEK_SET,
+ Start: lkStart,
+ Len: lkLen,
+ }
+ return unix.FcntlFlock(uintptr(f.intFd()), syscallcompat.F_OFD_SETLKW, &lk)
+}
diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go
index 52d4e0f..17e6a9e 100644
--- a/internal/syscallcompat/sys_darwin.go
+++ b/internal/syscallcompat/sys_darwin.go
@@ -33,6 +33,10 @@ const (
// On Darwin we use O_SYMLINK which allows opening a symlink itself.
// On Linux, we only have O_NOFOLLOW.
OpenatFlagNofollowSymlink = unix.O_SYMLINK
+
+ // F_OFD_SETLKW only exists on Linux. On Darwin, fall back to F_SETLKW as a
+ // flawed replacement.
+ F_OFD_SETLKW = unix.F_SETLKW
)
// Unfortunately fsetattrlist does not have a syscall wrapper yet.
diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go
index a850ba1..51a2c09 100644
--- a/internal/syscallcompat/sys_linux.go
+++ b/internal/syscallcompat/sys_linux.go
@@ -35,6 +35,10 @@ const (
// Only defined on Linux
ENODATA = unix.ENODATA
+
+ // F_OFD_SETLKW only exists on Linux. On Darwin, fall back to F_SETLKW as a
+ // flawed replacement.
+ F_OFD_SETLKW = unix.F_OFD_SETLKW
)
var preallocWarn sync.Once