feat(cli): move destination-open orchestration to Rust
FIO_openDstFile() still owned the complete destination policy loop in C even though the filesystem opener and status/action classifier were already Rust leaves. That left confirmation, sparse-mode adjustment, overwrite removal, and retry ordering duplicated beside private FILE* and CLI state. Project the C-owned callbacks for diagnostics, preference mutation, user confirmation, stdout setup, and existing-file removal. Rust now owns the retry/order state machine and calls the existing narrow opener for each attempt; C keeps private preferences and context, exact diagnostics, and FILE* ownership. The callback layout is asserted on both sides, and focused tests cover status ordering, confirmation/removal retry, setvbuf preservation, and invalid policy inputs. Test Plan: - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 make -j1` -- passed, including the single-thread library, MT library, and CLI binary. - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings` -- passed. - `ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/cli/Cargo.toml --all-targets` -- passed: 206 tests. - `git diff --cached --check` -- passed.
This commit is contained in:
+138
-103
@@ -358,8 +358,8 @@ int FIO_rust_multiFilesConcatWarning(int nbFilesTotal,
|
||||
|
||||
|
||||
/* These policy and filesystem leaves are implemented by the Rust CLI archive.
|
||||
* The declarations in fileio.h remain the C ABI shims while file-I/O
|
||||
* orchestration and diagnostics stay here. */
|
||||
* The declarations in fileio.h remain the C ABI shims while C keeps private
|
||||
* resource/layout state and exact diagnostics around the Rust policy calls. */
|
||||
/* FIO_highbit64() is a Rust-owned scalar policy leaf. */
|
||||
unsigned FIO_highbit64(unsigned long long v);
|
||||
unsigned long long FIO_getLargestFileSize(const char** inFileNames, unsigned nbFiles);
|
||||
@@ -439,6 +439,44 @@ enum {
|
||||
};
|
||||
int FIO_rust_openDstFileAction(int status, int overwrite, int displayLevel,
|
||||
int confirmation, int sparseAdjusted);
|
||||
typedef void (*FIO_rust_open_dst_action_fn)(void* opaque, int action,
|
||||
const char* dstFileName);
|
||||
typedef void (*FIO_rust_open_dst_adjust_sparse_fn)(void* opaque,
|
||||
int isDstRegFile);
|
||||
typedef void* (*FIO_rust_open_dst_stdout_fn)(void* opaque);
|
||||
typedef int (*FIO_rust_open_dst_confirm_fn)(void* opaque);
|
||||
typedef void (*FIO_rust_open_dst_remove_fn)(void* opaque,
|
||||
const char* dstFileName);
|
||||
typedef struct {
|
||||
void* opaque;
|
||||
FIO_rust_open_dst_action_fn displayAction;
|
||||
FIO_rust_open_dst_adjust_sparse_fn adjustSparse;
|
||||
FIO_rust_open_dst_stdout_fn useStdout;
|
||||
FIO_rust_open_dst_confirm_fn confirm;
|
||||
FIO_rust_open_dst_remove_fn removeExisting;
|
||||
} FIO_rust_openDstFileCallbacks_t;
|
||||
typedef char FIO_rust_open_dst_callback_sizes[
|
||||
(sizeof(FIO_rust_open_dst_action_fn) == sizeof(void*)
|
||||
&& sizeof(FIO_rust_open_dst_adjust_sparse_fn) == sizeof(void*)
|
||||
&& sizeof(FIO_rust_open_dst_stdout_fn) == sizeof(void*)
|
||||
&& sizeof(FIO_rust_open_dst_confirm_fn) == sizeof(void*)
|
||||
&& sizeof(FIO_rust_open_dst_remove_fn) == sizeof(void*)) ? 1 : -1];
|
||||
typedef char FIO_rust_open_dst_callback_offsets[
|
||||
(offsetof(FIO_rust_openDstFileCallbacks_t, opaque) == 0
|
||||
&& offsetof(FIO_rust_openDstFileCallbacks_t, displayAction) == sizeof(void*)
|
||||
&& offsetof(FIO_rust_openDstFileCallbacks_t, adjustSparse) == 2 * sizeof(void*)
|
||||
&& offsetof(FIO_rust_openDstFileCallbacks_t, useStdout) == 3 * sizeof(void*)
|
||||
&& offsetof(FIO_rust_openDstFileCallbacks_t, confirm) == 4 * sizeof(void*)
|
||||
&& offsetof(FIO_rust_openDstFileCallbacks_t, removeExisting) == 5 * sizeof(void*)
|
||||
&& sizeof(FIO_rust_openDstFileCallbacks_t) == 6 * sizeof(void*)) ? 1 : -1];
|
||||
void* FIO_rust_openDstFileOrchestrate(
|
||||
int testMode,
|
||||
int overwrite,
|
||||
int displayLevel,
|
||||
const char* srcFileName,
|
||||
const char* dstFileName,
|
||||
int mode,
|
||||
const FIO_rust_openDstFileCallbacks_t* callbacks);
|
||||
int FIO_rust_openDstFile(int testMode,
|
||||
int allowExisting,
|
||||
const char* srcFileName,
|
||||
@@ -780,6 +818,92 @@ static FILE* FIO_openSrcFile(const FIO_prefs_t* const prefs, const char* srcFile
|
||||
}
|
||||
}
|
||||
|
||||
/* C-owned state for the Rust destination-open policy callbacks. */
|
||||
typedef struct {
|
||||
FIO_ctx_t* fCtx;
|
||||
FIO_prefs_t* prefs;
|
||||
const char* dstFileName;
|
||||
} FIO_openDstFileCallbackContext_t;
|
||||
|
||||
static void
|
||||
FIO_openDstAdjustSparse(void* opaque, int isDstRegFile)
|
||||
{
|
||||
FIO_openDstFileCallbackContext_t* const context =
|
||||
(FIO_openDstFileCallbackContext_t*)opaque;
|
||||
if (context->prefs->sparseFileSupport == 1) {
|
||||
context->prefs->sparseFileSupport = ZSTD_SPARSE_DEFAULT;
|
||||
if (!isDstRegFile) {
|
||||
context->prefs->sparseFileSupport = 0;
|
||||
DISPLAYLEVEL(4, "Sparse File Support is disabled when output is not a file \n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void*
|
||||
FIO_openDstUseStdout(void* opaque)
|
||||
{
|
||||
FIO_openDstFileCallbackContext_t* const context =
|
||||
(FIO_openDstFileCallbackContext_t*)opaque;
|
||||
DISPLAYLEVEL(4,"Using stdout for output \n");
|
||||
SET_BINARY_MODE(stdout);
|
||||
if (context->prefs->sparseFileSupport == 1) {
|
||||
context->prefs->sparseFileSupport = 0;
|
||||
DISPLAYLEVEL(4, "Sparse File Support is automatically disabled on stdout ; try --sparse \n");
|
||||
}
|
||||
return stdout;
|
||||
}
|
||||
|
||||
static void
|
||||
FIO_openDstDisplayAction(void* opaque, int action, const char* dstFileName)
|
||||
{
|
||||
(void)opaque;
|
||||
switch (action) {
|
||||
case FIO_RUST_OPEN_DST_ACTION_SAME_FILE:
|
||||
DISPLAYLEVEL(1, "zstd: Refusing to open an output file which will overwrite the input file \n");
|
||||
break;
|
||||
case FIO_RUST_OPEN_DST_ACTION_NULL_DEVICE_REGULAR:
|
||||
EXM_THROW(40, "%s is unexpectedly categorized as a regular file",
|
||||
dstFileName);
|
||||
return;
|
||||
case FIO_RUST_OPEN_DST_ACTION_EXISTING_QUIET_ABORT:
|
||||
DISPLAYLEVEL(1, "zstd: %s already exists; not overwritten \n",
|
||||
dstFileName);
|
||||
break;
|
||||
case FIO_RUST_OPEN_DST_ACTION_OPEN_FAILED:
|
||||
DISPLAYLEVEL(1, "zstd: %s: %s\n", dstFileName, strerror(errno));
|
||||
break;
|
||||
case FIO_RUST_OPEN_DST_ACTION_SETBUF_FAILED:
|
||||
DISPLAYLEVEL(2, "Warning: setvbuf failed for %s\n", dstFileName);
|
||||
break;
|
||||
case FIO_RUST_OPEN_DST_ACTION_INVALID:
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
FIO_openDstConfirm(void* opaque)
|
||||
{
|
||||
FIO_openDstFileCallbackContext_t* const context =
|
||||
(FIO_openDstFileCallbackContext_t*)opaque;
|
||||
DISPLAY("zstd: %s already exists; ", context->dstFileName);
|
||||
return UTIL_requireUserConfirmation(
|
||||
"overwrite (y/n) ? ", "Not overwritten \n", "yY",
|
||||
context->fCtx->hasStdinInput)
|
||||
? FIO_RUST_OPEN_DST_CONFIRMATION_ABORT
|
||||
: FIO_RUST_OPEN_DST_CONFIRMATION_ACCEPT;
|
||||
}
|
||||
|
||||
static void
|
||||
FIO_openDstRemoveExisting(void* opaque, const char* dstFileName)
|
||||
{
|
||||
(void)opaque;
|
||||
/* Keep the existing C wrapper so its stat/non-regular diagnostics remain
|
||||
* unchanged. Rust calls this only after the overwrite policy allows it. */
|
||||
(void)FIO_removeFile(dstFileName);
|
||||
}
|
||||
|
||||
/** FIO_openDstFile() :
|
||||
* condition : `dstFileName` must be non-NULL.
|
||||
* @result : FILE* to `dstFileName`, or NULL if it fails */
|
||||
@@ -788,107 +912,18 @@ FIO_openDstFile(FIO_ctx_t* fCtx, FIO_prefs_t* const prefs,
|
||||
const char* srcFileName, const char* dstFileName,
|
||||
const int mode)
|
||||
{
|
||||
int isDstRegFile = 0;
|
||||
int allowExisting = 0;
|
||||
int sparseAdjusted = 0;
|
||||
int confirmation = FIO_RUST_OPEN_DST_CONFIRMATION_UNASKED;
|
||||
int status;
|
||||
FILE* f = NULL;
|
||||
|
||||
status = FIO_rust_openDstFile(prefs->testMode, allowExisting,
|
||||
srcFileName, dstFileName, mode,
|
||||
&isDstRegFile, &f);
|
||||
for (;;) {
|
||||
int const action = FIO_rust_openDstFileAction(
|
||||
status, prefs->overwrite, g_display_prefs.displayLevel,
|
||||
confirmation, sparseAdjusted);
|
||||
|
||||
if (action == FIO_RUST_OPEN_DST_ACTION_TEST_MODE)
|
||||
return NULL; /* do not open file in test mode */
|
||||
|
||||
assert(dstFileName != NULL);
|
||||
switch (action) {
|
||||
case FIO_RUST_OPEN_DST_ACTION_STDOUT:
|
||||
DISPLAYLEVEL(4,"Using stdout for output \n");
|
||||
SET_BINARY_MODE(stdout);
|
||||
if (prefs->sparseFileSupport == 1) {
|
||||
prefs->sparseFileSupport = 0;
|
||||
DISPLAYLEVEL(4, "Sparse File Support is automatically disabled on stdout ; try --sparse \n");
|
||||
}
|
||||
return stdout;
|
||||
case FIO_RUST_OPEN_DST_ACTION_SAME_FILE:
|
||||
DISPLAYLEVEL(1, "zstd: Refusing to open an output file which will overwrite the input file \n");
|
||||
return NULL;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_ADJUST_SPARSE:
|
||||
/* Rust orders this once-only policy action before all ordinary
|
||||
* destination outcomes. Keep the preference mutation and its
|
||||
* diagnostic in C, and let the next loop iteration classify the
|
||||
* same status again. */
|
||||
if (prefs->sparseFileSupport == 1) {
|
||||
prefs->sparseFileSupport = ZSTD_SPARSE_DEFAULT;
|
||||
if (!isDstRegFile) {
|
||||
prefs->sparseFileSupport = 0;
|
||||
DISPLAYLEVEL(4, "Sparse File Support is disabled when output is not a file \n");
|
||||
}
|
||||
}
|
||||
sparseAdjusted = 1;
|
||||
continue;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_NULL_DEVICE_REGULAR:
|
||||
EXM_THROW(40, "%s is unexpectedly categorized as a regular file",
|
||||
dstFileName);
|
||||
return NULL;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_EXISTING_QUIET_ABORT:
|
||||
/* No interaction possible. */
|
||||
DISPLAYLEVEL(1, "zstd: %s already exists; not overwritten \n",
|
||||
dstFileName);
|
||||
return NULL;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_EXISTING_PROMPT:
|
||||
DISPLAY("zstd: %s already exists; ", dstFileName);
|
||||
confirmation = UTIL_requireUserConfirmation(
|
||||
"overwrite (y/n) ? ", "Not overwritten \n", "yY",
|
||||
fCtx->hasStdinInput)
|
||||
? FIO_RUST_OPEN_DST_CONFIRMATION_ABORT
|
||||
: FIO_RUST_OPEN_DST_CONFIRMATION_ACCEPT;
|
||||
continue;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_EXISTING_ABORT:
|
||||
/* UTIL_requireUserConfirmation() already emitted the exact
|
||||
* existing diagnostic before returning the abort result. */
|
||||
return NULL;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_EXISTING_REMOVE:
|
||||
/* Keep the existing C wrapper so its stat/non-regular diagnostics
|
||||
* remain unchanged. The next Rust call opens with O_TRUNC. */
|
||||
FIO_removeFile(dstFileName);
|
||||
allowExisting = 1;
|
||||
f = NULL;
|
||||
confirmation = FIO_RUST_OPEN_DST_CONFIRMATION_UNASKED;
|
||||
status = FIO_rust_openDstFile(prefs->testMode, allowExisting,
|
||||
srcFileName, dstFileName, mode,
|
||||
&isDstRegFile, &f);
|
||||
continue;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_OPEN_FAILED:
|
||||
DISPLAYLEVEL(1, "zstd: %s: %s\n", dstFileName, strerror(errno));
|
||||
return NULL;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_SETBUF_FAILED:
|
||||
DISPLAYLEVEL(2, "Warning: setvbuf failed for %s\n", dstFileName);
|
||||
return f;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_SUCCESS:
|
||||
return f;
|
||||
|
||||
case FIO_RUST_OPEN_DST_ACTION_INVALID:
|
||||
default:
|
||||
assert(0);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
FIO_openDstFileCallbackContext_t context = { fCtx, prefs, dstFileName };
|
||||
FIO_rust_openDstFileCallbacks_t callbacks = {
|
||||
&context,
|
||||
FIO_openDstDisplayAction,
|
||||
FIO_openDstAdjustSparse,
|
||||
FIO_openDstUseStdout,
|
||||
FIO_openDstConfirm,
|
||||
FIO_openDstRemoveExisting
|
||||
};
|
||||
return (FILE*)FIO_rust_openDstFileOrchestrate(
|
||||
prefs->testMode, prefs->overwrite, g_display_prefs.displayLevel,
|
||||
srcFileName, dstFileName, mode, &callbacks);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user