Skip to content

Add Filesystem::tmpfile() for FUSE_TMPFILE - #740

Open
cberner wants to merge 1 commit into
masterfrom
claude/github-issues-triage-dv6dhi
Open

Add Filesystem::tmpfile() for FUSE_TMPFILE#740
cberner wants to merge 1 commit into
masterfrom
claude/github-issues-triage-dv6dhi

Conversation

@cberner

@cberner cberner commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Fixes #366.

FUSE_TMPFILE is opcode 51, ABI 7.37. The kernel sends it for open() with O_TMPFILE, asking for a file with no name that belongs to no directory until linkat() gives it one.

On the wire it is a create request: fuse_create_open() in the kernel builds it with opcode = FUSE_TMPFILE and otherwise identical arguments, so it carries fuse_create_in plus a name and is answered with fuse_entry_out + fuse_open_out. The name is the placeholder / that the VFS gives every anonymous dentry, so it says nothing and is not passed to the filesystem -- libfuse's tmpfile handler drops it for the same reason, taking only (req, parent, mode, fi).

Unlike #359 this is reachable on an ordinary mount, so it comes with a real end-to-end test rather than only an ABI one.

Behaviour is unchanged for a filesystem that does not implement it: ENOSYS is what the unknown-opcode path already returned, the kernel takes that as permanent, sets fc->no_tmpfile and reports EOPNOTSUPP without asking again. That includes the simple example, so nothing in pjdfs or xfstests changes.

Testing

  • session::test::tmpfile_open mounts a filesystem, opens the mountpoint with O_TMPFILE | O_RDWR, and requires that the call reaches Filesystem::tmpfile() with S_IFREG set and O_TMPFILE in the flags, and that the descriptor the filesystem replies with gets back to the caller. Confirmed it fails without the dispatch arm, with EOPNOTSUPP -- which is exactly the old behaviour, so the test is pinning the change rather than the plumbing.
  • ll::request::tests::tmpfile covers the parse in the byte-level style of the neighbouring request tests, for both endiannesses.

cargo fmt, all three clippy invocations from make pre, cargo doc, cargo test --all with and without libfuse, the musl build, cargo check --target x86_64-apple-darwin --features=macos-no-mount, and make test_passthrough are green. Docker was unavailable here, so mount_tests/pjdfs_tests/xfstests did not run locally.

Not included

simple does not implement tmpfile. Doing so needs an inode with no name and no parent, plus link() to later give it one, which is a feature of the example rather than of this change -- and without it the example's behaviour is byte-for-byte what it is today.

🤖 Generated with Claude Code

https://claude.ai/code/session_01H5akmePV1VnD9YRQLZH4gv


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 354de0f9f3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ll/request.rs
assert_eq!(x.mode(), 0o100_640);
assert_eq!(x.umask(), 0o22);
// O_TMPFILE is O_DIRECTORY | 0o20_000_000 on every architecture fuser builds for
assert_eq!(x.flags(), libc::O_RDWR | libc::O_TMPFILE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate the tmpfile unit test to Linux

The macOS and FreeBSD CI jobs compile all test targets (cargo build --all --all-targets), but this new always-compiled unit test references the Linux-only libc::O_TMPFILE constant. On those non-Linux targets the constant is not available, so cross-platform CI will fail before tests can run; please gate this test/assertion to Linux or compare against a portable literal wire value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and already fixed in b906df2 -- both jobs had failed with exactly this before the review landed, so we reached the same conclusion independently.

Gated the constants and the test on target_os = "linux" rather than comparing against a literal. O_TMPFILE exists on Linux alone and macFUSE never sends this opcode, so a Linux-only test is the honest description; a portable literal would have kept it compiling everywhere while testing a value that means nothing on those platforms.

The end-to-end test needed nothing: it is in a module that is already #[cfg(all(test, target_os = "linux"))].

The real mistake was in how I checked before pushing. I ran cargo check --target x86_64-apple-darwin --features=macos-no-mount, which builds only the lib, while mac-ci builds --all-targets. Now verified with --all-targets on both x86_64-apple-darwin and x86_64-unknown-freebsd, which are clean.


Generated by Claude Code

@cberner
cberner force-pushed the claude/github-issues-triage-dv6dhi branch from 354de0f to b906df2 Compare August 4, 2026 03:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b906df2f4e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/session.rs
Comment on lines +918 to +922
let opened = nix::fcntl::open(
&mountpoint,
nix::fcntl::OFlag::O_TMPFILE | nix::fcntl::OFlag::O_RDWR,
nix::sys::stat::Mode::from_bits_truncate(0o600),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip tmpfile test on old FUSE kernels

On Linux kernels whose negotiated FUSE protocol is older than 7.37, this O_TMPFILE open is rejected by the kernel with EOPNOTSUPP before fuser sees any request, so the later opened.expect(...) panics even though the library still supports older kernels (the session only rejects ABI versions below 7.6). Please skip based on negotiated ABI/kernel support rather than requiring FUSE_TMPFILE for every Linux test run.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid, and worse than the version numbers suggest. Fixed in 1558748.

I checked which kernel this actually landed in rather than going by the ABI number: fuse_tmpfile is absent in v6.0 and present in v6.1. And the guard is not in fuse at all -- vfs_tmpfile() returns before touching the filesystem:

	if (!dir->i_op->tmpfile)
		return -EOPNOTSUPP;

So on anything older than 6.1 the open fails without a request being sent, and the test would have failed for anyone on 5.15, 5.14 or similar, which fuser supports perfectly well. Not a niche case.

The test now reads Session::proto_version after the handshake and skips below 7.37, in the style of the existing session_ends_cleanly_after_abort skip. Since fuser advertises 7.44, the negotiated version is the kernel's own, so it is a faithful proxy for whether the inode operation exists. Verified both paths: the skip prints and returns cleanly when forced, and the real path still exercises the round trip on this kernel.


Generated by Claude Code

The kernel sends this for open() with O_TMPFILE, asking for a file that has
no name and belongs to no directory until linkat() gives it one. Without it
fuser answered the opcode with ENOSYS from the unknown-operation path, and
since the kernel takes that as permanent, O_TMPFILE was reported to callers
as EOPNOTSUPP for the life of the connection. That is still what happens for
a filesystem that leaves the method unimplemented.

On the wire this is a create request, so it carries a name, but the name is
the placeholder "/" the VFS gives every anonymous dentry. It says nothing and
is not passed on, which is also what libfuse's tmpfile handler does.

Fixes #366

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5akmePV1VnD9YRQLZH4gv
@cberner
cberner force-pushed the claude/github-issues-triage-dv6dhi branch from b906df2 to 1558748 Compare August 4, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add FUSE_TMPFILE

2 participants