feat(cli): move compressed filename helper to Rust

Port the compressed destination-name helper to the Rust file-I/O preference module. Preserve stdin/stdout sentinels, output-directory basename handling, suffix assembly, static-buffer reuse, and allocation failure behavior behind the existing C shim.

Test Plan:\n- cargo test --manifest-path rust/cli/Cargo.toml --no-default-features --features cli,compression,decompression,benchmark\n- cargo test --manifest-path rust/cli/Cargo.toml --no-default-features --features helpers\n- cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets --no-default-features --features cli,compression,decompression,benchmark\n- cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets --no-default-features --features helpers\n- make -B -C programs -j2 zstd zstd-small zstd-frugal\n- make -B -C tests -j2 test-cli-tests
This commit is contained in:
2026-07-18 04:06:02 +02:00
parent bdb14b4fbf
commit af2991ea40
2 changed files with 98 additions and 35 deletions
+2 -35
View File
@@ -311,6 +311,7 @@ unsigned FIO_rust_highbit64(unsigned long long v);
unsigned long long FIO_rust_getLargestFileSize(const char** inFileNames, unsigned nbFiles);
void FIO_rust_setInBuffer(ZSTD_inBuffer* output, const void* buf, size_t s, size_t pos);
void FIO_rust_setOutBuffer(ZSTD_outBuffer* output, void* buf, size_t s, size_t pos);
const char* FIO_rust_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix);
#ifdef ZSTD_LZ4COMPRESS
int FIO_rust_LZ4_GetBlockSize_FromBlockId(int id);
#endif
@@ -1912,41 +1913,7 @@ int FIO_compressFilename(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs, const
static const char*
FIO_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix)
{
static size_t dfnbCapacity = 0;
static char* dstFileNameBuffer = NULL; /* using static allocation : this function cannot be multi-threaded */
char* outDirFilename = NULL;
size_t sfnSize = strlen(srcFileName);
size_t const srcSuffixLen = strlen(suffix);
if(!strcmp(srcFileName, stdinmark)) {
return stdoutmark;
}
if (outDirName) {
outDirFilename = FIO_createFilename_fromOutDir(srcFileName, outDirName, srcSuffixLen);
sfnSize = strlen(outDirFilename);
assert(outDirFilename != NULL);
}
if (dfnbCapacity <= sfnSize+srcSuffixLen+1) {
/* resize buffer for dstName */
free(dstFileNameBuffer);
dfnbCapacity = sfnSize + srcSuffixLen + 30;
dstFileNameBuffer = (char*)malloc(dfnbCapacity);
if (!dstFileNameBuffer) {
EXM_THROW(30, "zstd: %s", strerror(errno));
}
}
assert(dstFileNameBuffer != NULL);
if (outDirFilename) {
memcpy(dstFileNameBuffer, outDirFilename, sfnSize);
free(outDirFilename);
} else {
memcpy(dstFileNameBuffer, srcFileName, sfnSize);
}
memcpy(dstFileNameBuffer+sfnSize, suffix, srcSuffixLen+1 /* Include terminating null */);
return dstFileNameBuffer;
return FIO_rust_determineCompressedName(srcFileName, outDirName, suffix);
}
static unsigned long long FIO_getLargestFileSize(const char** inFileNames, unsigned nbFiles)
+96
View File
@@ -19,6 +19,10 @@ use std::ptr;
const FIO_ZSTD_COMPRESSION: c_int = 0;
const FIO_OVERLAP_LOG_NOTSET: c_int = 9999;
const FIO_LDM_PARAM_NOTSET: c_int = 9999;
static STDOUT_MARK: &[u8] = b"/*stdout*\\\0";
static mut COMPRESSED_NAME_CAPACITY: usize = 0;
static mut COMPRESSED_NAME_BUFFER: *mut c_char = ptr::null_mut();
#[repr(C)]
pub struct FIO_inBuffer {
@@ -578,6 +582,73 @@ pub extern "C" fn FIO_rust_LZ4_GetBlockSize_FromBlockId(id: c_int) -> c_int {
lz4_block_size_from_block_id(id)
}
/// Build the compressed destination name used by the multi-file CLI path.
///
/// This deliberately keeps the original static allocation contract: callers
/// must consume the returned pointer before the next call, and the helper is
/// not thread-safe. The output-directory basename construction is shared
/// with `UTIL_createFilenameFromOutDir`, while suffix assembly remains here.
#[no_mangle]
pub unsafe extern "C" fn FIO_rust_determineCompressedName(
src_file_name: *const c_char,
out_dir_name: *const c_char,
suffix: *const c_char,
) -> *const c_char {
let source = unsafe { CStr::from_ptr(src_file_name).to_bytes() };
let suffix_bytes = unsafe { CStr::from_ptr(suffix).to_bytes() };
if source == b"/*stdin*\\" {
return STDOUT_MARK.as_ptr().cast();
}
let mut out_dir_filename = ptr::null_mut();
let source_bytes = if out_dir_name.is_null() {
source
} else {
out_dir_filename = unsafe {
crate::util::UTIL_createFilenameFromOutDir(
src_file_name,
out_dir_name,
suffix_bytes.len(),
)
};
assert!(!out_dir_filename.is_null());
unsafe { CStr::from_ptr(out_dir_filename).to_bytes() }
};
let source_len = source_bytes.len();
let suffix_len = suffix_bytes.len();
let required = source_len.wrapping_add(suffix_len).wrapping_add(1);
unsafe {
let capacity = ptr::addr_of_mut!(COMPRESSED_NAME_CAPACITY);
let buffer = ptr::addr_of_mut!(COMPRESSED_NAME_BUFFER);
if capacity.read() <= required {
libc::free(buffer.read().cast::<c_void>());
let new_capacity = source_len.wrapping_add(suffix_len).wrapping_add(30);
let new_buffer = libc::malloc(new_capacity).cast::<c_char>();
if new_buffer.is_null() {
throw(30, "Allocation error : not enough memory");
}
capacity.write(new_capacity);
buffer.write(new_buffer);
}
let destination = buffer.read();
assert!(!destination.is_null());
ptr::copy_nonoverlapping(source_bytes.as_ptr(), destination.cast(), source_len);
ptr::copy_nonoverlapping(
suffix_bytes.as_ptr(),
destination.add(source_len).cast(),
suffix_len + 1,
);
if !out_dir_filename.is_null() {
libc::free(out_dir_filename.cast::<c_void>());
}
destination.cast()
}
}
#[inline]
fn filename_path_separator() -> u8 {
if cfg!(windows) {
@@ -781,6 +852,31 @@ mod tests {
assert_eq!(FIO_rust_LZ4_GetBlockSize_FromBlockId(4), 1 << 16);
}
#[test]
fn compressed_filename_shim_preserves_sentinels_suffixes_and_output_dirs() {
let source = CString::new("input/nested/file").unwrap();
let suffix = CString::new(".zst").unwrap();
let output_dir = CString::new("out").unwrap();
unsafe {
let name =
FIO_rust_determineCompressedName(source.as_ptr(), ptr::null(), suffix.as_ptr());
assert_eq!(CStr::from_ptr(name).to_bytes(), b"input/nested/file.zst");
let name = FIO_rust_determineCompressedName(
source.as_ptr(),
output_dir.as_ptr(),
suffix.as_ptr(),
);
assert_eq!(CStr::from_ptr(name).to_bytes(), b"out/file.zst");
let stdin = CString::new("/*stdin*\\").unwrap();
let name =
FIO_rust_determineCompressedName(stdin.as_ptr(), ptr::null(), suffix.as_ptr());
assert_eq!(CStr::from_ptr(name).to_bytes(), b"/*stdout*\\");
}
}
#[test]
fn c_layouts_match_the_headers() {
let word = size_of::<usize>();