Skip to content

partclone: a failed status file must not end the restore - #185

Merged
mastacontrola merged 1 commit into
masterfrom
fix/partclone-status-exit
Sep 18, 2026
Merged

mastacontrola merged 1 commit into
masterfrom
fix/partclone-status-exit

Conversation

@mastacontrola

Copy link
Copy Markdown
Member

The defect

fogLogStatusFile() is FOG's own addition to partclone's progress.c
(Buildroot/package/partclone/partclone-0.3.47.patch). It is the only writer of
/tmp/status.fog, which fog.statusreporter forwards to service/progress.php.

It called exit(0) when it could not open that file.

exit(0) is a SUCCESS status. funcs.sh reads $exitcode after the partclone
pipeline and calls handleError only when it is non-zero. So partclone quitting
halfway through a restore is recorded as a good deploy: the task completes, the
client reboots, and the partition is partially written with nothing said on
screen or in the log. /tmp in FOS is a ramdisk, so a full one is enough to
reach it.

Progress reporting is best effort. It now warns once on stderr and returns. The
restore continues, and the cost is a stalled percentage on the web UI.

Two more defects in the same function

Both were in front of me while editing it.

Was Why it is wrong Now
sprintf(total_str, filesize_conv(totalsize, buf, max_len)) the converted string is passed as sprintf's FORMAT argument, so a % in it reads an argument that was never passed filesize_conv(totalsize, total_str, sizeof(total_str)) — it already writes into the buffer it is handed
int max_len=15; sizing two VLAs, plus a shared buf a variable for a constant, and both conversions aliased one scratch buffer char total_str[16]; / char current_str[16];, no shared buffer

The gate

tests/checks/partclone-status-patch.sh checks the patch as text:

  • no exit( anywhere in fogLogStatusFile()
  • a failed fopen returns (so removing the exit without replacing it cannot pass)
  • no sprintf whose format argument is a call's return value
  • every @@ header's counts match the lines under it

The last one is not incidental. The added code is + lines inside a unified
diff, so editing it by hand moves counts that nothing else recomputes. patch
will often apply a wrong count anyway and silently drop or duplicate a line; the
failure then lands in the middle of a Buildroot build, far from the edit.

Verification

  • The patch applies with no fuzz and no offset against pristine partclone-0.3.47
    (patch -p1 --dry-run).
  • make partclone-dirclean && make partclone in fssourcex64 re-extracts,
    re-patches, re-configures and rebuilds it to a linked partclone.restore. The
    function in the extracted tree is the new one.
  • Each of the four assertions was driven red by restoring the defect it covers:
    exit(0) back (2 red), the return deleted (2 red), the computed sprintf
    format back (1 red), a stale hunk count (1 red).
  • tests/run-all.sh: 24 passed, 0 failed.

Verification gap: this has not been run against a real deploy with a full
/tmp. The path that was changed is the failure path, which is exactly the one a
normal lab run never takes. What is proven is that the patched source compiles
and that the success path is byte-identical in behavior to before.

FOS is not branched, so this init runs against 1.5 and 1.6 servers alike. The
wire format written to /tmp/status.fog is unchanged.

🤖 Generated with Claude Code

fogLogStatusFile() is FOG's own addition to partclone's progress.c. It is
the only writer of /tmp/status.fog, which fog.statusreporter forwards to
service/progress.php. It called exit(0) when it could not open that file.

exit(0) is a SUCCESS status. funcs.sh reads $exitcode after the partclone
pipeline and only calls handleError when it is non-zero, so partclone
quitting halfway through a restore is recorded as a good deploy: the task
completes, the client reboots, and the partition is partially written with
nothing said. /tmp in FOS is a ramdisk, so a full one is enough to reach
it. Progress reporting is best effort; it now warns once on stderr and
returns, and the restore continues with a stalled percentage on the web UI.

Two defects in the same function, fixed while editing it:

  sprintf(total_str, filesize_conv(totalsize, buf, max_len)) passes the
  converted string as sprintf's FORMAT argument, so a % in it would read
  an argument that was never passed. filesize_conv() already writes into
  the buffer it is handed, so it now writes into total_str directly and
  the shared scratch buffer is gone -- which also removes the aliasing
  between the two calls.

  max_len was a variable sizing two VLAs for a constant 15 bytes. The
  buffers are char[16] and sizeof() is passed to filesize_conv().

tests/checks/partclone-status-patch.sh gates the patch as text: no exit()
in the function, a failed open returns, no sprintf format that is a call's
return value, and every hunk header's counts match the lines under it. The
last one is not incidental -- the added code is `+` lines inside a unified
diff, so editing it by hand moves counts that nothing else recomputes, and
`patch` will often apply a wrong count and silently drop or duplicate a
line in the middle of a Buildroot build.

Verified: the patch applies with no fuzz or offset against pristine
partclone-0.3.47, and `make partclone` re-extracts, re-patches, and
rebuilds it to a linked partclone.restore. Each of the four assertions was
driven red by restoring the defect it covers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mastacontrola
mastacontrola merged commit a5c10b7 into master Sep 18, 2026
1 check passed
@mastacontrola
mastacontrola deleted the fix/partclone-status-exit branch September 18, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant