diff --git a/programs/fileio.c b/programs/fileio.c index c7b6936d5..55c87313d 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -1244,9 +1244,12 @@ typedef void (*FIO_rust_decompress_dst_stat_fn)(void* opaque, int dstFd, typedef int (*FIO_rust_decompress_dst_close_fn)(void* opaque); typedef void (*FIO_rust_decompress_dst_remove_fn)(void* opaque, const char* dstFileName); typedef int (*FIO_rust_decompress_src_remove_fn)(void* opaque, const char* srcFileName); +typedef int (*FIO_rust_decompress_src_directory_fn)(void* opaque, + const char* srcFileName); -/* Rust owns per-file source/destination ordering; the callback context keeps - * dRess_t, FILE/pool handles, diagnostics, and the format dispatcher private. */ +/* Rust owns per-file source/directory ordering and destination ordering; the + * callback context keeps dRess_t, FILE/pool handles, diagnostics, and the + * format dispatcher private. */ typedef struct { void* opaque; const char* dstFileName; @@ -1271,6 +1274,7 @@ typedef struct { FIO_rust_decompress_handler_fn utimeDestination; FIO_rust_decompress_dst_remove_fn removeDestination; FIO_rust_decompress_src_remove_fn removeSourceFile; + FIO_rust_decompress_src_directory_fn sourceIsDirectory; } FIO_rust_decompress_file_projection_t; typedef char FIO_rust_decompress_file_opaque_offset[ (offsetof(FIO_rust_decompress_file_projection_t, opaque) == 0) ? 1 : -1]; @@ -1299,11 +1303,16 @@ typedef char FIO_rust_decompress_file_callback_offset[ (offsetof(FIO_rust_decompress_file_projection_t, openSource) == ((3 * sizeof(void*) + 5 * sizeof(int) + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)) ? 1 : -1]; +typedef char FIO_rust_decompress_file_source_directory_offset[ + (offsetof(FIO_rust_decompress_file_projection_t, sourceIsDirectory) + == ((3 * sizeof(void*) + 5 * sizeof(int) + sizeof(void*) - 1) + / sizeof(void*)) * sizeof(void*) + + 15 * sizeof(void*)) ? 1 : -1]; typedef char FIO_rust_decompress_file_projection_size[ (sizeof(FIO_rust_decompress_file_projection_t) == ((3 * sizeof(void*) + 5 * sizeof(int) + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*) - + 15 * sizeof(FIO_rust_decompress_src_open_fn)) ? 1 : -1]; + + 16 * sizeof(FIO_rust_decompress_src_open_fn)) ? 1 : -1]; int FIO_rust_decompressFilename( const FIO_rust_decompress_file_projection_t* projection); @@ -4249,11 +4258,6 @@ static int FIO_rust_decompressSourceOpen(void* opaque, const char* srcFileName, FIO_rust_decompress_file_context_t* const context = (FIO_rust_decompress_file_context_t*)opaque; - if (UTIL_isDirectory(srcFileName)) { - DISPLAYLEVEL(1, "zstd: %s is a directory -- ignored \n", srcFileName); - return 1; - } - context->srcFile = FIO_openSrcFile(context->prefs, srcFileName, &context->srcFileStat); if (context->srcFile == NULL) return 1; @@ -4267,6 +4271,18 @@ static int FIO_rust_decompressSourceOpen(void* opaque, const char* srcFileName, return FIO_RUST_DECOMPRESS_SRC_OPEN_OK; } +static int FIO_rust_decompressSourceIsDirectory(void* opaque, const char* srcFileName) +{ + /* Rust owns the rejection decision; keep the probe and exact diagnostic + * here because they depend on the CLI's private filesystem helpers. */ + (void)opaque; + if (UTIL_isDirectory(srcFileName)) { + DISPLAYLEVEL(1, "zstd: %s is a directory -- ignored \n", srcFileName); + return 1; + } + return 0; +} + static void FIO_rust_decompressSourceSetAsync(void* opaque, int asyncMode) { FIO_rust_decompress_file_context_t* const context = @@ -4433,6 +4449,7 @@ static int FIO_decompressFile(FIO_ctx_t* const fCtx, FIO_prefs_t* const prefs, projection.utimeDestination = FIO_rust_decompressDestinationUtime; projection.removeDestination = FIO_rust_decompressDestinationRemove; projection.removeSourceFile = FIO_rust_decompressSourceRemove; + projection.sourceIsDirectory = FIO_rust_decompressSourceIsDirectory; return FIO_rust_decompressFilename(&projection); } diff --git a/rust/src/fileio_asyncio.rs b/rust/src/fileio_asyncio.rs index b5830a50b..72701e610 100644 --- a/rust/src/fileio_asyncio.rs +++ b/rust/src/fileio_asyncio.rs @@ -671,6 +671,8 @@ const FIO_RUST_DECOMPRESS_SRC_UNKNOWN_SIZE: u64 = u64::MAX; pub type FIO_rust_decompress_src_open_fn = unsafe extern "C" fn(*mut c_void, *const c_char, *mut u64, *mut c_int) -> c_int; +pub type FIO_rust_decompress_src_directory_fn = + unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; pub type FIO_rust_decompress_src_async_fn = unsafe extern "C" fn(*mut c_void, c_int); pub type FIO_rust_decompress_src_attach_fn = unsafe extern "C" fn(*mut c_void); pub type FIO_rust_decompress_src_close_fn = unsafe extern "C" fn(*mut c_void) -> c_int; @@ -713,6 +715,7 @@ pub struct FIO_rust_decompress_file_projection_t { pub utime_destination: Option, pub remove_destination: Option, pub remove_source_file: Option, + pub source_is_directory: Option, } const FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET: usize = @@ -812,9 +815,13 @@ const _: () = { == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 14 * size_of::() ); assert!( - size_of::() + std::mem::offset_of!(FIO_rust_decompress_file_projection_t, source_is_directory) == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 15 * size_of::() ); + assert!( + size_of::() + == FIO_RUST_DECOMPRESS_FILE_CALLBACK_OFFSET + 16 * size_of::() + ); }; pub const FIO_RUST_ZSTD_OK: c_int = 0; @@ -3073,6 +3080,15 @@ pub unsafe extern "C" fn FIO_rust_decompressFilename( assert!(!projection.dst_file_name.is_null()); assert!(!projection.src_file_name.is_null()); + if projection.source_is_stdin == 0 { + let source_is_directory = projection + .source_is_directory + .expect("source directory callback is required for named files"); + if unsafe { source_is_directory(projection.opaque, projection.src_file_name) } != 0 { + return 1; + } + } + let open_source = projection .open_source .expect("source open callback is required"); @@ -5869,6 +5885,8 @@ mod tests { destination_fds: Vec, source_file_size: u64, source_is_regular: c_int, + source_is_directory: c_int, + source_directory_checks: usize, source_open_status: c_int, source_close_status: c_int, destination_open_status: c_int, @@ -5892,6 +5910,15 @@ mod tests { state.source_open_status } + unsafe extern "C" fn decompress_policy_source_is_directory( + opaque: *mut c_void, + _src_file_name: *const c_char, + ) -> c_int { + let state = unsafe { &mut *opaque.cast::() }; + state.source_directory_checks += 1; + state.source_is_directory + } + unsafe extern "C" fn decompress_policy_async(opaque: *mut c_void, async_mode: c_int) { let state = unsafe { &mut *opaque.cast::() }; state.events.push(DECOMPRESS_POLICY_ASYNC); @@ -6023,9 +6050,23 @@ mod tests { utime_destination: Some(decompress_policy_utime_destination), remove_destination: Some(decompress_policy_remove_destination), remove_source_file: Some(decompress_policy_remove_source), + source_is_directory: Some(decompress_policy_source_is_directory), } } + #[test] + fn decompress_file_policy_rejects_directory_before_opening_source() { + let mut state = DecompressPolicyState { + source_is_directory: 1, + ..DecompressPolicyState::default() + }; + let projection = decompress_policy_projection(&mut state, 0, 0, 0, 0, 0); + + assert_eq!(unsafe { FIO_rust_decompressFilename(&projection) }, 1); + assert_eq!(state.source_directory_checks, 1); + assert!(state.events.is_empty()); + } + #[test] fn decompress_file_policy_orders_cleanup_and_successful_source_removal() { let mut state = DecompressPolicyState {