fix(bootstrap): harden archive names, the cancel window and quit wait
The bootstrap preflight's protected names could be bypassed by names
that resolve onto them, and its cancel and quit paths had two timing
gaps.
- Entry names are judged as the file system resolves them: '\' is a
separator, "." components are dropped and each component loses its
trailing dots and spaces, as Win32 does ("./wallet.dat" and
"DRAGONX.conf." name protected files). The whole archive is refused
for an absolute or drive-rooted name, a ".." component, any ':'
(drive letter or NTFS alternate data stream, "DRAGONX.conf::$DATA"),
an 8.3 short name ("DRAGON~1.CON"), a component of only dots or
spaces (other than "."), and any byte >= 0x80 or ASCII control
character: macOS folds or ignores some Unicode (U+017F, U+212A,
U+200C) onto a protected name, and the published bootstrap archive
uses plain ASCII names. A refused name is echoed
with \xNN escapes. A member is written at the path it was checked
under, and the protected-name check runs on the resolved name.
- The user's Cancel (Settings dialog and first-run wizard) goes through
requestCancel(). It is refused once the old chain data starts being
replaced, and the start of that window re-checks a cancel under the
same lock, so a click lands before the old chain is touched or not at
all.
- Quit cancels the wizard's and the Settings bootstrap when the
shutdown starts; the HEAD and checksum requests now end at once on
cancel, and a cancelled HEAD keeps the partial download. A run
cancelled before the window leaves the old chain as it was. A run
inside it stops extracting and removes its partial chain while the
shutdown screen shows "Cleaning up...", so it ends with no chain data
(wallet.dat untouched; Retry reuses the zip, or the node resyncs).
That wait is bounded: at most 2 minutes from the start of the
shutdown, then 3 s in shutdown(), after which a worker still running
is left to the process exit. Only a cleanup that overruns that cap
can leave a partial chain, which dragonxd then answers with its
reindex offer.
Tests: refusal of the Windows-resolved, non-ASCII and control-character
names with the escaped echo; the protected-name list incl. the resolved
forms and a planted -wallet file, conf and database/ that stay
byte-identical; the user-cancel window; the bounded quit wait and
safeToAbandon(); a cancel ends a HEAD request that never answers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "../../app.h"
|
||||
@@ -50,14 +51,22 @@ public:
|
||||
// Close the dialog (used by the UI sweep teardown).
|
||||
static void hide() { s_open = false; s_state = State::Confirm; }
|
||||
|
||||
// Quit: cancel a running Settings bootstrap and wait for its worker. main() ends in _Exit, so without this
|
||||
// a quit during extraction would kill the worker mid-file and leave a half-written chain; a cancelled
|
||||
// extraction removes the partial chain before the worker exits.
|
||||
static void abortForShutdown() {
|
||||
if (s_bootstrap) {
|
||||
s_bootstrap->cancel();
|
||||
s_bootstrap.reset();
|
||||
}
|
||||
// Quit, from beginShutdown(): cancel a running Settings bootstrap without waiting for it.
|
||||
static void cancelForShutdown() {
|
||||
if (s_bootstrap) s_bootstrap->cancel();
|
||||
}
|
||||
|
||||
// Quit: true while a cancelled Settings bootstrap is still replacing the chain data. Its abort removes the
|
||||
// partial chain; the shutdown screen stays up until that is done, so main()'s exit watchdog never cuts it.
|
||||
static bool quitPending() { return s_bootstrap && !s_bootstrap->safeToAbandon(); }
|
||||
|
||||
// Quit, from App::shutdown(): cancel a running Settings bootstrap and wait for its worker until `deadline`
|
||||
// (main() ends in _Exit, and its 15 s watchdog is already running). A worker still running then is left to
|
||||
// the process exit and never destroyed (~Bootstrap would join).
|
||||
static void abortForShutdown(std::chrono::steady_clock::time_point deadline) {
|
||||
if (!s_bootstrap) return;
|
||||
if (s_bootstrap->cancelAndWait(deadline)) s_bootstrap.reset();
|
||||
else (void)s_bootstrap.release();
|
||||
}
|
||||
|
||||
static void render() {
|
||||
@@ -226,8 +235,8 @@ private:
|
||||
float btnW = 100.0f * dp;
|
||||
ImGui::BeginDisabled(s_cancelling || !prog.cancellable);
|
||||
if (TactileButton(TR("cancel"), ImVec2(btnW, 0))) {
|
||||
s_cancelling = true;
|
||||
s_bootstrap->cancel();
|
||||
// Refused (no-op) if the destructive window started after this frame read prog.cancellable.
|
||||
s_cancelling = s_bootstrap->requestCancel();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user