aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorJared Van Bortel2026-02-06 15:16:40 -0500
committerJakob Unterwurzacher2026-02-10 21:26:49 +0100
commitb024e3696d215e6b7a0b8d17ca3eb28af20dd504 (patch)
treeafd60d9189530c63e3e7d15e1b59f035914b5293 /internal
parent61685370b1aaffde25e5427a08509d33d71746fe (diff)
enable falloc on btrfs if CoW is disabled
Diffstat (limited to 'internal')
-rw-r--r--internal/fusefrontend/root_node.go4
-rw-r--r--internal/syscallcompat/quirks_linux.go40
2 files changed, 41 insertions, 3 deletions
diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go
index aa26b9c..38d070d 100644
--- a/internal/fusefrontend/root_node.go
+++ b/internal/fusefrontend/root_node.go
@@ -92,7 +92,9 @@ func NewRootNode(args Args, c *contentenc.ContentEnc, n *nametransform.NameTrans
}
// Suppress the message if the user has already specified -noprealloc
if rn.quirks&syscallcompat.QuirkBtrfsBrokenFalloc != 0 && !args.NoPrealloc {
- syscallcompat.LogQuirk("Btrfs detected, forcing -noprealloc. See https://github.com/rfjakob/gocryptfs/issues/395 for why.")
+ syscallcompat.LogQuirk("Btrfs detected, forcing -noprealloc. " +
+ "Use \"chattr +C\" on the backing directory to enable NOCOW and allow preallocation. " +
+ "See https://github.com/rfjakob/gocryptfs/issues/395 for details.")
}
if statErr == nil {
rn.inoMap.TranslateStat(&st)
diff --git a/internal/syscallcompat/quirks_linux.go b/internal/syscallcompat/quirks_linux.go
index 87af03d..35f754d 100644
--- a/internal/syscallcompat/quirks_linux.go
+++ b/internal/syscallcompat/quirks_linux.go
@@ -1,11 +1,38 @@
package syscallcompat
import (
+ "syscall"
+
"golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
)
+// FS_NOCOW_FL is the flag set by "chattr +C" to disable copy-on-write on
+// btrfs. Not exported by golang.org/x/sys/unix, value from linux/fs.h.
+const FS_NOCOW_FL = 0x00800000
+
+// dirHasNoCow checks whether the directory at the given path has the
+// NOCOW (No Copy-on-Write) attribute set (i.e. "chattr +C").
+// When a directory has this attribute, files created within it inherit
+// NOCOW, which makes fallocate work correctly on btrfs because writes
+// go in-place rather than through COW.
+func dirHasNoCow(path string) bool {
+ fd, err := syscall.Open(path, syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
+ if err != nil {
+ tlog.Debug.Printf("dirHasNoCow: Open %q failed: %v", path, err)
+ return false
+ }
+ defer syscall.Close(fd)
+
+ flags, err := unix.IoctlGetInt(fd, unix.FS_IOC_GETFLAGS)
+ if err != nil {
+ tlog.Debug.Printf("dirHasNoCow: FS_IOC_GETFLAGS on %q failed: %v", path, err)
+ return false
+ }
+ return flags&FS_NOCOW_FL != 0
+}
+
// DetectQuirks decides if there are known quirks on the backing filesystem
// that need to be workarounded.
//
@@ -21,10 +48,19 @@ func DetectQuirks(cipherdir string) (q uint64) {
// Preallocation on Btrfs is broken ( https://github.com/rfjakob/gocryptfs/issues/395 )
// and slow ( https://github.com/rfjakob/gocryptfs/issues/63 ).
//
+ // The root cause is that btrfs COW allocates new blocks on write even for
+ // preallocated extents, defeating the purpose of fallocate. However, if the
+ // backing directory has the NOCOW attribute (chattr +C), writes go in-place
+ // and fallocate works correctly.
+ //
// Cast to uint32 avoids compile error on arm: "constant 2435016766 overflows int32"
if uint32(st.Type) == unix.BTRFS_SUPER_MAGIC {
- // LogQuirk is called in fusefrontend/root_node.go
- q |= QuirkBtrfsBrokenFalloc
+ if dirHasNoCow(cipherdir) {
+ tlog.Debug.Printf("DetectQuirks: Btrfs detected but cipherdir has NOCOW attribute (chattr +C), fallocate should work correctly")
+ } else {
+ // LogQuirk is called in fusefrontend/root_node.go
+ q |= QuirkBtrfsBrokenFalloc
+ }
}
return q