diff --git a/programs/fileio.c b/programs/fileio.c index 952142a48..bd08fbf35 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -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) diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index abb2f1d1b..dadea2ee7 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -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::()); + let new_capacity = source_len.wrapping_add(suffix_len).wrapping_add(30); + let new_buffer = libc::malloc(new_capacity).cast::(); + 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::()); + } + 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::();