Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions curvine-common/proto/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ message FileStatusProto {
required uint32 mode = 17;
optional string target = 18;
required uint32 nlink = 19;
optional int64 ctime = 20; // Metadata change time in milliseconds
}

// Describe the worker address information.
Expand Down
12 changes: 12 additions & 0 deletions curvine-common/src/state/file_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ use orpc::ternary;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// NUL cannot appear in a FUSE xattr name, so this persisted metadata key cannot
// collide with a user-visible extended attribute.
pub const INTERNAL_CTIME_XATTR: &str = "\0curvine.ctime";
Comment thread
ljy1-lixing marked this conversation as resolved.

Comment thread
ljy1-lixing marked this conversation as resolved.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct FileStatus {
pub id: i64,
Expand Down Expand Up @@ -48,6 +52,14 @@ pub struct FileStatus {
}

impl FileStatus {
pub fn ctime(&self) -> i64 {
self.x_attr
.get(INTERNAL_CTIME_XATTR)
.and_then(|bytes| bytes.as_slice().try_into().ok())
.map(i64::from_le_bytes)
.unwrap_or(self.mtime)
}

pub fn with_name(id: i64, name: String, is_dir: bool) -> Self {
FileStatus {
id,
Expand Down
2 changes: 1 addition & 1 deletion curvine-common/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ mod block_info;
pub use self::block_info::*;

mod file_status;
pub use self::file_status::FileStatus;
pub use self::file_status::{FileStatus, INTERNAL_CTIME_XATTR};

mod master_info;
pub use self::master_info::MasterInfo;
Expand Down
7 changes: 6 additions & 1 deletion curvine-common/src/state/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@ pub struct SetAttrOpts {
impl SetAttrOpts {
// Recursive setting, only allows setting acl related attributes
pub fn child_opts(&self) -> Self {
let mut add_x_attr = HashMap::new();
if let Some(ctime) = self.add_x_attr.get(INTERNAL_CTIME_XATTR) {
add_x_attr.insert(INTERNAL_CTIME_XATTR.to_string(), ctime.clone());
}

Self {
recursive: false,
replicas: self.replicas,
Expand All @@ -387,7 +392,7 @@ impl SetAttrOpts {
mtime: None,
ttl_ms: None,
ttl_action: None,
add_x_attr: HashMap::default(),
add_x_attr,
remove_x_attr: vec![],
ufs_mtime: None,
}
Expand Down
37 changes: 34 additions & 3 deletions curvine-common/src/utils/proto_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ impl ProtoUtils {
}
}

pub fn file_status_to_pb(status: FileStatus) -> FileStatusProto {
pub fn file_status_to_pb(mut status: FileStatus) -> FileStatusProto {
let ctime = status.ctime();
status.x_attr.remove(INTERNAL_CTIME_XATTR);
FileStatusProto {
Comment thread
ljy1-lixing marked this conversation as resolved.
id: status.id,
path: status.path,
Expand All @@ -238,10 +240,18 @@ impl ProtoUtils {
mode: status.mode,
target: status.target,
nlink: status.nlink,
ctime: Some(ctime),
Comment thread
ljy1-lixing marked this conversation as resolved.
Outdated
}
}

pub fn file_status_from_pb(status: FileStatusProto) -> FileStatus {
let mut x_attr = status.x_attr;
if let Some(ctime) = status.ctime {
x_attr.insert(
INTERNAL_CTIME_XATTR.to_string(),
ctime.to_le_bytes().to_vec(),
);
}
FileStatus {
id: status.id,
path: status.path,
Expand All @@ -255,7 +265,7 @@ impl ProtoUtils {
replicas: status.replicas,
block_size: status.block_size,
file_type: FileType::from(status.file_type),
x_attr: status.x_attr,
x_attr,
storage_policy: Self::storage_policy_from_pb(status.storage_policy),
owner: status.owner,
group: status.group,
Expand Down Expand Up @@ -724,8 +734,9 @@ impl ProtoUtils {

#[cfg(test)]
mod tests {
use crate::state::{AccessMode, MountInfo, MountOptions};
use crate::state::{AccessMode, FileStatus, MountInfo, MountOptions, INTERNAL_CTIME_XATTR};
use crate::utils::ProtoUtils;
use std::collections::HashMap;

#[test]
fn test_mount_info_proto_round_trip_auto_cache_and_access_mode() {
Expand Down Expand Up @@ -758,4 +769,24 @@ mod tests {
assert_eq!(round_trip.auto_cache, Some(false));
assert_eq!(round_trip.access_mode, Some(AccessMode::ReadWrite));
}

#[test]
fn file_status_proto_preserves_ctime_and_falls_back_for_legacy_peers() {
let status = FileStatus {
mtime: 1_000,
x_attr: HashMap::from([(
INTERNAL_CTIME_XATTR.to_string(),
2_000_i64.to_le_bytes().to_vec(),
)]),
..Default::default()
};

let mut pb = ProtoUtils::file_status_to_pb(status);
assert_eq!(pb.ctime, Some(2_000));
assert!(!pb.x_attr.contains_key(INTERNAL_CTIME_XATTR));
assert_eq!(ProtoUtils::file_status_from_pb(pb.clone()).ctime(), 2_000);

pb.ctime = None;
assert_eq!(ProtoUtils::file_status_from_pb(pb).ctime(), 1_000);
}
}
6 changes: 4 additions & 2 deletions curvine-fuse/src/fs/state/node_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,8 +913,10 @@ impl NodeState {
if let Some(mtime) = self.get_writer_mtime(attr.ino).await {
attr.mtime = (mtime.max(0) / 1000) as u64;
attr.mtimensec = ((mtime.max(0) % 1000) * 1_000_000) as u32;
attr.ctime = attr.mtime;
attr.ctimensec = attr.mtimensec;
if (attr.mtime, attr.mtimensec) > (attr.ctime, attr.ctimensec) {
attr.ctime = attr.mtime;
attr.ctimensec = attr.mtimensec;
}
}
}

Expand Down
23 changes: 21 additions & 2 deletions curvine-fuse/src/fuse_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,8 +451,11 @@ impl FuseUtils {
let atime_sec = (status.atime.max(0) / 1000) as u64;
let atime_nsec = ((status.atime.max(0) % 1000) * 1_000_000) as u32;

let ctime_sec = mtime_sec;
let ctime_nsec = mtime_nsec;
// Legacy/object-store statuses may not provide ctime yet. Falling back to mtime
// preserves the previous behavior without hiding a real independent ctime.
let ctime = status.ctime();
let ctime_sec = (ctime.max(0) / 1000) as u64;
let ctime_nsec = ((ctime.max(0) % 1000) * 1_000_000) as u32;

let uid = if status.owner.is_empty() {
conf.uid
Expand Down Expand Up @@ -679,6 +682,7 @@ impl FuseUtils {
mod tests {
use super::*;
use crate::raw::fuse_abi::fuse_setattr_in;
use curvine_common::state::INTERNAL_CTIME_XATTR;

#[test]
fn protected_xattr_errors_match_operation() {
Expand Down Expand Up @@ -944,6 +948,21 @@ mod tests {
assert!(FuseUtils::status_to_attr(&conf, &link).is_ok());
}

#[test]
fn status_to_attr_preserves_independent_ctime() {
let conf = FuseConf::default();
let mut status = file_status(FileType::File, 0, 0o644);
status.mtime = 1_000;
status.x_attr.insert(
INTERNAL_CTIME_XATTR.to_string(),
2_500_i64.to_le_bytes().to_vec(),
);

let attr = FuseUtils::status_to_attr(&conf, &status).unwrap();
assert_eq!((attr.mtime, attr.mtimensec), (1, 0));
assert_eq!((attr.ctime, attr.ctimensec), (2, 500_000_000));
}

#[test]
fn blocks_derived_from_fuse_size() {
let conf = FuseConf::default();
Expand Down
10 changes: 9 additions & 1 deletion curvine-server/src/master/meta/fs_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use curvine_common::error::FsError;
use curvine_common::state::{
BlockLocation, CommitBlock, CreateFileOpts, ExtendedBlock, FileAllocOpts, FileLock, FileStatus,
FreeResult, ListOptions, MkdirOpts, MountInfo, RenameFlags, SetAttrOpts, WorkerAddress,
INTERNAL_CTIME_XATTR,
};
use curvine_common::FsResult;
use log::{debug, info, warn};
Expand Down Expand Up @@ -848,12 +849,19 @@ impl FsDir {
self.store.get_mount_point(id)
}

pub fn set_attr(&mut self, inp: InodePath, opts: SetAttrOpts) -> FsResult<FileStatus> {
pub fn set_attr(&mut self, inp: InodePath, mut opts: SetAttrOpts) -> FsResult<FileStatus> {
let inode = match inp.get_last_inode() {
Some(v) => v,
None => return err_ext!(FsError::file_not_found(inp.path())),
};

// Persist the operation timestamp inside the existing xattr map so journal
// replay restores the original ctime without changing the bincode layout.
let ctime = LocalTime::mills() as i64;
Comment thread
ljy1-lixing marked this conversation as resolved.
opts.add_x_attr.insert(
INTERNAL_CTIME_XATTR.to_string(),
ctime.to_le_bytes().to_vec(),
);
self.unprotected_set_attr(inode.clone(), opts.clone())?;
self.journal_writer.log_set_attr(self, &inp, opts)?;
Ok(inode.to_file_status(inp.path())?)
Expand Down
11 changes: 10 additions & 1 deletion curvine-server/src/master/meta/inode/inode_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::master::meta::inode::inodes_children::InodeChildren;
use crate::master::meta::inode::{
ChildrenIter, Inode, InodeFile, InodePtr, InodeView, EMPTY_PARENT_ID,
};
use curvine_common::state::{ListOptions, MkdirOpts, StoragePolicy};
use curvine_common::state::{ListOptions, MkdirOpts, StoragePolicy, INTERNAL_CTIME_XATTR};
use glob::Pattern;
use orpc::CommonResult;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -160,6 +160,15 @@ impl Inode for InodeDir {
fn atime(&self) -> i64 {
self.atime
}

fn ctime(&self) -> i64 {
self.features
.x_attr
.get(INTERNAL_CTIME_XATTR)
.and_then(|bytes| bytes.as_slice().try_into().ok())
.map(i64::from_le_bytes)
.unwrap_or(self.mtime)
}
}

impl PartialEq for InodeDir {
Expand Down
11 changes: 10 additions & 1 deletion curvine-server/src/master/meta/inode/inode_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::master::meta::store::InodeStore;
use crate::master::meta::{BlockMeta, InodeId};
use curvine_common::state::{
is_special_file_type, BlockLocation, CommitBlock, CreateFileOpts, ExtendedBlock, FileAllocOpts,
FileType, StoragePolicy,
FileType, StoragePolicy, INTERNAL_CTIME_XATTR,
};
use curvine_common::FsResult;
use orpc::common::LocalTime;
Expand Down Expand Up @@ -575,6 +575,15 @@ impl Inode for InodeFile {
self.atime
}

fn ctime(&self) -> i64 {
self.features
.x_attr
.get(INTERNAL_CTIME_XATTR)
.and_then(|bytes| bytes.as_slice().try_into().ok())
.map(i64::from_le_bytes)
.unwrap_or(self.mtime)
}

fn nlink(&self) -> u32 {
self.nlink
}
Expand Down
70 changes: 69 additions & 1 deletion curvine-server/src/master/meta/inode/inode_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ use crate::master::meta::inode::InodeView::{Dir, File, FileEntry};
use crate::master::meta::inode::{
Inode, InodeDir, InodeFile, InodePtr, PATH_SEPARATOR, ROOT_INODE_ID,
};
use curvine_common::state::{FileStatus, FileType, SetAttrOpts, StoragePolicy, TtlAction};
use curvine_common::state::{
FileStatus, FileType, SetAttrOpts, StoragePolicy, TtlAction, INTERNAL_CTIME_XATTR,
};
use curvine_common::utils::SerdeUtils;
use orpc::common::{LocalTime, Utils};
use orpc::{err_box, CommonResult};
Expand Down Expand Up @@ -236,6 +238,14 @@ impl InodeView {
}
}

pub fn ctime(&self) -> i64 {
match self {
File(f) => f.ctime(),
Dir(d) => d.ctime(),
FileEntry(..) => 0,
}
}

pub fn update_mtime(&mut self, time: i64) {
match self {
File(f) => {
Expand Down Expand Up @@ -416,6 +426,25 @@ impl InodeView {
}

pub fn set_attr(&mut self, opts: SetAttrOpts) -> CommonResult<()> {
let ctime = opts
.add_x_attr
.get(INTERNAL_CTIME_XATTR)
.cloned()
.unwrap_or_else(|| (LocalTime::mills() as i64).to_le_bytes().to_vec());
Comment thread
ljy1-lixing marked this conversation as resolved.
Outdated
match self {
File(f) => {
f.features
.x_attr
.insert(INTERNAL_CTIME_XATTR.to_string(), ctime.clone());
}
Dir(d) => {
d.features
.x_attr
.insert(INTERNAL_CTIME_XATTR.to_string(), ctime);
}
FileEntry(..) => (),
}

if let Some(owner) = opts.owner {
self.acl_mut()?.owner = owner;
}
Expand Down Expand Up @@ -614,3 +643,42 @@ impl PartialEq for InodeView {
self.id() == other.id()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn setattr_updates_ctime_when_mtime_is_omitted() {
let mut inode = InodeView::new_file("file".to_string(), InodeFile::new(1, 1));
inode
.set_attr(SetAttrOpts {
atime: Some(2),
mtime: None,
..Default::default()
})
.unwrap();

let status = inode.to_file_status("/file").unwrap();
assert_eq!(status.atime, 2);
assert_eq!(status.mtime, 1);
assert!(status.ctime() > status.mtime);
}

#[test]
fn setattr_reuses_persisted_ctime_during_replay() {
let mut inode = InodeView::new_file("file".to_string(), InodeFile::new(1, 1));
inode
.set_attr(SetAttrOpts {
atime: Some(2),
add_x_attr: HashMap::from([(
INTERNAL_CTIME_XATTR.to_string(),
3_i64.to_le_bytes().to_vec(),
)]),
..Default::default()
})
.unwrap();

assert_eq!(inode.ctime(), 3);
}
}
3 changes: 3 additions & 0 deletions curvine-server/src/master/meta/inode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ pub trait Inode {
// Access time
fn atime(&self) -> i64;

// Metadata change time
fn ctime(&self) -> i64;

// nlink
fn nlink(&self) -> u32;
}
Loading