Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ struct InodeAttributes {
pub last_accessed: (i64, u32),
pub last_modified: (i64, u32),
pub last_metadata_changed: (i64, u32),
/// Time of creation, which `statx(2)` reports as `stx_btime` and nothing ever changes
pub created: (i64, u32),

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 Make created deserialization backward-compatible

When simple is started with a data_dir written by the previous version (including the default /tmp/fuser), the serialized inode records do not contain this new required field. init() immediately calls get_inode(ROOT), and get_inode unwraps rmp_serde::from_read, so those existing filesystems panic instead of mounting; please give created a migration/default path, such as deriving it from the existing timestamps on old records.

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.

Same answer as on #744, where this came up for the flags field and the PR merged as it was.

The example's data directory carries no format compatibility guarantee, and this has happened twice before without ceremony: 4c3e859 added rdev as a plain required field and 5ce9092 added flags, both mid-struct, both with no #[serde(default)] and no migration.

The suggested default would also not work here, for the same reason it would not have there. rmp_serde encodes structs as arrays, so what matters is field position, not field presence. created sits sixth of fifteen, between last_metadata_changed and kind, so an old record's kind lands in its slot and fails on the type rather than falling back to a default. Making this survive an upgrade means putting the field last, adding #[serde(default)], and writing down that ordering constraint so the next field addition does not silently undo it.

Deriving it from the existing timestamps is a good idea for a filesystem that wants this, and worth doing deliberately if the example should keep its data dir readable across versions - for every field rather than this one, and probably with a version in the superblock it already writes. Not as a side effect of adding a creation time.


Generated by Claude Code

pub kind: FileKind,
// Permissions and special mode bits
pub mode: u16,
Expand Down Expand Up @@ -519,7 +521,7 @@ impl From<InodeAttributes> for fuser::FileAttr {
attrs.last_metadata_changed.0,
attrs.last_metadata_changed.1,
),
crtime: SystemTime::UNIX_EPOCH,
crtime: system_time_from_time(attrs.created.0, attrs.created.1),
kind: attrs.kind.into(),
perm: attrs.mode,
nlink: attrs.hardlinks,
Expand Down Expand Up @@ -891,6 +893,7 @@ impl Filesystem for SimpleFS {
last_accessed: now,
last_modified: now,
last_metadata_changed: now,
created: now,
kind: FileKind::Directory,
mode: 0o777,
hardlinks: 2,
Expand Down Expand Up @@ -1261,6 +1264,7 @@ impl Filesystem for SimpleFS {
last_accessed: now,
last_modified: now,
last_metadata_changed: now,
created: now,
kind: as_file_kind(mode),
mode: self.creation_mode(mode),
hardlinks: 1,
Expand Down Expand Up @@ -1358,6 +1362,7 @@ impl Filesystem for SimpleFS {
last_accessed: now,
last_modified: now,
last_metadata_changed: now,
created: now,
kind: FileKind::Directory,
mode: self.creation_mode(mode),
hardlinks: 2, // Directories start with link count of 2, since they have a self link
Expand Down Expand Up @@ -1575,6 +1580,7 @@ impl Filesystem for SimpleFS {
last_accessed: now,
last_modified: now,
last_metadata_changed: now,
created: now,
kind: FileKind::Symlink,
mode: 0o777,
hardlinks: 1,
Expand Down Expand Up @@ -1868,6 +1874,7 @@ impl Filesystem for SimpleFS {
last_accessed: now,
last_modified: now,
last_metadata_changed: now,
created: now,
kind: FileKind::CharDevice,
mode: 0,
hardlinks: 1,
Expand Down Expand Up @@ -2482,6 +2489,7 @@ impl Filesystem for SimpleFS {
last_accessed: now,
last_modified: now,
last_metadata_changed: now,
created: now,
kind: as_file_kind(mode),
mode: self.creation_mode(mode),
hardlinks: 1,
Expand Down Expand Up @@ -2515,6 +2523,57 @@ impl Filesystem for SimpleFS {
);
}

/// Answers `statx(2)`, which differs from `getattr()` in carrying a creation time. That
/// is the whole reason to implement it: `fuse_attr` has no field for one on Linux, so
/// without this the kernel reports whatever it last cached
#[cfg(target_os = "linux")]
fn statx(
&self,
_req: &Request,
ino: INodeNo,
_fh: Option<FileHandle>,
_flags: u32,
_mask: fuser::StatxMask,
reply: fuser::ReplyStatx,
) {
debug!("statx() called with {ino:?}");
let attrs = match self.get_inode(ino) {
Ok(attrs) => attrs,
Err(error_code) => {
reply.error(error_code);
return;
}
};

// The inode flags this filesystem honors, in the encoding statx uses. The kernel
// discards these today - fuse_do_statx() takes the creation time and the basic stats
// out of the reply and nothing else - so `chattr +i` stays invisible to statx(2)
// whatever is reported here. Filled in anyway, since it costs nothing and is what the
// field is for if the kernel starts reading it
let mut attributes = fuser::StatxAttributes::empty();
attributes.set(fuser::StatxAttributes::IMMUTABLE, attrs.is_immutable());
attributes.set(fuser::StatxAttributes::APPEND, attrs.is_append_only());
attributes.set(
fuser::StatxAttributes::NODUMP,
attrs.flags & FS_NODUMP_FL != 0,
);

let btime = system_time_from_time(attrs.created.0, attrs.created.1);
reply.statx(
&Duration::new(0, 0),
&fuser::StatxAttr {
btime: Some(btime),
attributes,
// The three above are the ones this filesystem can speak to. Leaving the rest
// out is what tells a caller "cannot say" rather than "not set"
attributes_mask: fuser::StatxAttributes::IMMUTABLE
| fuser::StatxAttributes::APPEND
| fuser::StatxAttributes::NODUMP,
..self.file_attr(attrs).into()
},
);
}

/// Serves the four ioctls the kernel turns chattr(1) and lsattr(1) into. The kernel does
/// the ownership and `CAP_LINUX_IMMUTABLE` checks before sending them, so all that is left
/// here is to store the flags and to refuse the ones this filesystem does not honor
Expand Down Expand Up @@ -2665,6 +2724,7 @@ impl Filesystem for SimpleFS {
last_accessed: now,
last_modified: now,
last_metadata_changed: now,
created: now,
// The kernel only ever asks for a regular file here
kind: FileKind::File,
mode: self.creation_mode(mode),
Expand Down
Loading