Skip to content

fix(fuse): update ctime for futimens#1296

Merged
szbr9486 merged 2 commits into
CurvineIO:mainfrom
ljy1-lixing:fix/fuse-futimens-ctime
Jul 25, 2026
Merged

fix(fuse): update ctime for futimens#1296
szbr9486 merged 2 commits into
CurvineIO:mainfrom
ljy1-lixing:fix/fuse-futimens-ctime

Conversation

@ljy1-lixing

Copy link
Copy Markdown
Contributor

Summary

Fix FUSE futimens() failing to update ctime when metadata changes without changing mtime.

Curvine now tracks an independent metadata change timestamp across Master persistence, journal replay, RPC transport, and FUSE attribute replies.

Root cause

Curvine's metadata model only exposed atime and mtime. The FUSE attribute conversion derived ctime directly from mtime.

When futimens() updated atime while specifying UTIME_OMIT for mtime, the metadata operation succeeded, but the returned ctime remained unchanged because mtime had not changed.

The failure is reproduced by xfstests generic/221:

struct timespec times[2] = {
    { .tv_sec = 1000000000, .tv_nsec = 0 },
    { .tv_sec = 0, .tv_nsec = UTIME_OMIT },
};

fstat(fd, &before);
sleep(1);
futimens(fd, times);
fstat(fd, &after);

Before this fix:

ctime before: 1784884658.899000000
ctime after:  1784884658.899000000
FAIL: futimens did not update ctime

Fix approach

  • Record an independent ctime whenever the Master applies setattr.
  • Persist it through the existing inode metadata extension map instead of changing the bincode inode layout.
  • Store the operation timestamp in the journal entry so replay preserves the original ctime.
  • Add an optional ctime field to FileStatusProto.
  • Fall back to mtime when communicating with legacy peers or backends that do not provide ctime.
  • Return the independent timestamp through FUSE attributes.
  • Prevent active-writer metadata merging from overwriting a newer ctime.

The internal persisted key starts with a NUL byte, which cannot occur in a FUSE xattr name, preventing collisions with user-visible extended attributes.

Changes

Module / File Change Impact on existing behavior
curvine-common/proto/common.proto Add optional ctime to FileStatusProto Backward-compatible RPC extension
curvine-common/src/state/file_status.rs Add internal ctime encoding and legacy fallback Existing statuses without ctime continue using mtime
curvine-common/src/state/opts.rs Preserve internal ctime during recursive setattr Journal replay retains the original operation time
curvine-common/src/utils/proto_utils.rs Encode and decode ctime separately from user xattrs Internal metadata is not exposed in protobuf xattr maps
curvine-server/src/master/meta/fs_dir.rs Assign ctime when applying setattr and include it in the journal Metadata-changing setattr operations now update ctime
curvine-server/src/master/meta/inode/* Read and persist independent inode ctime No change to the existing bincode inode structure
curvine-fuse/src/fuse_utils.rs Populate FUSE ctime from independent metadata stat(2) now reports POSIX-compatible ctime
curvine-fuse/src/fs/state/node_state.rs Preserve newer ctime while merging active-writer metadata Active writers no longer hide setattr ctime updates

Test verified

Test case Result Notes
Standalone futimens C reproducer PASS ctime advances while mtime is omitted
generic/221 PASS Ran: generic/221, Passed all 1 tests
cargo test -p curvine-common --lib PASS 100 passed
cargo test -p curvine-server --lib PASS 112 passed, 1 ignored
cargo test -p curvine-fuse --lib -- --test-threads=1 PASS 297 passed
cargo check --workspace PASS All workspace crates checked
make format PASS Full pre-commit checks passed
git diff --check PASS No whitespace errors

After the fix:

ctime before: 1784885529.607000000
ctime after:  1784885530.608000000
PASS: futimens updated ctime

Persist an independent metadata change timestamp across master storage, journal replay, RPC transport, and FUSE replies. Preserve compatibility with existing bincode inode layouts and legacy peers that do not send ctime.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes POSIX ctime reporting for FUSE futimens() when metadata changes without changing mtime, by introducing an independent metadata-change timestamp that is persisted, journal-replayed, transported over RPC, and surfaced via FUSE attributes.

Changes:

  • Introduces an internal persisted ctime representation (via an internal xattr key) and a FileStatus::ctime() accessor with legacy fallback to mtime.
  • Ensures Master set_attr records an operation-time ctime and propagates it through journaling/replay and inode persistence without changing the bincode inode layout.
  • Extends RPC (FileStatusProto) to carry optional ctime and updates FUSE attribute conversion and active-writer merge logic to preserve newer ctime.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
curvine-server/src/master/meta/inode/mod.rs Extends the Inode trait with ctime() support.
curvine-server/src/master/meta/inode/inode_view.rs Applies/reads internal ctime during set_attr and adds unit tests for omit-mtime scenarios and replay.
curvine-server/src/master/meta/inode/inode_file.rs Reads ctime from internal persisted xattr with fallback to mtime.
curvine-server/src/master/meta/inode/inode_dir.rs Reads ctime from internal persisted xattr with fallback to mtime.
curvine-server/src/master/meta/fs_dir.rs Stamps ctime at Master set_attr time and logs it for journal replay.
curvine-fuse/src/fuse_utils.rs Uses FileStatus::ctime() to populate FUSE ctime and adds a preservation test.
curvine-fuse/src/fs/state/node_state.rs Avoids overwriting a newer ctime when merging active-writer mtime.
curvine-common/src/utils/proto_utils.rs Adds ctime to protobuf encoding/decoding and hides internal ctime from protobuf xattr maps.
curvine-common/src/state/opts.rs Preserves internal ctime across recursive setattr via child_opts().
curvine-common/src/state/mod.rs Re-exports INTERNAL_CTIME_XATTR.
curvine-common/src/state/file_status.rs Introduces INTERNAL_CTIME_XATTR and FileStatus::ctime() accessor.
curvine-common/proto/common.proto Adds optional ctime field to FileStatusProto.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread curvine-common/src/utils/proto_utils.rs
Comment thread curvine-common/src/utils/proto_utils.rs Outdated
Comment thread curvine-common/src/state/file_status.rs
Comment thread curvine-common/src/state/file_status.rs
Comment thread curvine-server/src/master/meta/fs_dir.rs
Comment thread curvine-server/src/master/meta/inode/inode_view.rs Outdated
@szbr9486
szbr9486 merged commit d90f686 into CurvineIO:main Jul 25, 2026
3 checks passed
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.

3 participants