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
|
/* 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. */
|
* 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
|
* Checks for and warns if there are any files that would have the same output path
|
||||||
*/
|
*/
|
||||||
int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) {
|
int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) {
|
||||||
const char **filenameTableSorted, *prevElem, *filename;
|
return FIO_rust_checkFilenameCollisions(filenameTable, nbFiles);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* UTIL_createFilenameFromOutDir(const char* path, const char* outDirName,
|
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
|
//! implementation. In particular, the four legacy fields which C leaves
|
||||||
//! unspecified are not zero-filled here either.
|
//! 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::mem::size_of;
|
||||||
use std::os::raw::{c_char, c_int, c_uint};
|
use std::os::raw::{c_char, c_int, c_uint};
|
||||||
use std::ptr;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::ffi::CString;
|
||||||
use std::mem::{align_of, offset_of, size_of};
|
use std::mem::{align_of, offset_of, size_of};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -516,6 +599,39 @@ mod tests {
|
|||||||
assert_eq!(size_of::<FIO_ctx_t>(), total_bytes + 2 * word);
|
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]
|
#[test]
|
||||||
fn preference_defaults_match_fileio_c() {
|
fn preference_defaults_match_fileio_c() {
|
||||||
let prefs = unsafe { FIO_createPreferences() };
|
let prefs = unsafe { FIO_createPreferences() };
|
||||||
|
|||||||
Reference in New Issue
Block a user