feat(fileio): move filename collision checks to Rust
Implement basename extraction, bytewise sorting, collision reporting, and allocation failure handling in the Rust fileio preferences module. Keep programs/fileio.c as the stable ABI shim and preserve non-UTF-8 filenames without lossy conversion. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml --no-default-features --features cli,compression,decompression - cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets --no-default-features --features cli,compression,decompression - make -B -C tests -j2 test-cli-tests - git diff --cached --check
This commit is contained in:
+2
-29
@@ -306,6 +306,7 @@ static int FIO_shouldDisplayMultipleFileSummary(FIO_ctx_t const* fCtx)
|
||||
|
||||
/* These symbols are implemented by rust/src/fileio_prefs.rs. The declarations
|
||||
* in fileio.h remain the C ABI shims while all actual file I/O stays here. */
|
||||
int FIO_rust_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles);
|
||||
|
||||
|
||||
/*-*************************************
|
||||
@@ -678,35 +679,7 @@ static void FIO_initDict(FIO_Dict_t* dict, const char* fileName, FIO_prefs_t* co
|
||||
* Checks for and warns if there are any files that would have the same output path
|
||||
*/
|
||||
int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) {
|
||||
const char **filenameTableSorted, *prevElem, *filename;
|
||||
unsigned u;
|
||||
|
||||
filenameTableSorted = (const char**) malloc(sizeof(char*) * nbFiles);
|
||||
if (!filenameTableSorted) {
|
||||
DISPLAYLEVEL(1, "Allocation error during filename collision checking \n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (u = 0; u < nbFiles; ++u) {
|
||||
filename = strrchr(filenameTable[u], PATH_SEP);
|
||||
if (filename == NULL) {
|
||||
filenameTableSorted[u] = filenameTable[u];
|
||||
} else {
|
||||
filenameTableSorted[u] = filename+1;
|
||||
}
|
||||
}
|
||||
|
||||
qsort((void*)filenameTableSorted, nbFiles, sizeof(char*), UTIL_compareStr);
|
||||
prevElem = filenameTableSorted[0];
|
||||
for (u = 1; u < nbFiles; ++u) {
|
||||
if (strcmp(prevElem, filenameTableSorted[u]) == 0) {
|
||||
DISPLAYLEVEL(2, "WARNING: Two files have same filename: %s\n", prevElem);
|
||||
}
|
||||
prevElem = filenameTableSorted[u];
|
||||
}
|
||||
|
||||
free((void*)filenameTableSorted);
|
||||
return 0;
|
||||
return FIO_rust_checkFilenameCollisions(filenameTable, nbFiles);
|
||||
}
|
||||
|
||||
char* UTIL_createFilenameFromOutDir(const char* path, const char* outDirName,
|
||||
|
||||
+117
-1
@@ -10,7 +10,8 @@
|
||||
//! implementation. In particular, the four legacy fields which C leaves
|
||||
//! unspecified are not zero-filled here either.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::io::{self, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::raw::{c_char, c_int, c_uint};
|
||||
use std::ptr;
|
||||
@@ -475,9 +476,91 @@ pub unsafe extern "C" fn FIO_determineHasStdinInput(
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn filename_path_separator() -> u8 {
|
||||
if cfg!(windows) {
|
||||
b'\\'
|
||||
} else {
|
||||
b'/'
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the byte-level basename used for collision checks.
|
||||
///
|
||||
/// A null entry has no filename and is ignored by the exported checker. It
|
||||
/// still maps to an empty key here so the key operation is safe to exercise
|
||||
/// independently and never passes a null pointer to `CStr::from_ptr`.
|
||||
unsafe fn filename_collision_key<'a>(filename: *const c_char) -> &'a [u8] {
|
||||
if filename.is_null() {
|
||||
return &[];
|
||||
}
|
||||
let bytes = unsafe { CStr::from_ptr(filename).to_bytes() };
|
||||
match bytes
|
||||
.iter()
|
||||
.rposition(|&byte| byte == filename_path_separator())
|
||||
{
|
||||
Some(index) => &bytes[index + 1..],
|
||||
None => bytes,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn same_filename_collision_key(left: *const c_char, right: *const c_char) -> bool {
|
||||
unsafe { filename_collision_key(left) == filename_collision_key(right) }
|
||||
}
|
||||
|
||||
fn display_filename_collision_warning(filename: *const c_char) {
|
||||
let enabled = unsafe { (*display_prefs()).displayLevel >= 2 };
|
||||
if !enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let basename = unsafe { filename_collision_key(filename) };
|
||||
let mut stderr = io::stderr().lock();
|
||||
let _ = stderr.write_all(b"WARNING: Two files have same filename: ");
|
||||
let _ = stderr.write_all(basename);
|
||||
let _ = stderr.write_all(b"\n");
|
||||
}
|
||||
|
||||
/// Rust implementation of the filename collision checker used by
|
||||
/// `FIO_checkFilenameCollisions` in `programs/fileio.c`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn FIO_rust_checkFilenameCollisions(
|
||||
filename_table: *const *const c_char,
|
||||
nb_files: c_uint,
|
||||
) -> c_int {
|
||||
if filename_table.is_null() || nb_files == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut filenames = Vec::<*const c_char>::new();
|
||||
if filenames.try_reserve_exact(nb_files as usize).is_err() {
|
||||
display(1, "Allocation error during filename collision checking \n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for index in 0..nb_files as usize {
|
||||
let filename = unsafe { *filename_table.add(index) };
|
||||
if !filename.is_null() {
|
||||
filenames.push(filename);
|
||||
}
|
||||
}
|
||||
|
||||
filenames.sort_unstable_by(|left, right| unsafe {
|
||||
filename_collision_key(*left).cmp(filename_collision_key(*right))
|
||||
});
|
||||
for pair in filenames.windows(2) {
|
||||
if unsafe { same_filename_collision_key(pair[0], pair[1]) } {
|
||||
display_filename_collision_warning(pair[0]);
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ffi::CString;
|
||||
use std::mem::{align_of, offset_of, size_of};
|
||||
|
||||
#[test]
|
||||
@@ -516,6 +599,39 @@ mod tests {
|
||||
assert_eq!(size_of::<FIO_ctx_t>(), total_bytes + 2 * word);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filename_collision_checker_handles_null_and_empty_inputs() {
|
||||
let empty = CString::new("").unwrap();
|
||||
let names = [ptr::null(), empty.as_ptr()];
|
||||
|
||||
unsafe {
|
||||
assert_eq!(FIO_rust_checkFilenameCollisions(ptr::null(), 0), 0);
|
||||
assert_eq!(FIO_rust_checkFilenameCollisions(names.as_ptr(), 2), 0);
|
||||
assert!(same_filename_collision_key(ptr::null(), empty.as_ptr()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filename_collision_keys_compare_basename_bytes_without_utf8_conversion() {
|
||||
let first = CString::new(b"left/\xffname".to_vec()).unwrap();
|
||||
let duplicate = CString::new(b"right/\xffname".to_vec()).unwrap();
|
||||
let different = CString::new(b"right/\xfename".to_vec()).unwrap();
|
||||
let names = [first.as_ptr(), duplicate.as_ptr()];
|
||||
|
||||
unsafe {
|
||||
assert_eq!(filename_collision_key(first.as_ptr()), b"\xffname");
|
||||
assert!(same_filename_collision_key(
|
||||
first.as_ptr(),
|
||||
duplicate.as_ptr()
|
||||
));
|
||||
assert!(!same_filename_collision_key(
|
||||
first.as_ptr(),
|
||||
different.as_ptr()
|
||||
));
|
||||
assert_eq!(FIO_rust_checkFilenameCollisions(names.as_ptr(), 2), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preference_defaults_match_fileio_c() {
|
||||
let prefs = unsafe { FIO_createPreferences() };
|
||||
|
||||
Reference in New Issue
Block a user