From 4171c424480c5c23d32f9caee866d7d06b1c103d Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 18 Jul 2026 02:52:26 +0200 Subject: [PATCH] feat(fileio): move output path construction to Rust Move the basename-preserving output-directory helper behind the existing Rust utility ABI. Keep the libc allocation contract and platform separator behavior intact while leaving the C file-I/O engines and higher-level naming policy unchanged. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml (93 passed) - cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets - make -B -C tests -j2 test-cli-tests (41 passed) --- programs/fileio.c | 39 ++------------------------ rust/src/util.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/programs/fileio.c b/programs/fileio.c index 523799702..4b645a664 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -709,13 +709,8 @@ int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) { return 0; } -static const char* -extractFilename(const char* path, char separator) -{ - const char* search = strrchr(path, separator); - if (search == NULL) return path; - return search+1; -} +char* UTIL_createFilenameFromOutDir(const char* path, const char* outDirName, + size_t suffixLen); /* FIO_createFilename_fromOutDir() : * Takes a source file name and specified output directory, and @@ -725,35 +720,7 @@ extractFilename(const char* path, char separator) static char* FIO_createFilename_fromOutDir(const char* path, const char* outDirName, const size_t suffixLen) { - const char* filenameStart; - char separator; - char* result; - -#if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) /* windows support */ - separator = '\\'; -#else - separator = '/'; -#endif - - filenameStart = extractFilename(path, separator); -#if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) /* windows support */ - filenameStart = extractFilename(filenameStart, '/'); /* sometimes, '/' separator is also used on Windows (mingw+msys2) */ -#endif - - result = (char*) calloc(1, strlen(outDirName) + 1 + strlen(filenameStart) + suffixLen + 1); - if (!result) { - EXM_THROW(30, "zstd: FIO_createFilename_fromOutDir: %s", strerror(errno)); - } - - memcpy(result, outDirName, strlen(outDirName)); - if (outDirName[strlen(outDirName)-1] == separator) { - memcpy(result + strlen(outDirName), filenameStart, strlen(filenameStart)); - } else { - memcpy(result + strlen(outDirName), &separator, 1); - memcpy(result + strlen(outDirName) + 1, filenameStart, strlen(filenameStart)); - } - - return result; + return UTIL_createFilenameFromOutDir(path, outDirName, suffixLen); } /* FIO_highbit64() : diff --git a/rust/src/util.rs b/rust/src/util.rs index a8b2b53dd..ac2f2de24 100644 --- a/rust/src/util.rs +++ b/rust/src/util.rs @@ -161,6 +161,59 @@ fn path_separator() -> u8 { } } +/// Build the basename-preserving output path used by the file-I/O backend. +/// +/// The caller owns the returned libc allocation. `suffix_len` is retained in +/// the ABI because the original C helper reserved that extra capacity for its +/// callers, even though it only wrote the directory and basename. +#[no_mangle] +pub unsafe extern "C" fn UTIL_createFilenameFromOutDir( + path: *const c_char, + out_dir: *const c_char, + _suffix_len: usize, +) -> *mut c_char { + if path.is_null() || out_dir.is_null() { + return ptr::null_mut(); + } + let path = unsafe { c_bytes(path) }; + let out_dir = unsafe { c_bytes(out_dir) }; + if out_dir.is_empty() { + std::process::abort(); + } + + let separator = path_separator(); + let mut filename = path; + if let Some(index) = path.iter().rposition(|&byte| byte == separator) { + filename = &path[index + 1..]; + } + #[cfg(windows)] + if let Some(index) = filename.iter().rposition(|&byte| byte == b'/') { + filename = &filename[index + 1..]; + } + + let separator_len = usize::from(out_dir.last().copied() != Some(separator)); + let Some(size) = out_dir + .len() + .checked_add(separator_len) + .and_then(|size| size.checked_add(filename.len())) + .and_then(|size| size.checked_add(1)) + else { + std::process::abort(); + }; + let result = unsafe { malloc_bytes(size).cast::() }; + unsafe { + ptr::copy_nonoverlapping(out_dir.as_ptr(), result.cast(), out_dir.len()); + let mut offset = out_dir.len(); + if separator_len != 0 { + *result.add(offset) = separator as c_char; + offset += 1; + } + ptr::copy_nonoverlapping(filename.as_ptr(), result.add(offset).cast(), filename.len()); + *result.add(offset + filename.len()) = 0; + } + result +} + fn path_from_bytes(bytes: &[u8]) -> PathBuf { #[cfg(unix)] { @@ -1558,6 +1611,24 @@ mod tests { } } + #[test] + fn output_path_preserves_basename_and_trailing_separator() { + let path = CString::new("input/nested/file.txt").unwrap(); + let out_dir = CString::new("out").unwrap(); + unsafe { + let name = UTIL_createFilenameFromOutDir(path.as_ptr(), out_dir.as_ptr(), 4); + assert_eq!(CStr::from_ptr(name).to_bytes(), b"out/file.txt"); + free_ptr(name); + } + + let out_dir = CString::new(if cfg!(windows) { "out\\" } else { "out/" }).unwrap(); + unsafe { + let name = UTIL_createFilenameFromOutDir(path.as_ptr(), out_dir.as_ptr(), 0); + assert_eq!(CStr::from_ptr(name).to_bytes(), b"out/file.txt"); + free_ptr(name); + } + } + #[test] fn human_readable_size_uses_the_c_scaling_policy() { unsafe {