feat(cli): move shared destination lifecycle to Rust
Move the warning, destination open/attach, shared file iteration, and close ordering for multi-file compression and decompression into Rust orchestration. The C side retains the private preferences/resources, filesystem callbacks, write-pool operations, diagnostics, and exception behavior behind opaque callbacks. Decompression test mode continues to skip destination I/O. Add explicit C/Rust projection layout assertions and focused lifecycle tests covering callback order, open failure, aggregate file errors, and test mode. Test Plan: - git diff --check - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo check --manifest-path rust/Cargo.toml --tests - ulimit -v 41943040; make -j1 - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings - capped two-input shared-destination compression/decompression smoke test
This commit is contained in:
+175
-44
@@ -1388,6 +1388,44 @@ typedef char FIO_rust_compress_multiple_projection_size[
|
|||||||
int FIO_rust_compressMultipleFilenames(
|
int FIO_rust_compressMultipleFilenames(
|
||||||
const FIO_rust_compress_multiple_projection_t* projection);
|
const FIO_rust_compress_multiple_projection_t* projection);
|
||||||
|
|
||||||
|
typedef int (*FIO_rust_multiple_destination_warning_fn)(void* opaque);
|
||||||
|
typedef void* (*FIO_rust_multiple_destination_open_fn)(void* opaque);
|
||||||
|
typedef void (*FIO_rust_multiple_destination_attach_fn)(
|
||||||
|
void* opaque, void* destination);
|
||||||
|
typedef int (*FIO_rust_multiple_destination_close_fn)(void* opaque);
|
||||||
|
typedef struct {
|
||||||
|
const FIO_rust_compress_multiple_projection_t* files;
|
||||||
|
FIO_rust_multiple_destination_warning_fn warning;
|
||||||
|
FIO_rust_multiple_destination_open_fn openDestination;
|
||||||
|
FIO_rust_multiple_destination_attach_fn attachDestination;
|
||||||
|
FIO_rust_multiple_destination_close_fn closeDestination;
|
||||||
|
} FIO_rust_compress_multiple_destination_projection_t;
|
||||||
|
typedef char FIO_rust_compress_multiple_destination_files_offset[
|
||||||
|
(offsetof(FIO_rust_compress_multiple_destination_projection_t, files)
|
||||||
|
== 0) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_compress_multiple_destination_warning_offset[
|
||||||
|
(offsetof(FIO_rust_compress_multiple_destination_projection_t, warning)
|
||||||
|
== sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_compress_multiple_destination_open_offset[
|
||||||
|
(offsetof(FIO_rust_compress_multiple_destination_projection_t, openDestination)
|
||||||
|
== 2 * sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_compress_multiple_destination_attach_offset[
|
||||||
|
(offsetof(FIO_rust_compress_multiple_destination_projection_t, attachDestination)
|
||||||
|
== 3 * sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_compress_multiple_destination_close_offset[
|
||||||
|
(offsetof(FIO_rust_compress_multiple_destination_projection_t, closeDestination)
|
||||||
|
== 4 * sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_compress_multiple_destination_callback_size[
|
||||||
|
(sizeof(FIO_rust_multiple_destination_warning_fn) == sizeof(void*)
|
||||||
|
&& sizeof(FIO_rust_multiple_destination_open_fn) == sizeof(void*)
|
||||||
|
&& sizeof(FIO_rust_multiple_destination_attach_fn) == sizeof(void*)
|
||||||
|
&& sizeof(FIO_rust_multiple_destination_close_fn) == sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_compress_multiple_destination_projection_size[
|
||||||
|
(sizeof(FIO_rust_compress_multiple_destination_projection_t)
|
||||||
|
== 5 * sizeof(void*)) ? 1 : -1];
|
||||||
|
int FIO_rust_compressMultipleFilenamesWithDestination(
|
||||||
|
const FIO_rust_compress_multiple_destination_projection_t* projection);
|
||||||
|
|
||||||
typedef int (*FIO_rust_compress_multiple_separate_file_fn)(
|
typedef int (*FIO_rust_compress_multiple_separate_file_fn)(
|
||||||
void* opaque, const char* srcFileName);
|
void* opaque, const char* srcFileName);
|
||||||
/* Rust selects the destination-mode callback; both callbacks retain their
|
/* Rust selects the destination-mode callback; both callbacks retain their
|
||||||
@@ -1456,6 +1494,40 @@ typedef char FIO_rust_decompress_multiple_projection_size[
|
|||||||
int FIO_rust_decompressMultipleFilenames(
|
int FIO_rust_decompressMultipleFilenames(
|
||||||
const FIO_rust_decompress_multiple_projection_t* projection);
|
const FIO_rust_decompress_multiple_projection_t* projection);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const FIO_rust_decompress_multiple_projection_t* files;
|
||||||
|
int destinationEnabled;
|
||||||
|
FIO_rust_multiple_destination_warning_fn warning;
|
||||||
|
FIO_rust_multiple_destination_open_fn openDestination;
|
||||||
|
FIO_rust_multiple_destination_attach_fn attachDestination;
|
||||||
|
FIO_rust_multiple_destination_close_fn closeDestination;
|
||||||
|
} FIO_rust_decompress_multiple_destination_projection_t;
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_files_offset[
|
||||||
|
(offsetof(FIO_rust_decompress_multiple_destination_projection_t, files)
|
||||||
|
== 0) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_enabled_offset[
|
||||||
|
(offsetof(FIO_rust_decompress_multiple_destination_projection_t, destinationEnabled)
|
||||||
|
== sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_warning_offset[
|
||||||
|
(offsetof(FIO_rust_decompress_multiple_destination_projection_t, warning)
|
||||||
|
== 2 * sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_open_offset[
|
||||||
|
(offsetof(FIO_rust_decompress_multiple_destination_projection_t, openDestination)
|
||||||
|
== 3 * sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_attach_offset[
|
||||||
|
(offsetof(FIO_rust_decompress_multiple_destination_projection_t, attachDestination)
|
||||||
|
== 4 * sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_close_offset[
|
||||||
|
(offsetof(FIO_rust_decompress_multiple_destination_projection_t, closeDestination)
|
||||||
|
== 5 * sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_int_size[
|
||||||
|
(sizeof(int) <= sizeof(void*)) ? 1 : -1];
|
||||||
|
typedef char FIO_rust_decompress_multiple_destination_projection_size[
|
||||||
|
(sizeof(FIO_rust_decompress_multiple_destination_projection_t)
|
||||||
|
== 6 * sizeof(void*)) ? 1 : -1];
|
||||||
|
int FIO_rust_decompressMultipleFilenamesWithDestination(
|
||||||
|
const FIO_rust_decompress_multiple_destination_projection_t* projection);
|
||||||
|
|
||||||
typedef int (*FIO_rust_decompress_multiple_separate_file_fn)(
|
typedef int (*FIO_rust_decompress_multiple_separate_file_fn)(
|
||||||
void* opaque, const char* srcFileName);
|
void* opaque, const char* srcFileName);
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -3692,8 +3764,43 @@ typedef struct {
|
|||||||
FIO_prefs_t* prefs;
|
FIO_prefs_t* prefs;
|
||||||
cRess_t* ress;
|
cRess_t* ress;
|
||||||
int compressionLevel;
|
int compressionLevel;
|
||||||
|
const char* outFileName;
|
||||||
} FIO_rust_compress_multiple_context_t;
|
} FIO_rust_compress_multiple_context_t;
|
||||||
|
|
||||||
|
static int FIO_rust_compressMultipleDestinationWarning(void* opaque)
|
||||||
|
{
|
||||||
|
FIO_rust_compress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_compress_multiple_context_t*)opaque;
|
||||||
|
return FIO_multiFilesConcatWarning(
|
||||||
|
context->fCtx, context->prefs, context->outFileName, 1 /* displayLevelCutoff */);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* FIO_rust_compressMultipleDestinationOpen(void* opaque)
|
||||||
|
{
|
||||||
|
FIO_rust_compress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_compress_multiple_context_t*)opaque;
|
||||||
|
return (void*)FIO_openDstFile(
|
||||||
|
context->fCtx, context->prefs, NULL,
|
||||||
|
context->outFileName, DEFAULT_FILE_PERMISSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FIO_rust_compressMultipleDestinationAttach(void* opaque, void* destination)
|
||||||
|
{
|
||||||
|
FIO_rust_compress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_compress_multiple_context_t*)opaque;
|
||||||
|
AIO_WritePool_setFile(context->ress->writeCtx, (FILE*)destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FIO_rust_compressMultipleDestinationClose(void* opaque)
|
||||||
|
{
|
||||||
|
FIO_rust_compress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_compress_multiple_context_t*)opaque;
|
||||||
|
if (AIO_WritePool_closeFile(context->ress->writeCtx))
|
||||||
|
EXM_THROW(29, "Write error (%s) : cannot properly close %s",
|
||||||
|
strerror(errno), context->outFileName);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int FIO_rust_compressMultipleFileCallback(void* opaque,
|
static int FIO_rust_compressMultipleFileCallback(void* opaque,
|
||||||
const char* dstFileName,
|
const char* dstFileName,
|
||||||
const char* srcFileName)
|
const char* srcFileName)
|
||||||
@@ -3771,28 +3878,21 @@ int FIO_compressMultipleFilenames(FIO_ctx_t* const fCtx,
|
|||||||
/* init */
|
/* init */
|
||||||
assert(outFileName != NULL || suffix != NULL);
|
assert(outFileName != NULL || suffix != NULL);
|
||||||
if (outFileName != NULL) { /* output into a single destination (stdout typically) */
|
if (outFileName != NULL) { /* output into a single destination (stdout typically) */
|
||||||
FILE *dstFile;
|
FIO_rust_compress_multiple_context_t callbackContext = {
|
||||||
if (FIO_multiFilesConcatWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) {
|
fCtx, prefs, &ress, compressionLevel, outFileName
|
||||||
FIO_freeCResources(&ress);
|
};
|
||||||
return 1;
|
FIO_rust_compress_multiple_projection_t files = {
|
||||||
}
|
fCtx, inFileNamesTable, outFileName,
|
||||||
dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS);
|
&callbackContext, FIO_rust_compressMultipleFileCallback
|
||||||
if (dstFile == NULL) { /* could not open outFileName */
|
};
|
||||||
error = 1;
|
FIO_rust_compress_multiple_destination_projection_t projection = {
|
||||||
} else {
|
&files,
|
||||||
FIO_rust_compress_multiple_context_t callbackContext = {
|
FIO_rust_compressMultipleDestinationWarning,
|
||||||
fCtx, prefs, &ress, compressionLevel
|
FIO_rust_compressMultipleDestinationOpen,
|
||||||
};
|
FIO_rust_compressMultipleDestinationAttach,
|
||||||
FIO_rust_compress_multiple_projection_t projection = {
|
FIO_rust_compressMultipleDestinationClose
|
||||||
fCtx, inFileNamesTable, outFileName,
|
};
|
||||||
&callbackContext, FIO_rust_compressMultipleFileCallback
|
error = FIO_rust_compressMultipleFilenamesWithDestination(&projection);
|
||||||
};
|
|
||||||
AIO_WritePool_setFile(ress.writeCtx, dstFile);
|
|
||||||
error = FIO_rust_compressMultipleFilenames(&projection);
|
|
||||||
if (AIO_WritePool_closeFile(ress.writeCtx))
|
|
||||||
EXM_THROW(29, "Write error (%s) : cannot properly close %s",
|
|
||||||
strerror(errno), outFileName);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
FIO_rust_compress_multiple_separate_context_t callbackContext = {
|
FIO_rust_compress_multiple_separate_context_t callbackContext = {
|
||||||
fCtx, prefs, &ress, outMirroredRootDirName,
|
fCtx, prefs, &ress, outMirroredRootDirName,
|
||||||
@@ -4899,8 +4999,45 @@ typedef struct {
|
|||||||
FIO_ctx_t* fCtx;
|
FIO_ctx_t* fCtx;
|
||||||
FIO_prefs_t* prefs;
|
FIO_prefs_t* prefs;
|
||||||
dRess_t* ress;
|
dRess_t* ress;
|
||||||
|
const char* outFileName;
|
||||||
} FIO_rust_decompress_multiple_context_t;
|
} FIO_rust_decompress_multiple_context_t;
|
||||||
|
|
||||||
|
static int FIO_rust_decompressMultipleDestinationWarning(void* opaque)
|
||||||
|
{
|
||||||
|
FIO_rust_decompress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_decompress_multiple_context_t*)opaque;
|
||||||
|
return FIO_multiFilesConcatWarning(
|
||||||
|
context->fCtx, context->prefs, context->outFileName, 1 /* displayLevelCutoff */);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* FIO_rust_decompressMultipleDestinationOpen(void* opaque)
|
||||||
|
{
|
||||||
|
FIO_rust_decompress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_decompress_multiple_context_t*)opaque;
|
||||||
|
FILE* const dstFile = FIO_openDstFile(
|
||||||
|
context->fCtx, context->prefs, NULL,
|
||||||
|
context->outFileName, DEFAULT_FILE_PERMISSIONS);
|
||||||
|
if (dstFile == 0) EXM_THROW(19, "cannot open %s", context->outFileName);
|
||||||
|
return (void*)dstFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FIO_rust_decompressMultipleDestinationAttach(void* opaque, void* destination)
|
||||||
|
{
|
||||||
|
FIO_rust_decompress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_decompress_multiple_context_t*)opaque;
|
||||||
|
AIO_WritePool_setFile(context->ress->writeCtx, (FILE*)destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FIO_rust_decompressMultipleDestinationClose(void* opaque)
|
||||||
|
{
|
||||||
|
FIO_rust_decompress_multiple_context_t* const context =
|
||||||
|
(FIO_rust_decompress_multiple_context_t*)opaque;
|
||||||
|
if (AIO_WritePool_closeFile(context->ress->writeCtx))
|
||||||
|
EXM_THROW(72, "Write error : %s : cannot properly close output file",
|
||||||
|
strerror(errno));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Keep private source/destination operations, format dispatch, and
|
/* Keep private source/destination operations, format dispatch, and
|
||||||
* diagnostics in C; Rust owns per-file ordering and removal policy. */
|
* diagnostics in C; Rust owns per-file ordering and removal policy. */
|
||||||
static int FIO_rust_decompressMultipleFileCallback(void* opaque,
|
static int FIO_rust_decompressMultipleFileCallback(void* opaque,
|
||||||
@@ -5015,28 +5152,22 @@ FIO_decompressMultipleFilenames(FIO_ctx_t* const fCtx,
|
|||||||
dRess_t ress = FIO_createDResources(prefs, dictFileName);
|
dRess_t ress = FIO_createDResources(prefs, dictFileName);
|
||||||
|
|
||||||
if (outFileName) {
|
if (outFileName) {
|
||||||
if (FIO_multiFilesConcatWarning(fCtx, prefs, outFileName, 1 /* displayLevelCutoff */)) {
|
FIO_rust_decompress_multiple_context_t callbackContext = {
|
||||||
FIO_freeDResources(ress);
|
fCtx, prefs, &ress, outFileName
|
||||||
return 1;
|
};
|
||||||
}
|
FIO_rust_decompress_multiple_projection_t files = {
|
||||||
if (!prefs->testMode) {
|
fCtx, srcNamesTable, outFileName,
|
||||||
FILE* dstFile = FIO_openDstFile(fCtx, prefs, NULL, outFileName, DEFAULT_FILE_PERMISSIONS);
|
&callbackContext, FIO_rust_decompressMultipleFileCallback
|
||||||
if (dstFile == 0) EXM_THROW(19, "cannot open %s", outFileName);
|
};
|
||||||
AIO_WritePool_setFile(ress.writeCtx, dstFile);
|
FIO_rust_decompress_multiple_destination_projection_t projection = {
|
||||||
}
|
&files,
|
||||||
{
|
!prefs->testMode,
|
||||||
FIO_rust_decompress_multiple_context_t callbackContext = {
|
FIO_rust_decompressMultipleDestinationWarning,
|
||||||
fCtx, prefs, &ress
|
FIO_rust_decompressMultipleDestinationOpen,
|
||||||
};
|
FIO_rust_decompressMultipleDestinationAttach,
|
||||||
FIO_rust_decompress_multiple_projection_t projection = {
|
FIO_rust_decompressMultipleDestinationClose
|
||||||
fCtx, srcNamesTable, outFileName,
|
};
|
||||||
&callbackContext, FIO_rust_decompressMultipleFileCallback
|
error = FIO_rust_decompressMultipleFilenamesWithDestination(&projection);
|
||||||
};
|
|
||||||
error = FIO_rust_decompressMultipleFilenames(&projection);
|
|
||||||
}
|
|
||||||
if ((!prefs->testMode) && (AIO_WritePool_closeFile(ress.writeCtx)))
|
|
||||||
EXM_THROW(72, "Write error : %s : cannot properly close output file",
|
|
||||||
strerror(errno));
|
|
||||||
} else {
|
} else {
|
||||||
FIO_rust_decompress_multiple_separate_context_t callbackContext = {
|
FIO_rust_decompress_multiple_separate_context_t callbackContext = {
|
||||||
fCtx, prefs, &ress, outMirroredRootDirName, outDirName
|
fCtx, prefs, &ress, outMirroredRootDirName, outDirName
|
||||||
|
|||||||
@@ -602,6 +602,14 @@ pub type FIO_rust_compress_multiple_file_fn =
|
|||||||
unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int;
|
unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int;
|
||||||
pub type FIO_rust_compress_multiple_separate_file_fn =
|
pub type FIO_rust_compress_multiple_separate_file_fn =
|
||||||
unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int;
|
unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int;
|
||||||
|
pub type FIO_rust_multiple_destination_warning_fn =
|
||||||
|
unsafe extern "C" fn(*mut c_void) -> c_int;
|
||||||
|
pub type FIO_rust_multiple_destination_open_fn =
|
||||||
|
unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
||||||
|
pub type FIO_rust_multiple_destination_attach_fn =
|
||||||
|
unsafe extern "C" fn(*mut c_void, *mut c_void);
|
||||||
|
pub type FIO_rust_multiple_destination_close_fn =
|
||||||
|
unsafe extern "C" fn(*mut c_void) -> c_int;
|
||||||
|
|
||||||
/// Rust owns only the shared-destination file iteration and aggregate error
|
/// Rust owns only the shared-destination file iteration and aggregate error
|
||||||
/// handling. C retains the private preferences/resources and supplies one
|
/// handling. C retains the private preferences/resources and supplies one
|
||||||
@@ -636,6 +644,52 @@ const _: () = {
|
|||||||
assert!(size_of::<FIO_rust_compress_multiple_projection_t>() == 5 * size_of::<usize>());
|
assert!(size_of::<FIO_rust_compress_multiple_projection_t>() == 5 * size_of::<usize>());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Rust owns the shared-destination lifecycle around the file iterator. C
|
||||||
|
/// retains warning, filesystem, and write-pool operations behind callbacks.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FIO_rust_compress_multiple_destination_projection_t {
|
||||||
|
pub files: *const FIO_rust_compress_multiple_projection_t,
|
||||||
|
pub warning: Option<FIO_rust_multiple_destination_warning_fn>,
|
||||||
|
pub open_destination: Option<FIO_rust_multiple_destination_open_fn>,
|
||||||
|
pub attach_destination: Option<FIO_rust_multiple_destination_attach_fn>,
|
||||||
|
pub close_destination: Option<FIO_rust_multiple_destination_close_fn>,
|
||||||
|
}
|
||||||
|
const _: () = {
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(FIO_rust_compress_multiple_destination_projection_t, files) == 0
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(FIO_rust_compress_multiple_destination_projection_t, warning)
|
||||||
|
== size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(
|
||||||
|
FIO_rust_compress_multiple_destination_projection_t,
|
||||||
|
open_destination
|
||||||
|
) == 2 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(
|
||||||
|
FIO_rust_compress_multiple_destination_projection_t,
|
||||||
|
attach_destination
|
||||||
|
) == 3 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(
|
||||||
|
FIO_rust_compress_multiple_destination_projection_t,
|
||||||
|
close_destination
|
||||||
|
) == 4 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(size_of::<FIO_rust_multiple_destination_warning_fn>() == size_of::<usize>());
|
||||||
|
assert!(size_of::<FIO_rust_multiple_destination_open_fn>() == size_of::<usize>());
|
||||||
|
assert!(size_of::<FIO_rust_multiple_destination_attach_fn>() == size_of::<usize>());
|
||||||
|
assert!(size_of::<FIO_rust_multiple_destination_close_fn>() == size_of::<usize>());
|
||||||
|
assert!(
|
||||||
|
size_of::<FIO_rust_compress_multiple_destination_projection_t>()
|
||||||
|
== 5 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/// Rust owns separate-destination mode selection, file iteration, and
|
/// Rust owns separate-destination mode selection, file iteration, and
|
||||||
/// aggregate error handling. C retains destination-name construction, private
|
/// aggregate error handling. C retains destination-name construction, private
|
||||||
/// preferences/resources, diagnostics, and compression dispatch in the two
|
/// preferences/resources, diagnostics, and compression dispatch in the two
|
||||||
@@ -724,6 +778,54 @@ const _: () = {
|
|||||||
assert!(size_of::<FIO_rust_decompress_multiple_projection_t>() == 5 * size_of::<usize>());
|
assert!(size_of::<FIO_rust_decompress_multiple_projection_t>() == 5 * size_of::<usize>());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Rust owns the shared-destination lifecycle around the decompression file
|
||||||
|
/// iterator. Test mode skips the destination callbacks just as the original
|
||||||
|
/// C path did.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FIO_rust_decompress_multiple_destination_projection_t {
|
||||||
|
pub files: *const FIO_rust_decompress_multiple_projection_t,
|
||||||
|
pub destination_enabled: c_int,
|
||||||
|
pub warning: Option<FIO_rust_multiple_destination_warning_fn>,
|
||||||
|
pub open_destination: Option<FIO_rust_multiple_destination_open_fn>,
|
||||||
|
pub attach_destination: Option<FIO_rust_multiple_destination_attach_fn>,
|
||||||
|
pub close_destination: Option<FIO_rust_multiple_destination_close_fn>,
|
||||||
|
}
|
||||||
|
const _: () = {
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(FIO_rust_decompress_multiple_destination_projection_t, files) == 0
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(
|
||||||
|
FIO_rust_decompress_multiple_destination_projection_t,
|
||||||
|
destination_enabled
|
||||||
|
) == size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(FIO_rust_decompress_multiple_destination_projection_t, warning)
|
||||||
|
== 2 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(
|
||||||
|
FIO_rust_decompress_multiple_destination_projection_t,
|
||||||
|
open_destination
|
||||||
|
) == 3 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(
|
||||||
|
FIO_rust_decompress_multiple_destination_projection_t,
|
||||||
|
attach_destination
|
||||||
|
) == 4 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
std::mem::offset_of!(
|
||||||
|
FIO_rust_decompress_multiple_destination_projection_t,
|
||||||
|
close_destination
|
||||||
|
) == 5 * size_of::<usize>()
|
||||||
|
);
|
||||||
|
assert!(size_of::<c_int>() <= size_of::<usize>());
|
||||||
|
assert!(size_of::<FIO_rust_decompress_multiple_destination_projection_t>() == 6 * size_of::<usize>());
|
||||||
|
};
|
||||||
|
|
||||||
/// Rust owns only the separate-destination file iteration and aggregate error
|
/// Rust owns only the separate-destination file iteration and aggregate error
|
||||||
/// handling. C retains destination-name construction, source opening, format
|
/// handling. C retains destination-name construction, source opening, format
|
||||||
/// dispatch, diagnostics, and source removal through the per-source callback.
|
/// dispatch, diagnostics, and source removal through the per-source callback.
|
||||||
@@ -3372,6 +3474,49 @@ pub unsafe extern "C" fn FIO_rust_compressMultipleFilenames(
|
|||||||
error
|
error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open, attach, iterate, and close one destination shared by all input
|
||||||
|
/// files. C retains the private destination and write-pool operations.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn FIO_rust_compressMultipleFilenamesWithDestination(
|
||||||
|
projection: *const FIO_rust_compress_multiple_destination_projection_t,
|
||||||
|
) -> c_int {
|
||||||
|
assert!(!projection.is_null());
|
||||||
|
let projection = unsafe { &*projection };
|
||||||
|
assert!(!projection.files.is_null());
|
||||||
|
let files = unsafe { &*projection.files };
|
||||||
|
assert!(!files.f_ctx.is_null());
|
||||||
|
assert!(!files.input_file_names.is_null());
|
||||||
|
assert!(!files.output_file_name.is_null());
|
||||||
|
assert!(!files.opaque.is_null());
|
||||||
|
assert!(files.compress_file.is_some());
|
||||||
|
|
||||||
|
let warning = projection
|
||||||
|
.warning
|
||||||
|
.expect("shared-destination warning callback is required");
|
||||||
|
if unsafe { warning(files.opaque) } != 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let open_destination = projection
|
||||||
|
.open_destination
|
||||||
|
.expect("shared-destination open callback is required");
|
||||||
|
let destination = unsafe { open_destination(files.opaque) };
|
||||||
|
if destination.is_null() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attach_destination = projection
|
||||||
|
.attach_destination
|
||||||
|
.expect("shared-destination attach callback is required");
|
||||||
|
unsafe { attach_destination(files.opaque, destination) };
|
||||||
|
|
||||||
|
let result = unsafe { FIO_rust_compressMultipleFilenames(projection.files) };
|
||||||
|
let close_destination = projection
|
||||||
|
.close_destination
|
||||||
|
.expect("shared-destination close callback is required");
|
||||||
|
result | unsafe { close_destination(files.opaque) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Select the destination-mode callback once, then iterate files that each
|
/// Select the destination-mode callback once, then iterate files that each
|
||||||
/// receive a separate destination. C retains destination-name construction,
|
/// receive a separate destination. C retains destination-name construction,
|
||||||
/// resource state, diagnostics, and compression dispatch; Rust preserves the
|
/// resource state, diagnostics, and compression dispatch; Rust preserves the
|
||||||
@@ -3449,6 +3594,53 @@ pub unsafe extern "C" fn FIO_rust_decompressMultipleFilenames(
|
|||||||
error
|
error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run the shared-destination decompression lifecycle. Test mode still runs
|
||||||
|
/// the warning and file iterator, but intentionally skips destination I/O.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn FIO_rust_decompressMultipleFilenamesWithDestination(
|
||||||
|
projection: *const FIO_rust_decompress_multiple_destination_projection_t,
|
||||||
|
) -> c_int {
|
||||||
|
assert!(!projection.is_null());
|
||||||
|
let projection = unsafe { &*projection };
|
||||||
|
assert!(!projection.files.is_null());
|
||||||
|
let files = unsafe { &*projection.files };
|
||||||
|
assert!(!files.f_ctx.is_null());
|
||||||
|
assert!(!files.src_names_table.is_null());
|
||||||
|
assert!(!files.out_file_name.is_null());
|
||||||
|
assert!(!files.opaque.is_null());
|
||||||
|
assert!(files.decompress_file.is_some());
|
||||||
|
|
||||||
|
let warning = projection
|
||||||
|
.warning
|
||||||
|
.expect("shared-destination warning callback is required");
|
||||||
|
if unsafe { warning(files.opaque) } != 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if projection.destination_enabled == 0 {
|
||||||
|
return unsafe { FIO_rust_decompressMultipleFilenames(projection.files) };
|
||||||
|
}
|
||||||
|
|
||||||
|
let open_destination = projection
|
||||||
|
.open_destination
|
||||||
|
.expect("shared-destination open callback is required");
|
||||||
|
let destination = unsafe { open_destination(files.opaque) };
|
||||||
|
if destination.is_null() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attach_destination = projection
|
||||||
|
.attach_destination
|
||||||
|
.expect("shared-destination attach callback is required");
|
||||||
|
unsafe { attach_destination(files.opaque, destination) };
|
||||||
|
|
||||||
|
let result = unsafe { FIO_rust_decompressMultipleFilenames(projection.files) };
|
||||||
|
let close_destination = projection
|
||||||
|
.close_destination
|
||||||
|
.expect("shared-destination close callback is required");
|
||||||
|
result | unsafe { close_destination(files.opaque) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate files that each receive a separate destination. The C callback
|
/// Iterate files that each receive a separate destination. The C callback
|
||||||
/// retains destination-name construction, private I/O, format dispatch, and
|
/// retains destination-name construction, private I/O, format dispatch, and
|
||||||
/// diagnostics; Rust owns per-file ordering and removal policy.
|
/// diagnostics; Rust owns per-file ordering and removal policy.
|
||||||
@@ -7240,6 +7432,191 @@ mod tests {
|
|||||||
status
|
status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MultipleDestinationSessionState {
|
||||||
|
events: Vec<&'static str>,
|
||||||
|
sources: Vec<*const c_char>,
|
||||||
|
destinations: Vec<*const c_char>,
|
||||||
|
statuses: Vec<c_int>,
|
||||||
|
allow_open: bool,
|
||||||
|
warning_status: c_int,
|
||||||
|
close_status: c_int,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn record_multiple_destination_warning(opaque: *mut c_void) -> c_int {
|
||||||
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
||||||
|
state.events.push("warning");
|
||||||
|
state.warning_status
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn record_multiple_destination_open(opaque: *mut c_void) -> *mut c_void {
|
||||||
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
||||||
|
state.events.push("open");
|
||||||
|
if state.allow_open {
|
||||||
|
ptr::dangling_mut::<c_void>()
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn record_multiple_destination_attach(
|
||||||
|
opaque: *mut c_void,
|
||||||
|
destination: *mut c_void,
|
||||||
|
) {
|
||||||
|
assert!(!destination.is_null());
|
||||||
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
||||||
|
state.events.push("attach");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn record_multiple_destination_close(opaque: *mut c_void) -> c_int {
|
||||||
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
||||||
|
state.events.push("close");
|
||||||
|
state.close_status
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn record_multiple_destination_file(
|
||||||
|
opaque: *mut c_void,
|
||||||
|
destination: *const c_char,
|
||||||
|
source: *const c_char,
|
||||||
|
) -> c_int {
|
||||||
|
let state = unsafe { &mut *opaque.cast::<MultipleDestinationSessionState>() };
|
||||||
|
let status = state
|
||||||
|
.statuses
|
||||||
|
.get(state.sources.len())
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0);
|
||||||
|
state.events.push("file");
|
||||||
|
state.sources.push(source);
|
||||||
|
state.destinations.push(destination);
|
||||||
|
status
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compression_multiple_destination_session_owns_lifecycle() {
|
||||||
|
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
||||||
|
let destination = c"archive.zst";
|
||||||
|
let mut context = FIO_rust_compression_context_t {
|
||||||
|
nbFilesTotal: 2,
|
||||||
|
hasStdinInput: 0,
|
||||||
|
hasStdoutOutput: 0,
|
||||||
|
currFileIdx: 0,
|
||||||
|
nbFilesProcessed: 0,
|
||||||
|
totalBytesInput: 0,
|
||||||
|
totalBytesOutput: 0,
|
||||||
|
};
|
||||||
|
let mut state = MultipleDestinationSessionState {
|
||||||
|
allow_open: true,
|
||||||
|
statuses: vec![0, 4],
|
||||||
|
..MultipleDestinationSessionState::default()
|
||||||
|
};
|
||||||
|
let files = FIO_rust_compress_multiple_projection_t {
|
||||||
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
||||||
|
input_file_names: source_names.as_ptr(),
|
||||||
|
output_file_name: destination.as_ptr(),
|
||||||
|
opaque: (&mut state as *mut MultipleDestinationSessionState).cast(),
|
||||||
|
compress_file: Some(record_multiple_destination_file),
|
||||||
|
};
|
||||||
|
let projection = FIO_rust_compress_multiple_destination_projection_t {
|
||||||
|
files: &files,
|
||||||
|
warning: Some(record_multiple_destination_warning),
|
||||||
|
open_destination: Some(record_multiple_destination_open),
|
||||||
|
attach_destination: Some(record_multiple_destination_attach),
|
||||||
|
close_destination: Some(record_multiple_destination_close),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { FIO_rust_compressMultipleFilenamesWithDestination(&projection) },
|
||||||
|
4
|
||||||
|
);
|
||||||
|
assert_eq!(state.events, ["warning", "open", "attach", "file", "file", "close"]);
|
||||||
|
assert_eq!(state.sources, source_names);
|
||||||
|
assert_eq!(state.destinations, vec![destination.as_ptr(); 2]);
|
||||||
|
assert_eq!(context.currFileIdx, 2);
|
||||||
|
assert_eq!(context.nbFilesProcessed, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compression_multiple_destination_session_stops_on_open_failure() {
|
||||||
|
let source_names = [c"one".as_ptr()];
|
||||||
|
let destination = c"archive.zst";
|
||||||
|
let mut context = FIO_rust_compression_context_t {
|
||||||
|
nbFilesTotal: 1,
|
||||||
|
hasStdinInput: 0,
|
||||||
|
hasStdoutOutput: 0,
|
||||||
|
currFileIdx: 0,
|
||||||
|
nbFilesProcessed: 3,
|
||||||
|
totalBytesInput: 0,
|
||||||
|
totalBytesOutput: 0,
|
||||||
|
};
|
||||||
|
let mut state = MultipleDestinationSessionState::default();
|
||||||
|
let files = FIO_rust_compress_multiple_projection_t {
|
||||||
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
||||||
|
input_file_names: source_names.as_ptr(),
|
||||||
|
output_file_name: destination.as_ptr(),
|
||||||
|
opaque: (&mut state as *mut MultipleDestinationSessionState).cast(),
|
||||||
|
compress_file: Some(record_multiple_destination_file),
|
||||||
|
};
|
||||||
|
let projection = FIO_rust_compress_multiple_destination_projection_t {
|
||||||
|
files: &files,
|
||||||
|
warning: Some(record_multiple_destination_warning),
|
||||||
|
open_destination: Some(record_multiple_destination_open),
|
||||||
|
attach_destination: Some(record_multiple_destination_attach),
|
||||||
|
close_destination: Some(record_multiple_destination_close),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { FIO_rust_compressMultipleFilenamesWithDestination(&projection) },
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(state.events, ["warning", "open"]);
|
||||||
|
assert!(state.sources.is_empty());
|
||||||
|
assert_eq!(context.currFileIdx, 0);
|
||||||
|
assert_eq!(context.nbFilesProcessed, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decompression_multiple_destination_session_skips_io_in_test_mode() {
|
||||||
|
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
||||||
|
let destination = c"archive";
|
||||||
|
let mut context = FIO_rust_compression_context_t {
|
||||||
|
nbFilesTotal: 2,
|
||||||
|
hasStdinInput: 0,
|
||||||
|
hasStdoutOutput: 0,
|
||||||
|
currFileIdx: 0,
|
||||||
|
nbFilesProcessed: 0,
|
||||||
|
totalBytesInput: 0,
|
||||||
|
totalBytesOutput: 0,
|
||||||
|
};
|
||||||
|
let mut state = MultipleDestinationSessionState {
|
||||||
|
statuses: vec![0, 8],
|
||||||
|
..MultipleDestinationSessionState::default()
|
||||||
|
};
|
||||||
|
let files = FIO_rust_decompress_multiple_projection_t {
|
||||||
|
f_ctx: (&mut context as *mut FIO_rust_compression_context_t).cast(),
|
||||||
|
src_names_table: source_names.as_ptr(),
|
||||||
|
out_file_name: destination.as_ptr(),
|
||||||
|
opaque: (&mut state as *mut MultipleDestinationSessionState).cast(),
|
||||||
|
decompress_file: Some(record_multiple_destination_file),
|
||||||
|
};
|
||||||
|
let projection = FIO_rust_decompress_multiple_destination_projection_t {
|
||||||
|
files: &files,
|
||||||
|
destination_enabled: 0,
|
||||||
|
warning: Some(record_multiple_destination_warning),
|
||||||
|
open_destination: Some(record_multiple_destination_open),
|
||||||
|
attach_destination: Some(record_multiple_destination_attach),
|
||||||
|
close_destination: Some(record_multiple_destination_close),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
unsafe { FIO_rust_decompressMultipleFilenamesWithDestination(&projection) },
|
||||||
|
8
|
||||||
|
);
|
||||||
|
assert_eq!(state.events, ["warning", "file", "file"]);
|
||||||
|
assert_eq!(state.sources, source_names);
|
||||||
|
assert_eq!(context.currFileIdx, 2);
|
||||||
|
assert_eq!(context.nbFilesProcessed, 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compression_multiple_separate_destinations_selects_mirror_callback_only() {
|
fn compression_multiple_separate_destinations_selects_mirror_callback_only() {
|
||||||
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
let source_names = [c"one".as_ptr(), c"two".as_ptr()];
|
||||||
|
|||||||
Reference in New Issue
Block a user