refactor(fileio): move list-file ordering to Rust
Move the single-file --list status gates, diagnostic-versus-metadata ordering, and aggregate projection into Rust. C retains file opening and frame analysis, the private fileInfo_t layout, exact diagnostics, and row formatting behind explicit callbacks, so the user-visible behavior remains unchanged while the policy boundary is auditable. Test Plan: - git diff --cached --check - worker format, check, clippy, and serial C compilation (passed); focused Rust tests compiled but the standalone link hit the pre-existing C bridge-symbol gap
This commit is contained in:
+294
-2
@@ -241,6 +241,7 @@ pub struct FIO_compressionParameters {
|
||||
|
||||
/// C's private `fileInfo_t` from `programs/fileio.c`.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct FIO_fileInfo_t {
|
||||
decompressedSize: u64,
|
||||
compressedSize: u64,
|
||||
@@ -255,8 +256,8 @@ pub struct FIO_fileInfo_t {
|
||||
}
|
||||
|
||||
/// The subset of C's private `fileInfo_t` needed by the `--list` aggregate
|
||||
/// row. C keeps opening, parsing, and formatting each file; Rust owns only
|
||||
/// the policy that iterates and aggregates these values.
|
||||
/// row. C keeps opening, parsing, and formatting each file; Rust owns the
|
||||
/// per-file and multi-file policy around those callbacks.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct FIO_listFileInfoProjection_t {
|
||||
@@ -729,6 +730,33 @@ pub type FIO_listMultipleFilesListFileFn = unsafe extern "C" fn(
|
||||
pub type FIO_listMultipleFilesDisplayTotalFn =
|
||||
unsafe extern "C" fn(*mut c_void, *const FIO_listFileInfoProjection_t);
|
||||
|
||||
pub type FIO_listFileGetInfoFn = unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
*const c_char,
|
||||
*mut FIO_fileInfo_t,
|
||||
) -> c_int;
|
||||
pub type FIO_listFileDisplayStatusFn =
|
||||
unsafe extern "C" fn(*mut c_void, c_int, *const c_char, c_int);
|
||||
pub type FIO_listFileDisplayInfoFn = unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
*const c_char,
|
||||
*const FIO_fileInfo_t,
|
||||
c_int,
|
||||
);
|
||||
|
||||
/// Callbacks for the C-owned `--list` file operation.
|
||||
///
|
||||
/// The callbacks keep stat/open, frame analysis, exact status diagnostics, and
|
||||
/// row formatting in C. Rust only orders those operations and publishes the
|
||||
/// small aggregate projection consumed by the surrounding multi-file policy.
|
||||
#[repr(C)]
|
||||
pub struct FIO_listFileCallbacks_t {
|
||||
opaque: *mut c_void,
|
||||
getInfo: Option<FIO_listFileGetInfoFn>,
|
||||
displayStatus: Option<FIO_listFileDisplayStatusFn>,
|
||||
displayInfo: Option<FIO_listFileDisplayInfoFn>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn list_file_status_action(status: c_int) -> c_int {
|
||||
match status {
|
||||
@@ -750,6 +778,85 @@ pub extern "C" fn FIO_rust_listFileStatusAction(status: c_int) -> c_int {
|
||||
list_file_status_action(status)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn project_list_file_info(info: &FIO_fileInfo_t) -> FIO_listFileInfoProjection_t {
|
||||
FIO_listFileInfoProjection_t {
|
||||
decompressedSize: info.decompressedSize,
|
||||
compressedSize: info.compressedSize,
|
||||
numActualFrames: info.numActualFrames,
|
||||
numSkippableFrames: info.numSkippableFrames,
|
||||
decompUnavailable: info.decompUnavailable,
|
||||
usesCheck: info.usesCheck,
|
||||
nbFiles: info.nbFiles,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the policy and ordering around one C-owned `--list` file operation.
|
||||
///
|
||||
/// C retains the stat/open and frame-analysis callback, all user-visible
|
||||
/// status diagnostics, and the full metadata row formatter. Rust preserves
|
||||
/// the original status gates: fatal statuses are reported and return `1`, a
|
||||
/// frame error reports a diagnostic but still formats and publishes its
|
||||
/// partial metadata, and success formats and publishes normally.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn FIO_rust_listFile(
|
||||
input_name: *const c_char,
|
||||
display_level: c_int,
|
||||
output: *mut FIO_listFileInfoProjection_t,
|
||||
callbacks: *const FIO_listFileCallbacks_t,
|
||||
) -> c_int {
|
||||
assert!(!input_name.is_null());
|
||||
assert!(!output.is_null());
|
||||
assert!(!callbacks.is_null());
|
||||
|
||||
let callbacks = unsafe { &*callbacks };
|
||||
let get_info = callbacks
|
||||
.getInfo
|
||||
.expect("--list file-info callback is required");
|
||||
let display_status = callbacks
|
||||
.displayStatus
|
||||
.expect("--list status-display callback is required");
|
||||
let display_info = callbacks
|
||||
.displayInfo
|
||||
.expect("--list info-display callback is required");
|
||||
|
||||
let mut info = unsafe { std::mem::zeroed::<FIO_fileInfo_t>() };
|
||||
let status = unsafe { get_info(callbacks.opaque, input_name, &mut info) };
|
||||
let action = list_file_status_action(status);
|
||||
|
||||
match action {
|
||||
FIO_RUST_LIST_FILE_ACTION_FRAME_ERROR
|
||||
| FIO_RUST_LIST_FILE_ACTION_NOT_ZSTD
|
||||
| FIO_RUST_LIST_FILE_ACTION_FILE_ERROR
|
||||
| FIO_RUST_LIST_FILE_ACTION_TRUNCATED_INPUT => unsafe {
|
||||
display_status(callbacks.opaque, action, input_name, display_level);
|
||||
},
|
||||
FIO_RUST_LIST_FILE_ACTION_SUCCESS | FIO_RUST_LIST_FILE_ACTION_INVALID => {}
|
||||
_ => unreachable!("unknown --list file action {action}"),
|
||||
}
|
||||
|
||||
match action {
|
||||
FIO_RUST_LIST_FILE_ACTION_NOT_ZSTD
|
||||
| FIO_RUST_LIST_FILE_ACTION_FILE_ERROR
|
||||
| FIO_RUST_LIST_FILE_ACTION_TRUNCATED_INPUT => 1,
|
||||
FIO_RUST_LIST_FILE_ACTION_SUCCESS | FIO_RUST_LIST_FILE_ACTION_FRAME_ERROR => {
|
||||
unsafe { display_info(callbacks.opaque, input_name, &info, display_level) };
|
||||
unsafe { output.write(project_list_file_info(&info)) };
|
||||
status
|
||||
}
|
||||
FIO_RUST_LIST_FILE_ACTION_INVALID => {
|
||||
unsafe { display_info(callbacks.opaque, input_name, &info, display_level) };
|
||||
unsafe { output.write(project_list_file_info(&info)) };
|
||||
debug_assert!(
|
||||
action == FIO_RUST_LIST_FILE_ACTION_SUCCESS
|
||||
|| action == FIO_RUST_LIST_FILE_ACTION_FRAME_ERROR
|
||||
);
|
||||
status
|
||||
}
|
||||
_ => unreachable!("unknown --list file action {action}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn remove_file_action(status: c_int) -> c_int {
|
||||
match status {
|
||||
@@ -3296,6 +3403,57 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ListFileTestState {
|
||||
status: c_int,
|
||||
info: FIO_fileInfo_t,
|
||||
events: Vec<&'static str>,
|
||||
status_actions: Vec<(c_int, c_int)>,
|
||||
displayed: Option<(FIO_fileInfo_t, c_int)>,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn list_file_get_info(
|
||||
opaque: *mut c_void,
|
||||
_file_name: *const c_char,
|
||||
info: *mut FIO_fileInfo_t,
|
||||
) -> c_int {
|
||||
let state = unsafe { &mut *opaque.cast::<ListFileTestState>() };
|
||||
state.events.push("get-info");
|
||||
unsafe { info.write(state.info) };
|
||||
state.status
|
||||
}
|
||||
|
||||
unsafe extern "C" fn list_file_display_status(
|
||||
opaque: *mut c_void,
|
||||
action: c_int,
|
||||
_file_name: *const c_char,
|
||||
display_level: c_int,
|
||||
) {
|
||||
let state = unsafe { &mut *opaque.cast::<ListFileTestState>() };
|
||||
state.events.push("display-status");
|
||||
state.status_actions.push((action, display_level));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn list_file_display_info(
|
||||
opaque: *mut c_void,
|
||||
_file_name: *const c_char,
|
||||
info: *const FIO_fileInfo_t,
|
||||
display_level: c_int,
|
||||
) {
|
||||
let state = unsafe { &mut *opaque.cast::<ListFileTestState>() };
|
||||
state.events.push("display-info");
|
||||
state.displayed = Some((unsafe { *info }, display_level));
|
||||
}
|
||||
|
||||
fn list_file_callbacks(state: &mut ListFileTestState) -> FIO_listFileCallbacks_t {
|
||||
FIO_listFileCallbacks_t {
|
||||
opaque: (state as *mut ListFileTestState).cast(),
|
||||
getInfo: Some(list_file_get_info),
|
||||
displayStatus: Some(list_file_display_status),
|
||||
displayInfo: Some(list_file_display_info),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "decompression")]
|
||||
struct TemporaryCFile(*mut libc::FILE);
|
||||
|
||||
@@ -4031,6 +4189,140 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_file_policy_preserves_status_order_and_projection_gates() {
|
||||
let file_name = CString::new("input.zst").unwrap();
|
||||
let info = FIO_fileInfo_t {
|
||||
decompressedSize: 100,
|
||||
compressedSize: 40,
|
||||
windowSize: 32,
|
||||
numActualFrames: 2,
|
||||
numSkippableFrames: 1,
|
||||
decompUnavailable: 0,
|
||||
usesCheck: 1,
|
||||
checksum: [1, 2, 3, 4],
|
||||
nbFiles: 1,
|
||||
dictID: 7,
|
||||
};
|
||||
let expected_projection = project_list_file_info(&info);
|
||||
let display_level = 3;
|
||||
|
||||
for (status, expected_action, expected_return, publishes_info) in [
|
||||
(
|
||||
FIO_LIST_INFO_SUCCESS,
|
||||
FIO_RUST_LIST_FILE_ACTION_SUCCESS,
|
||||
0,
|
||||
true,
|
||||
),
|
||||
(
|
||||
FIO_LIST_INFO_FRAME_ERROR,
|
||||
FIO_RUST_LIST_FILE_ACTION_FRAME_ERROR,
|
||||
1,
|
||||
true,
|
||||
),
|
||||
(
|
||||
FIO_LIST_INFO_NOT_ZSTD,
|
||||
FIO_RUST_LIST_FILE_ACTION_NOT_ZSTD,
|
||||
1,
|
||||
false,
|
||||
),
|
||||
(
|
||||
FIO_LIST_INFO_FILE_ERROR,
|
||||
FIO_RUST_LIST_FILE_ACTION_FILE_ERROR,
|
||||
1,
|
||||
false,
|
||||
),
|
||||
(
|
||||
FIO_LIST_INFO_TRUNCATED_INPUT,
|
||||
FIO_RUST_LIST_FILE_ACTION_TRUNCATED_INPUT,
|
||||
1,
|
||||
false,
|
||||
),
|
||||
] {
|
||||
let mut state = ListFileTestState {
|
||||
status,
|
||||
info,
|
||||
..ListFileTestState::default()
|
||||
};
|
||||
let callbacks = list_file_callbacks(&mut state);
|
||||
let sentinel = FIO_listFileInfoProjection_t {
|
||||
decompressedSize: 9,
|
||||
compressedSize: 8,
|
||||
numActualFrames: 7,
|
||||
numSkippableFrames: 6,
|
||||
decompUnavailable: 5,
|
||||
usesCheck: 4,
|
||||
nbFiles: 3,
|
||||
};
|
||||
let mut output = sentinel;
|
||||
|
||||
let result = unsafe {
|
||||
FIO_rust_listFile(
|
||||
file_name.as_ptr(),
|
||||
display_level,
|
||||
&mut output,
|
||||
&callbacks,
|
||||
)
|
||||
};
|
||||
|
||||
assert_eq!(result, expected_return, "status {status}");
|
||||
assert_eq!(
|
||||
state.events,
|
||||
if publishes_info {
|
||||
if expected_action == FIO_RUST_LIST_FILE_ACTION_FRAME_ERROR {
|
||||
vec!["get-info", "display-status", "display-info"]
|
||||
} else {
|
||||
vec!["get-info", "display-info"]
|
||||
}
|
||||
} else {
|
||||
vec!["get-info", "display-status"]
|
||||
},
|
||||
"status {status} event order"
|
||||
);
|
||||
assert_eq!(
|
||||
state.status_actions,
|
||||
if expected_action == FIO_RUST_LIST_FILE_ACTION_SUCCESS {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![(expected_action, display_level)]
|
||||
},
|
||||
"status {status} diagnostic action"
|
||||
);
|
||||
assert_eq!(
|
||||
state.displayed,
|
||||
publishes_info.then_some((info, display_level)),
|
||||
"status {status} display gate"
|
||||
);
|
||||
assert_eq!(
|
||||
output,
|
||||
if publishes_info {
|
||||
expected_projection
|
||||
} else {
|
||||
sentinel
|
||||
},
|
||||
"status {status} projection gate"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_file_callback_bridge_matches_c_layout() {
|
||||
let word = size_of::<*mut c_void>();
|
||||
let callback = size_of::<Option<FIO_listFileGetInfoFn>>();
|
||||
|
||||
assert_eq!(offset_of!(FIO_listFileCallbacks_t, opaque), 0);
|
||||
assert_eq!(offset_of!(FIO_listFileCallbacks_t, getInfo), word);
|
||||
assert_eq!(
|
||||
offset_of!(FIO_listFileCallbacks_t, displayStatus),
|
||||
word + callback
|
||||
);
|
||||
assert_eq!(
|
||||
offset_of!(FIO_listFileCallbacks_t, displayInfo),
|
||||
word + 2 * callback
|
||||
);
|
||||
assert_eq!(size_of::<FIO_listFileCallbacks_t>(), word + 3 * callback);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_info_layout_matches_private_c_record() {
|
||||
let word = size_of::<u64>();
|
||||
|
||||
Reference in New Issue
Block a user