Skip to content

fix(extract): read libarchive's error after the write, not before - #126

Open
Junker der Provinz (junkerderprovinz) wants to merge 1 commit into
unraid:mainfrom
junkerderprovinz:fix/write-disk-error-argument-order
Open

Junker der Provinz (junkerderprovinz) wants to merge 1 commit into
unraid:mainfrom
junkerderprovinz:fix/write-disk-error-argument-order

Conversation

@junkerderprovinz

@junkerderprovinz Junker der Provinz (junkerderprovinz) commented Aug 23, 2026 •

Copy link
Copy Markdown

Fixes #122.

The bug

extractMultiFileRun() had four call sites shaped like:

Unraid::requireArchiveWriteSuccess(batcher.Add(buff, size, offset),
                                   archive_error_string(ext));

C++ leaves the evaluation order of function arguments unspecified, and this project's Windows build uses MinGW GCC (win64_mingw / tools_mingw1310 in .github/workflows/build.yml), which evaluates them right to left. That means archive_error_string(ext) was read before the write ran, and archive_write_header() had already cleared the error slot for that entry a few lines earlier, so the read always returned NULL. Every failure at these four sites therefore fell back to the generic "Failed to write a file to the target drive" message and lost archive_errno(), which is the actual Win32-mapped error code, exactly what #122 says it couldn't capture.

The header-check path a few lines above does this correctly today, because r is assigned as its own statement first — that's the tell that this was an oversight on the write path specifically, not a deliberate choice.

The fix

At all four sites, the call that can fail is computed into a named local as its own statement, so the compiler can no longer observably reorder anything, and a new small helper (_requireWriteSucceeded, beside the existing _checkResult in the same file) logs archive_error_string(ext) and archive_errno(ext) via qWarning() (so --log-file captures it, same mechanism the header path already uses) before delegating to the existing, unmodified Unraid::requireArchiveWriteSuccess().

archive_write_result.h itself is left untouched on purpose. It's dependency-free and has its own standalone unit test target that doesn't link Qt or libarchive; extending its signature to carry the errno would have dragged both into that test.

Two small things beyond the four sites, both deliberate, flagged here so they're a decision rather than a surprise:

  • The exception text users see now comes from libarchive directly instead of the generic fallback. That's not a translation regression, since the fallback string was never translated either (it flows into a tr() wrapper one level up, only the wrapper is translated) — it's the actual fix, just visible in the UI too.
  • The adjacent header-check qWarning() also gained archive_errno() logging. Same failure family, same function, one line — leaving it out would mean the header path is still the one place that can't name its Win32 cause.

Verification

I could not build this locally: the environment I had available is GCC only, with no Qt6 and no CMake, and libarchive is fetched at configure time, so even a syntax-only check of the real translation unit was out of reach.

Instead I verified the change against a harness that includes the real block_batcher.h and archive_write_result.h, uses the ARCHIVE_* values from libarchive v3.8.7 as pinned in src/dependencies/libarchive.cmake, and compiles the new helper's exact committed text (extracted directly from the file, not retyped). That harness reproduces the old behavior (generic fallback message, errno lost) and the new one (libarchive's own message plus errno) at all four call sites, confirms the log predicate and the throw predicate agree for every result value in range, confirms the null-pathname path, and independently confirms the underlying GCC argument-evaluation-order mechanism directly (read-before-write at both -O0 and -O2). It also confirms qWarning() << (pathname ? pathname : "target drive") resolves to the const char* overload, not something surprising, by reconstructing Qt's actual QDebug overload set.

What it does not do is compile the real translation unit against real Qt6/libarchive, so CI is the first genuine build of this change on Windows, macOS and Linux.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of archive write failures during downloads and extraction.
    • Added clearer diagnostics, including the affected path and system error details.
    • Ensured failures are detected consistently when writing data, finalizing entries, and closing archives.

The four archive_write_disk() call sites in extractMultiFileRun() passed
the write result and archive_error_string(ext) as two arguments of a
single requireArchiveWriteSuccess() call. C++ leaves the evaluation order
of function arguments unspecified, and GCC, which is what the Windows
build uses (win64_mingw / tools_mingw1310), evaluates them right to left.
The error slot was therefore read before the write ran. Since
archive_write_header() had already cleared it for that entry, the read
always returned NULL and every write failure surfaced as the generic
"Failed to write a file to the target drive" fallback, discarding
libarchive's own message.

Sequence each write into its own statement and hand the already known
result to a new _requireWriteSucceeded() helper, which logs the result,
archive_errno(ext) and libarchive's message through qWarning() before
turning it into an exception. archive_errno() carries the Win32 code that
libarchive's la_dosmaperr() mapped from GetLastError(), which is the
value needed to name the real cause of a failed write, and qWarning() is
the path --log-file captures. The entry-header warning a few lines above
now logs archive_errno() as well, so both paths report the same detail.

archive_write_result.h is deliberately left alone: it is unit tested in
isolation and must not grow a Qt or libarchive dependency.

Fixes unraid#122
@coderabbitai

coderabbitai Bot commented Aug 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f7cfe131-5f7b-45a4-8567-f5e3d313d480

📥 Commits

Reviewing files that changed from the base of the PR and between 43ccce0 and ff73728.

📒 Files selected for processing (1)
  • src/downloadextractthread.cpp

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The archive extraction path now validates libarchive writes after each operation. It logs operation-specific failure details, including pathname, errno, and libarchive messages. Entry-header diagnostics also include errno.

Changes

Archive write diagnostics

Layer / File(s) Summary
Sequenced write validation and diagnostics
src/downloadextractthread.cpp
Added _requireWriteSucceeded() for detailed write-failure logging. Data writes, buffered flushes, entry finalization, and archive close now execute before result validation. Header warnings now include errno.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to ff737

This is a localized fix that preserves the existing archive-write flow while surfacing the underlying write error; no actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: elibosley, tdewey-rpi

Poem

A rabbit checks each archive write,
And logs the error clear and bright.
Errno hops into the line,
Each operation now keeps time.
The files finish safely tonight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes sequence all archive writes and log libarchive errors and errno, meeting issue #122's diagnostic objective.
Out of Scope Changes check ✅ Passed All changes are limited to archive-write error sequencing and diagnostics in downloadextractthread.cpp.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses the required Conventional Commits format with the fix type and accurately describes the libarchive error-reporting correction.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@junkerderprovinz Junker der Provinz (junkerderprovinz) changed the title Sequence archive write result and error-string read so the real libarchive error surfaces fix(extract): read libarchive's error after the write, not before Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

1 participant