diff options
| author | Marios Titas | 2026-07-21 20:27:38 +0300 |
|---|---|---|
| committer | Jakob Unterwurzacher | 2026-07-22 21:57:57 +0200 |
| commit | 790362e8f8eabd1bdf75f98328daf94cf623c600 (patch) | |
| tree | fcebc1fa556e8335a2fc0aef5df2bc573fd064d1 /internal | |
| parent | b3b292a8a3c1478b769e213e0e590f38cb1b923c (diff) | |
Forward the backing filesystem's STATX_BTIME on Linux while preserving
existing metadata translations and fallback behavior.
On Linux 6.6 and newer, this allows statx consumers such as GNU stat
to report birth time when the backing filesystem provides it. Older
kernels and backing filesystems without birth-time support retain the
existing behavior. No on-disk format change or migration is required.
Reverse mode remains unchanged.
Fixes #868.
Related context: #732
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend/node_helpers.go | 4 | ||||
| -rw-r--r-- | internal/fusefrontend/statx_linux.go | 78 | ||||
| -rw-r--r-- | internal/syscallcompat/sys_linux.go | 9 |
3 files changed, 89 insertions, 2 deletions
diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go index 96c0961..3102275 100644 --- a/internal/fusefrontend/node_helpers.go +++ b/internal/fusefrontend/node_helpers.go @@ -53,9 +53,9 @@ func (n *Node) readlink(dirfd int, cName string) (out []byte, errno syscall.Errn return []byte(target), 0 } -// translateSize translates the ciphertext size in `out` into plaintext size. +// translateSize translates the ciphertext size cSize into plaintext size. // Handles regular files & symlinks (and finds out what is what by looking at -// `out.Mode`). +// mode). func (n *Node) translateSize(dirfd int, cName string, mode uint32, cSize uint64) (pSize uint64) { switch mode & syscall.S_IFMT { case syscall.S_IFREG: diff --git a/internal/fusefrontend/statx_linux.go b/internal/fusefrontend/statx_linux.go new file mode 100644 index 0000000..f9f87cf --- /dev/null +++ b/internal/fusefrontend/statx_linux.go @@ -0,0 +1,78 @@ +package fusefrontend + +import ( + "context" + "syscall" + + "github.com/hanwen/go-fuse/v2/fs" + "github.com/hanwen/go-fuse/v2/fuse" + "golang.org/x/sys/unix" + + "github.com/rfjakob/gocryptfs/v2/internal/inomap" + "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat" +) + +var _ = (fs.NodeStatxer)((*Node)(nil)) +var _ = (fs.FileStatxer)((*File)(nil)) + +// Statx is the Linux statx equivalent of Getattr. +func (n *Node) Statx(ctx context.Context, f fs.FileHandle, flags uint32, mask uint32, out *fuse.StatxOut) (errno syscall.Errno) { + // If the kernel gives us a file handle, use it. Current Linux kernels do + // not send one with FUSE_STATX, but keep this for future compatibility. + if f != nil { + if fsx, ok := f.(fs.FileStatxer); ok { + return fsx.Statx(ctx, flags, mask, out) + } + } + + dirfd, cName, errno := n.prepareAtSyscallMyself() + if errno != 0 { + return errno + } + defer syscall.Close(dirfd) + + var st unix.Statx_t + err := syscallcompat.Statx(dirfd, cName, int(flags)|unix.AT_SYMLINK_NOFOLLOW, int(mask), &st) + if err != nil { + return fs.ToErrno(err) + } + + // fix inode number, size, owner + rn := n.rootNode() + st.Ino = rn.inoMap.Translate(inomap.NewQIno(unix.Mkdev(st.Dev_major, st.Dev_minor), 0, st.Ino)) + st.Size = rn.translateSize(dirfd, cName, uint32(st.Mode), st.Size) + if rn.args.ForceOwner != nil { + st.Uid = rn.args.ForceOwner.Uid + st.Gid = rn.args.ForceOwner.Gid + } + + out.FromStatx(&st) + return 0 +} + +// Statx returns statx information for an open backing file. Current Linux +// kernels do not send a file handle with FUSE_STATX, so this is not reached yet. +func (f *File) Statx(_ context.Context, flags uint32, mask uint32, out *fuse.StatxOut) syscall.Errno { + f.fdLock.RLock() + defer f.fdLock.RUnlock() + + var st unix.Statx_t + err := syscallcompat.Statx(f.intFd(), "", int(flags)|unix.AT_EMPTY_PATH, int(mask), &st) + if err != nil { + return fs.ToErrno(err) + } + + // fix inode number, size, owner + rn := f.rootNode + st.Ino = rn.inoMap.Translate(inomap.NewQIno(unix.Mkdev(st.Dev_major, st.Dev_minor), 0, st.Ino)) + if uint32(st.Mode)&syscall.S_IFMT == syscall.S_IFREG { + st.Size = rn.contentEnc.CipherSizeToPlainSize(st.Size) + } + if rn.args.ForceOwner != nil { + st.Uid = rn.args.ForceOwner.Uid + st.Gid = rn.args.ForceOwner.Gid + } + + out.FromStatx(&st) + return 0 +} diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index 2b8a6f7..ffa5a97 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -80,6 +80,15 @@ func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) { return syscall.Mknodat(dirfd, path, mode, dev) } +// Statx wraps the Statx syscall. +// Retries on EINTR. +func Statx(dirfd int, path string, flags int, mask int, st *unix.Statx_t) (err error) { + err = retryEINTR(func() error { + return unix.Statx(dirfd, path, flags, mask, st) + }) + return err +} + // Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2 // is not implemented on arm64. func Dup3(oldfd int, newfd int, flags int) (err error) { |
