feat(cli): move decompressed filename policy to Rust
Port destination-name derivation to the Rust file-I/O preference module. Preserve build-configured suffix matching, tar-name conversion, stdin/stdout sentinels, output-directory handling, diagnostics, static-buffer reuse, and allocation behavior through the existing C adapter. 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 -C tests -j2 test-cli-tests
This commit is contained in:
+3
-96
@@ -312,6 +312,8 @@ unsigned long long FIO_rust_getLargestFileSize(const char** inFileNames, unsigne
|
|||||||
void FIO_rust_setInBuffer(ZSTD_inBuffer* output, const void* buf, size_t s, size_t pos);
|
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);
|
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);
|
const char* FIO_rust_determineCompressedName(const char* srcFileName, const char* outDirName, const char* suffix);
|
||||||
|
const char* FIO_rust_determineDstName(const char* srcFileName, const char* outDirName,
|
||||||
|
const char* const* suffixList, const char* suffixListStr);
|
||||||
#ifdef ZSTD_LZ4COMPRESS
|
#ifdef ZSTD_LZ4COMPRESS
|
||||||
int FIO_rust_LZ4_GetBlockSize_FromBlockId(int id);
|
int FIO_rust_LZ4_GetBlockSize_FromBlockId(int id);
|
||||||
#endif
|
#endif
|
||||||
@@ -690,20 +692,6 @@ int FIO_checkFilenameCollisions(const char** filenameTable, unsigned nbFiles) {
|
|||||||
return FIO_rust_checkFilenameCollisions(filenameTable, nbFiles);
|
return FIO_rust_checkFilenameCollisions(filenameTable, nbFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
* allocates memory for and returns a pointer to final path.
|
|
||||||
* This function never returns an error (it may abort() in case of pb)
|
|
||||||
*/
|
|
||||||
static char*
|
|
||||||
FIO_createFilename_fromOutDir(const char* path, const char* outDirName, const size_t suffixLen)
|
|
||||||
{
|
|
||||||
return UTIL_createFilenameFromOutDir(path, outDirName, suffixLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIO_highbit64() :
|
/* FIO_highbit64() :
|
||||||
* gives position of highest bit.
|
* gives position of highest bit.
|
||||||
* note : only works for v > 0 !
|
* note : only works for v > 0 !
|
||||||
@@ -2705,88 +2693,7 @@ static const char *suffixListStr =
|
|||||||
static const char*
|
static const char*
|
||||||
FIO_determineDstName(const char* srcFileName, const char* outDirName)
|
FIO_determineDstName(const char* srcFileName, const char* outDirName)
|
||||||
{
|
{
|
||||||
static size_t dfnbCapacity = 0;
|
return FIO_rust_determineDstName(srcFileName, outDirName, suffixList, suffixListStr);
|
||||||
static char* dstFileNameBuffer = NULL; /* using static allocation : this function cannot be multi-threaded */
|
|
||||||
size_t dstFileNameEndPos;
|
|
||||||
char* outDirFilename = NULL;
|
|
||||||
const char* dstSuffix = "";
|
|
||||||
size_t dstSuffixLen = 0;
|
|
||||||
|
|
||||||
size_t sfnSize = strlen(srcFileName);
|
|
||||||
|
|
||||||
size_t srcSuffixLen;
|
|
||||||
const char* const srcSuffix = strrchr(srcFileName, '.');
|
|
||||||
|
|
||||||
if(!strcmp(srcFileName, stdinmark)) {
|
|
||||||
return stdoutmark;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srcSuffix == NULL) {
|
|
||||||
DISPLAYLEVEL(1,
|
|
||||||
"zstd: %s: unknown suffix (%s expected). "
|
|
||||||
"Can't derive the output file name. "
|
|
||||||
"Specify it with -o dstFileName. Ignoring.\n",
|
|
||||||
srcFileName, suffixListStr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
srcSuffixLen = strlen(srcSuffix);
|
|
||||||
|
|
||||||
{
|
|
||||||
const char** matchedSuffixPtr;
|
|
||||||
for (matchedSuffixPtr = suffixList; *matchedSuffixPtr != NULL; matchedSuffixPtr++) {
|
|
||||||
if (!strcmp(*matchedSuffixPtr, srcSuffix)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check suffix is authorized */
|
|
||||||
if (sfnSize <= srcSuffixLen || *matchedSuffixPtr == NULL) {
|
|
||||||
DISPLAYLEVEL(1,
|
|
||||||
"zstd: %s: unknown suffix (%s expected). "
|
|
||||||
"Can't derive the output file name. "
|
|
||||||
"Specify it with -o dstFileName. Ignoring.\n",
|
|
||||||
srcFileName, suffixListStr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((*matchedSuffixPtr)[1] == 't') {
|
|
||||||
dstSuffix = ".tar";
|
|
||||||
dstSuffixLen = strlen(dstSuffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outDirName) {
|
|
||||||
outDirFilename = FIO_createFilename_fromOutDir(srcFileName, outDirName, 0);
|
|
||||||
sfnSize = strlen(outDirFilename);
|
|
||||||
assert(outDirFilename != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dfnbCapacity+srcSuffixLen <= sfnSize+1+dstSuffixLen) {
|
|
||||||
/* allocate enough space to write dstFilename into it */
|
|
||||||
free(dstFileNameBuffer);
|
|
||||||
dfnbCapacity = sfnSize + 20;
|
|
||||||
dstFileNameBuffer = (char*)malloc(dfnbCapacity);
|
|
||||||
if (dstFileNameBuffer==NULL)
|
|
||||||
EXM_THROW(74, "%s : not enough memory for dstFileName",
|
|
||||||
strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* return dst name == src name truncated from suffix */
|
|
||||||
assert(dstFileNameBuffer != NULL);
|
|
||||||
dstFileNameEndPos = sfnSize - srcSuffixLen;
|
|
||||||
if (outDirFilename) {
|
|
||||||
memcpy(dstFileNameBuffer, outDirFilename, dstFileNameEndPos);
|
|
||||||
free(outDirFilename);
|
|
||||||
} else {
|
|
||||||
memcpy(dstFileNameBuffer, srcFileName, dstFileNameEndPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The short tar extensions tzst, tgz, txz and tlz4 files should have "tar"
|
|
||||||
* extension on decompression. Also writes terminating null. */
|
|
||||||
strcpy(dstFileNameBuffer + dstFileNameEndPos, dstSuffix);
|
|
||||||
return dstFileNameBuffer;
|
|
||||||
|
|
||||||
/* note : dstFileNameBuffer memory is not going to be free */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ static STDOUT_MARK: &[u8] = b"/*stdout*\\\0";
|
|||||||
|
|
||||||
static mut COMPRESSED_NAME_CAPACITY: usize = 0;
|
static mut COMPRESSED_NAME_CAPACITY: usize = 0;
|
||||||
static mut COMPRESSED_NAME_BUFFER: *mut c_char = ptr::null_mut();
|
static mut COMPRESSED_NAME_BUFFER: *mut c_char = ptr::null_mut();
|
||||||
|
static mut DESTINATION_NAME_CAPACITY: usize = 0;
|
||||||
|
static mut DESTINATION_NAME_BUFFER: *mut c_char = ptr::null_mut();
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct FIO_inBuffer {
|
pub struct FIO_inBuffer {
|
||||||
@@ -649,6 +651,131 @@ pub unsafe extern "C" fn FIO_rust_determineCompressedName(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn display_unknown_suffix(source: &[u8], suffix_list: &[u8]) {
|
||||||
|
let enabled = unsafe { (*display_prefs()).displayLevel >= 1 };
|
||||||
|
if !enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stderr = io::stderr().lock();
|
||||||
|
let _ = stderr.write_all(b"zstd: ");
|
||||||
|
let _ = stderr.write_all(source);
|
||||||
|
let _ = stderr.write_all(b": unknown suffix (");
|
||||||
|
let _ = stderr.write_all(suffix_list);
|
||||||
|
let _ = stderr.write_all(
|
||||||
|
b" expected). Can't derive the output file name. Specify it with -o dstFileName. Ignoring.\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the decompressed destination name used by the multi-file CLI path.
|
||||||
|
///
|
||||||
|
/// The suffix table and its display string come from C because both are
|
||||||
|
/// selected by the C build configuration. As with the original helper, the
|
||||||
|
/// returned pointer refers to a static buffer and is overwritten by the next
|
||||||
|
/// successful call.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn FIO_rust_determineDstName(
|
||||||
|
src_file_name: *const c_char,
|
||||||
|
out_dir_name: *const c_char,
|
||||||
|
suffix_list: *const *const c_char,
|
||||||
|
suffix_list_str: *const c_char,
|
||||||
|
) -> *const c_char {
|
||||||
|
let source = unsafe { CStr::from_ptr(src_file_name).to_bytes() };
|
||||||
|
let suffix_display = unsafe { CStr::from_ptr(suffix_list_str).to_bytes() };
|
||||||
|
|
||||||
|
if source == b"/*stdin*\\" {
|
||||||
|
return STDOUT_MARK.as_ptr().cast();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(src_suffix_start) = source.iter().rposition(|&byte| byte == b'.') else {
|
||||||
|
display_unknown_suffix(source, suffix_display);
|
||||||
|
return ptr::null();
|
||||||
|
};
|
||||||
|
let src_suffix = &source[src_suffix_start..];
|
||||||
|
let src_suffix_len = src_suffix.len();
|
||||||
|
|
||||||
|
let mut matched_suffix = ptr::null();
|
||||||
|
let mut suffix_ptr = suffix_list;
|
||||||
|
unsafe {
|
||||||
|
while !suffix_ptr.is_null() {
|
||||||
|
let candidate = *suffix_ptr;
|
||||||
|
if candidate.is_null() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if CStr::from_ptr(candidate).to_bytes() == src_suffix {
|
||||||
|
matched_suffix = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
suffix_ptr = suffix_ptr.add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if source.len() <= src_suffix_len || matched_suffix.is_null() {
|
||||||
|
display_unknown_suffix(source, suffix_display);
|
||||||
|
return ptr::null();
|
||||||
|
}
|
||||||
|
|
||||||
|
let matched_suffix_bytes = unsafe { CStr::from_ptr(matched_suffix).to_bytes() };
|
||||||
|
let dst_suffix: &[u8] = if matched_suffix_bytes.get(1) == Some(&b't') {
|
||||||
|
b".tar\0"
|
||||||
|
} else {
|
||||||
|
b"\0"
|
||||||
|
};
|
||||||
|
let dst_suffix_len = dst_suffix.len() - 1;
|
||||||
|
|
||||||
|
let mut out_dir_filename = ptr::null_mut();
|
||||||
|
let source_for_destination = if out_dir_name.is_null() {
|
||||||
|
source
|
||||||
|
} else {
|
||||||
|
out_dir_filename =
|
||||||
|
unsafe { crate::util::UTIL_createFilenameFromOutDir(src_file_name, out_dir_name, 0) };
|
||||||
|
assert!(!out_dir_filename.is_null());
|
||||||
|
unsafe { CStr::from_ptr(out_dir_filename).to_bytes() }
|
||||||
|
};
|
||||||
|
let source_len = source_for_destination.len();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let capacity = ptr::addr_of_mut!(DESTINATION_NAME_CAPACITY);
|
||||||
|
let buffer = ptr::addr_of_mut!(DESTINATION_NAME_BUFFER);
|
||||||
|
if capacity.read().wrapping_add(src_suffix_len)
|
||||||
|
<= source_len.wrapping_add(1).wrapping_add(dst_suffix_len)
|
||||||
|
{
|
||||||
|
libc::free(buffer.read().cast::<c_void>());
|
||||||
|
let new_capacity = source_len.wrapping_add(20);
|
||||||
|
let new_buffer = libc::malloc(new_capacity).cast::<c_char>();
|
||||||
|
if new_buffer.is_null() {
|
||||||
|
throw(
|
||||||
|
74,
|
||||||
|
&format!(
|
||||||
|
"{} : not enough memory for dstFileName",
|
||||||
|
std::io::Error::last_os_error()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
capacity.write(new_capacity);
|
||||||
|
buffer.write(new_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let destination = buffer.read();
|
||||||
|
assert!(!destination.is_null());
|
||||||
|
let destination_end = source_len - src_suffix_len;
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
source_for_destination.as_ptr(),
|
||||||
|
destination.cast::<u8>(),
|
||||||
|
destination_end,
|
||||||
|
);
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
dst_suffix.as_ptr(),
|
||||||
|
destination.add(destination_end).cast::<u8>(),
|
||||||
|
dst_suffix.len(),
|
||||||
|
);
|
||||||
|
if !out_dir_filename.is_null() {
|
||||||
|
libc::free(out_dir_filename.cast::<c_void>());
|
||||||
|
}
|
||||||
|
destination.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn filename_path_separator() -> u8 {
|
fn filename_path_separator() -> u8 {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
@@ -877,6 +1004,79 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn destination_filename_shim_preserves_suffixes_tar_names_and_sentinels() {
|
||||||
|
let zst = CString::new(".zst").unwrap();
|
||||||
|
let tzst = CString::new(".tzst").unwrap();
|
||||||
|
let gz = CString::new(".gz").unwrap();
|
||||||
|
let tgz = CString::new(".tgz").unwrap();
|
||||||
|
let suffixes = [
|
||||||
|
zst.as_ptr(),
|
||||||
|
tzst.as_ptr(),
|
||||||
|
gz.as_ptr(),
|
||||||
|
tgz.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
];
|
||||||
|
let suffixes_display = CString::new(".zst/.tzst/.gz/.tgz").unwrap();
|
||||||
|
let source = CString::new("input/nested/file.zst").unwrap();
|
||||||
|
let tar_source = CString::new("input/nested/archive.tzst").unwrap();
|
||||||
|
let tar_gzip_source = CString::new("archive.tgz").unwrap();
|
||||||
|
let output_dir = CString::new("out").unwrap();
|
||||||
|
let stdin = CString::new("/*stdin*\\").unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let name = FIO_rust_determineDstName(
|
||||||
|
source.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
suffixes.as_ptr(),
|
||||||
|
suffixes_display.as_ptr(),
|
||||||
|
);
|
||||||
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"input/nested/file");
|
||||||
|
|
||||||
|
let name = FIO_rust_determineDstName(
|
||||||
|
tar_source.as_ptr(),
|
||||||
|
output_dir.as_ptr(),
|
||||||
|
suffixes.as_ptr(),
|
||||||
|
suffixes_display.as_ptr(),
|
||||||
|
);
|
||||||
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"out/archive.tar");
|
||||||
|
|
||||||
|
let name = FIO_rust_determineDstName(
|
||||||
|
tar_gzip_source.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
suffixes.as_ptr(),
|
||||||
|
suffixes_display.as_ptr(),
|
||||||
|
);
|
||||||
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"archive.tar");
|
||||||
|
|
||||||
|
let name = FIO_rust_determineDstName(
|
||||||
|
stdin.as_ptr(),
|
||||||
|
output_dir.as_ptr(),
|
||||||
|
suffixes.as_ptr(),
|
||||||
|
suffixes_display.as_ptr(),
|
||||||
|
);
|
||||||
|
assert_eq!(CStr::from_ptr(name).to_bytes(), b"/*stdout*\\");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn destination_filename_shim_rejects_unknown_suffix() {
|
||||||
|
let zst = CString::new(".zst").unwrap();
|
||||||
|
let suffixes = [zst.as_ptr(), ptr::null()];
|
||||||
|
let suffixes_display = CString::new(".zst").unwrap();
|
||||||
|
let source = CString::new("input/file.zip").unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
assert!(FIO_rust_determineDstName(
|
||||||
|
source.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
suffixes.as_ptr(),
|
||||||
|
suffixes_display.as_ptr(),
|
||||||
|
)
|
||||||
|
.is_null());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn c_layouts_match_the_headers() {
|
fn c_layouts_match_the_headers() {
|
||||||
let word = size_of::<usize>();
|
let word = size_of::<usize>();
|
||||||
|
|||||||
Reference in New Issue
Block a user