refactor(cli): move compressed source exclusion to Rust

The Rust source-file scheduler already owns the ordering around source
exclusion, but its suffix policy and diagnostic still depended on a C table
and helper.  That left the policy leaf on the C side of the boundary and
made the Rust scheduler call back into a C-owned decision.

Move the complete 113-entry, case-sensitive suffix policy into the Rust CLI
file-I/O layer and export the exclusion callback through the existing C ABI.
The callback preserves leading dots, the stdin and NULL non-match behavior,
the 0/1 return policy, and the exact display-level-4 diagnostic while leaving
C resource, compression, and asynchronous callbacks unchanged.  Focused
checks cover representative suffixes, case sensitivity, stdin/NULL handling,
and the display gate.

Test Plan:
- `cc -fsyntax-only -Iprograms -Ilib -Ilib/common programs/fileio.c` -- passed
- Exact extracted C/Rust suffix-list comparison -- passed; all 113 entries match
- `git diff --check` and `git diff --cached --check` -- passed
- Rust unit tests and Cargo/Make/native suites were not run per request
- `rustfmt +nightly --check --edition 2021 rust/src/fileio_prefs.rs` -- not clean
  because it reports pre-existing formatting drift in unchanged code
This commit is contained in:
2026-07-20 15:56:53 +02:00
parent 499886116b
commit 882ad7ccef
2 changed files with 211 additions and 134 deletions
+5 -132
View File
@@ -1659,9 +1659,10 @@ typedef int (*FIO_rust_compress_src_compress_fn)(void* opaque,
typedef void (*FIO_rust_compress_src_close_fn)(void* opaque); typedef void (*FIO_rust_compress_src_close_fn)(void* opaque);
typedef void (*FIO_rust_compress_src_remove_fn)(void* opaque, const char* srcFileName); typedef void (*FIO_rust_compress_src_remove_fn)(void* opaque, const char* srcFileName);
/* Rust owns the source policy; this projection exposes only scalar names and /* Rust owns the source policy and exclusion diagnostic; this projection
* opaque C callbacks. FIO_ctx_t, cRess_t, stat_t, and all resource/diagnostic * exposes only scalar names and opaque callbacks for C-owned operations.
* state remain private to the callback context below. */ * FIO_ctx_t, cRess_t, stat_t, and all resource state remain private to the
* callback context below. */
typedef struct { typedef struct {
void* opaque; void* opaque;
const char* dstFileName; const char* dstFileName;
@@ -1708,6 +1709,7 @@ typedef char FIO_rust_compress_src_projection_size[
+ 8 * sizeof(FIO_rust_compress_src_stat_fn)) ? 1 : -1]; + 8 * sizeof(FIO_rust_compress_src_stat_fn)) ? 1 : -1];
int FIO_rust_compressFilenameSrcFile( int FIO_rust_compressFilenameSrcFile(
const FIO_rust_compress_src_projection_t* projection); const FIO_rust_compress_src_projection_t* projection);
int FIO_rust_compressSourceIsExcluded(void* opaque, const char* srcFileName);
static void FIO_adjustParamsForPatchFromMode(FIO_prefs_t* const prefs, static void FIO_adjustParamsForPatchFromMode(FIO_prefs_t* const prefs,
ZSTD_compressionParameters* comprParams, ZSTD_compressionParameters* comprParams,
@@ -3158,126 +3160,6 @@ static int FIO_compressFilename_dstFile(FIO_ctx_t* const fCtx,
return FIO_rust_compressFilenameDstFile(&projection); return FIO_rust_compressFilenameDstFile(&projection);
} }
/* List used to compare file extensions (used with --exclude-compressed flag)
* Different from the suffixList and should only apply to ZSTD compress operationResult
*/
static const char *compressedFileExtensions[] = {
ZSTD_EXTENSION,
TZSTD_EXTENSION,
GZ_EXTENSION,
TGZ_EXTENSION,
LZMA_EXTENSION,
XZ_EXTENSION,
TXZ_EXTENSION,
LZ4_EXTENSION,
TLZ4_EXTENSION,
".7z",
".aa3",
".aac",
".aar",
".ace",
".alac",
".ape",
".apk",
".apng",
".arc",
".archive",
".arj",
".ark",
".asf",
".avi",
".avif",
".ba",
".br",
".bz2",
".cab",
".cdx",
".chm",
".cr2",
".divx",
".dmg",
".dng",
".docm",
".docx",
".dotm",
".dotx",
".dsft",
".ear",
".eftx",
".emz",
".eot",
".epub",
".f4v",
".flac",
".flv",
".gho",
".gif",
".gifv",
".gnp",
".iso",
".jar",
".jpeg",
".jpg",
".jxl",
".lz",
".lzh",
".m4a",
".m4v",
".mkv",
".mov",
".mp2",
".mp3",
".mp4",
".mpa",
".mpc",
".mpe",
".mpeg",
".mpg",
".mpl",
".mpv",
".msi",
".odp",
".ods",
".odt",
".ogg",
".ogv",
".otp",
".ots",
".ott",
".pea",
".png",
".pptx",
".qt",
".rar",
".s7z",
".sfx",
".sit",
".sitx",
".sqx",
".svgz",
".swf",
".tbz2",
".tib",
".tlz",
".vob",
".war",
".webm",
".webp",
".wma",
".wmv",
".woff",
".woff2",
".wvl",
".xlsx",
".xpi",
".xps",
".zip",
".zipx",
".zoo",
".zpaq",
NULL
};
typedef struct { typedef struct {
FIO_ctx_t* fCtx; FIO_ctx_t* fCtx;
FIO_prefs_t* prefs; FIO_prefs_t* prefs;
@@ -3311,15 +3193,6 @@ static int FIO_rust_compressSourceStat(void* opaque, const char* srcFileName)
return FIO_RUST_COMPRESS_SRC_STAT_OK; return FIO_RUST_COMPRESS_SRC_STAT_OK;
} }
static int FIO_rust_compressSourceIsExcluded(void* opaque, const char* srcFileName)
{
int const isCompressed = UTIL_isCompressedFile(srcFileName, compressedFileExtensions);
(void)opaque;
if (isCompressed)
DISPLAYLEVEL(4, "File is already compressed : %s \n", srcFileName);
return isCompressed;
}
static int FIO_rust_compressSourceOpen(void* opaque, const char* srcFileName, static int FIO_rust_compressSourceOpen(void* opaque, const char* srcFileName,
U64* fileSize) U64* fileSize)
{ {
+206 -2
View File
@@ -31,6 +31,121 @@ const FIO_MULTI_FILES_ACTION_CONFIRM: c_int = 5;
const FIO_LIST_INFO_SUCCESS: c_int = 0; const FIO_LIST_INFO_SUCCESS: c_int = 0;
const FIO_LIST_INFO_FRAME_ERROR: c_int = 1; const FIO_LIST_INFO_FRAME_ERROR: c_int = 1;
const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX; const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX;
const COMPRESSED_FILE_EXTENSIONS: &[&[u8]] = &[
b".zst",
b".tzst",
b".gz",
b".tgz",
b".lzma",
b".xz",
b".txz",
b".lz4",
b".tlz4",
b".7z",
b".aa3",
b".aac",
b".aar",
b".ace",
b".alac",
b".ape",
b".apk",
b".apng",
b".arc",
b".archive",
b".arj",
b".ark",
b".asf",
b".avi",
b".avif",
b".ba",
b".br",
b".bz2",
b".cab",
b".cdx",
b".chm",
b".cr2",
b".divx",
b".dmg",
b".dng",
b".docm",
b".docx",
b".dotm",
b".dotx",
b".dsft",
b".ear",
b".eftx",
b".emz",
b".eot",
b".epub",
b".f4v",
b".flac",
b".flv",
b".gho",
b".gif",
b".gifv",
b".gnp",
b".iso",
b".jar",
b".jpeg",
b".jpg",
b".jxl",
b".lz",
b".lzh",
b".m4a",
b".m4v",
b".mkv",
b".mov",
b".mp2",
b".mp3",
b".mp4",
b".mpa",
b".mpc",
b".mpe",
b".mpeg",
b".mpg",
b".mpl",
b".mpv",
b".msi",
b".odp",
b".ods",
b".odt",
b".ogg",
b".ogv",
b".otp",
b".ots",
b".ott",
b".pea",
b".png",
b".pptx",
b".qt",
b".rar",
b".s7z",
b".sfx",
b".sit",
b".sitx",
b".sqx",
b".svgz",
b".swf",
b".tbz2",
b".tib",
b".tlz",
b".vob",
b".war",
b".webm",
b".webp",
b".wma",
b".wmv",
b".woff",
b".woff2",
b".wvl",
b".xlsx",
b".xpi",
b".xps",
b".zip",
b".zipx",
b".zoo",
b".zpaq",
];
const FIO_ADAPT_WINDOWLOG_DEFAULT: c_uint = 23; const FIO_ADAPT_WINDOWLOG_DEFAULT: c_uint = 23;
const ZSTD_WINDOWLOG_MIN: u32 = 10; const ZSTD_WINDOWLOG_MIN: u32 = 10;
const ZSTD_WINDOWLOG_MAX: u32 = if size_of::<usize>() == 4 { 30 } else { 31 }; const ZSTD_WINDOWLOG_MAX: u32 = if size_of::<usize>() == 4 { 30 } else { 31 };
@@ -1173,12 +1288,61 @@ unsafe fn aio_supported() -> bool {
} }
fn display(level: c_int, message: &str) { fn display(level: c_int, message: &str) {
let enabled = unsafe { (*display_prefs()).displayLevel >= level }; if display_level_enabled(level) {
if enabled {
eprint!("{message}"); eprint!("{message}");
} }
} }
#[inline]
fn display_level_enabled(level: c_int) -> bool {
unsafe { (*display_prefs()).displayLevel >= level }
}
#[inline]
fn compressed_file_name_is_excluded(src_file_name: *const c_char) -> bool {
if src_file_name.is_null() {
return false;
}
let source = unsafe { CStr::from_ptr(src_file_name).to_bytes() };
let Some(extension_start) = source.iter().rposition(|&byte| byte == b'.') else {
return false;
};
if extension_start == 0 {
return false;
}
COMPRESSED_FILE_EXTENSIONS
.iter()
.any(|extension| *extension == &source[extension_start..])
}
fn display_already_compressed(src_file_name: *const c_char) {
if !display_level_enabled(4) {
return;
}
let source = unsafe { CStr::from_ptr(src_file_name).to_bytes() };
let mut stderr = io::stderr().lock();
let _ = stderr.write_all(b"File is already compressed : ");
let _ = stderr.write_all(source);
let _ = stderr.write_all(b" \n");
}
/// Return whether a source file has an extension excluded by
/// `--exclude-compressed`, preserving the C callback's exact return policy.
#[no_mangle]
pub unsafe extern "C" fn FIO_rust_compressSourceIsExcluded(
_opaque: *mut c_void,
src_file_name: *const c_char,
) -> c_int {
let is_compressed = compressed_file_name_is_excluded(src_file_name);
if is_compressed {
display_already_compressed(src_file_name);
}
c_int::from(is_compressed)
}
fn checked_option(options: &[&'static str], index: c_int) -> &'static str { fn checked_option(options: &[&'static str], index: c_int) -> &'static str {
options options
.get(index as usize) .get(index as usize)
@@ -2296,6 +2460,46 @@ mod tests {
use std::fs; use std::fs;
use std::mem::{align_of, offset_of, size_of}; use std::mem::{align_of, offset_of, size_of};
#[test]
fn source_exclusion_preserves_case_sensitive_suffixes_and_null_policy() {
let cases = [
("archive.zst", 1),
("archive.tlz4", 1),
("photo.webp", 1),
("archive.zpaq", 1),
("archive.ZST", 0),
("archive.zstd", 0),
("plain.txt", 0),
(".mp3", 0),
("/*stdin*\\", 0),
];
for (file_name, expected) in cases {
let file_name = CString::new(file_name).unwrap();
assert_eq!(
unsafe { FIO_rust_compressSourceIsExcluded(ptr::null_mut(), file_name.as_ptr()) },
expected,
"unexpected exclusion result for {file_name:?}"
);
}
assert_eq!(
unsafe { FIO_rust_compressSourceIsExcluded(ptr::null_mut(), ptr::null()) },
0
);
}
#[test]
fn source_exclusion_diagnostic_is_gated_at_display_level_four() {
FIO_setNotificationLevel(3);
assert!(!display_level_enabled(4));
FIO_setNotificationLevel(4);
assert!(display_level_enabled(4));
FIO_setNotificationLevel(2);
}
#[derive(Default)] #[derive(Default)]
struct FreeCResourcesTestState { struct FreeCResourcesTestState {
events: Vec<&'static str>, events: Vec<&'static str>,