From 1a93c49b8c4a0a0654a3cf1a6b782067a848048f Mon Sep 17 00:00:00 2001 From: "Claude Haiku 4.5" Date: Thu, 25 Jun 2026 15:33:51 +0100 Subject: [PATCH] fix: route archive I/O through C++ streams for embedded mode compatibility EmbeddedCli redirects std::cout/std::cin to stringstreams, but libarchive's archive_write_open_FILE(stdout) and archive_read_open_fd(STDIN_FILENO) operate on C-level handles that bypass that redirection. On Windows GUI apps these handles are disconnected, so spratpack's multi-atlas TAR output was silently lost and _setmode calls on the invalid fd caused a fatal exit. Replace direct FILE*/fd usage with archive_write_open/archive_read_open callbacks that go through std::cout/std::cin, matching the approach already used in spratunpack. Make _setmode failures non-fatal since the C-level handles are irrelevant in embedded mode. --- VERSION | 2 +- src/commands/spratlayout_command.cpp | 17 ++++++++++++----- src/commands/spratpack_command.cpp | 25 ++++++++++++++++--------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/VERSION b/VERSION index 97bd28b..bf2a1db 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.11.7 +v0.11.8 diff --git a/src/commands/spratlayout_command.cpp b/src/commands/spratlayout_command.cpp index 7375fe7..cd14889 100644 --- a/src/commands/spratlayout_command.cpp +++ b/src/commands/spratlayout_command.cpp @@ -10,9 +10,6 @@ #include #include #include -#ifndef STDIN_FILENO -#define STDIN_FILENO 0 -#endif #ifndef _O_BINARY #define _O_BINARY 0x8000 #endif @@ -1085,8 +1082,18 @@ bool extract_tar_from_stdin(const fs::path& output_dir) { archive_read_support_format_all(a); archive_read_support_filter_all(a); - // Open from stdin - if (archive_read_open_fd(a, STDIN_FILENO, k_tar_read_buffer_size) != ARCHIVE_OK) { + // Use a read callback so that input flows through std::cin. In embedded + // mode std::cin is redirected to a stringstream; reading from the OS-level + // stdin fd would bypass that redirection and receive no data. + auto tar_read_cb = [](struct archive* /*unused*/, void* /*client_data*/, + const void** buffer) -> la_ssize_t { + static thread_local std::vector buf(k_tar_read_buffer_size); + std::cin.read(buf.data(), static_cast(buf.size())); + auto n = std::cin.gcount(); + *buffer = buf.data(); + return static_cast(n); + }; + if (archive_read_open(a, nullptr, nullptr, tar_read_cb, nullptr) != ARCHIVE_OK) { std::cerr << tr("Error: Failed to open stdin for archive extraction: ") << archive_error_string(a) << '\n'; archive_read_free(a); return false; diff --git a/src/commands/spratpack_command.cpp b/src/commands/spratpack_command.cpp index 920b313..104b078 100644 --- a/src/commands/spratpack_command.cpp +++ b/src/commands/spratpack_command.cpp @@ -640,12 +640,21 @@ int run_spratpack(int argc, char** argv) { a.reset(archive_write_new()); archive_write_set_format_pax_restricted(a.get()); #ifdef _WIN32 - if (_setmode(_fileno(stdout), _O_BINARY) == -1) { - std::cerr << tr("Failed to set stdout to binary mode\n"); - return 1; - } + // Non-fatal: in embedded mode stdout may not be a real file handle. + _setmode(_fileno(stdout), _O_BINARY); #endif - if (archive_write_open_FILE(a.get(), stdout) != ARCHIVE_OK) { + // Use a write callback instead of archive_write_open_FILE so that + // output flows through std::cout. In embedded mode std::cout is + // redirected to a stringstream; writing to the C-level stdout FILE* + // would bypass that redirection and lose all data. + auto tar_write_cb = [](struct archive* /*unused*/, void* /*client_data*/, + const void* buffer, size_t length) -> la_ssize_t { + std::cout.write(static_cast(buffer), + static_cast(length)); + if (std::cout.fail()) return -1; + return static_cast(length); + }; + if (archive_write_open(a.get(), nullptr, nullptr, tar_write_cb, nullptr) != ARCHIVE_OK) { std::cerr << tr("Failed to open TAR stream on stdout: ") << archive_error_string(a.get()) << "\n"; return 1; } @@ -947,10 +956,8 @@ int run_spratpack(int argc, char** argv) { archive_entry_free(entry); } else if (output_pattern.empty()) { #ifdef _WIN32 - if (_setmode(_fileno(stdout), _O_BINARY) == -1) { - std::cerr << tr("Failed to set stdout to binary mode\n"); - return 1; - } + // Non-fatal: in embedded mode stdout may not be a real file handle. + _setmode(_fileno(stdout), _O_BINARY); #endif std::cout.write(reinterpret_cast(output_data.data()), static_cast(output_data.size())); } else {