Skip to content

Commit 957fa7e

Browse files
fix(gui): make Cancel build actually stop the rootful build
The build tree runs as root via pkexec; force_exit() from the user-owned GUI is EPERM-ignored, so Cancel printed '[cancelling…]' while the build marched on (Daniel, 2026-07-04, screenshot with two cancels mid-flatpak-install). Cancel now kills the tree ROOT-side via pkexec (auth dialog is correct: it's stopping a root process): a recursive children-first TERM so 'sudo podman build' aborts too, then the top script, whose EXIT trap hands ownership of .cache//output back to the user. The done-handler now reports 'Cancelled' (and survives signal deaths: get_exit_status is only valid after a normal exit, so it branches on get_if_exited). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent db32b98 commit 957fa7e

1 file changed

Lines changed: 36 additions & 4 deletions

File tree

tools/iso-builder/mib/build.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(self, win):
3232
self.win = win
3333
self._proc = None # the running Gio.Subprocess, if any
3434
self._stream = None # its stdout pipe (kept referenced while reading)
35+
self._cancelling = False # set only after the root-side TERM was sent
3536
self._iso_rows = [] # rows currently in the ISOs group
3637
self._refresh_gen = 0 # discards results of superseded refreshes
3738

@@ -247,12 +248,21 @@ def _on_build_done(self, proc, result):
247248
try:
248249
proc.wait_finish(result)
249250
ok = proc.get_successful()
250-
code = proc.get_exit_status()
251+
# A cancelled build dies of SIGTERM: get_exit_status() is only
252+
# valid for a normal exit, so branch on get_if_exited().
253+
code = (proc.get_exit_status() if proc.get_if_exited()
254+
else -(proc.get_term_sig() or 0))
251255
except GLib.Error as e:
252256
ok, code = False, -1
253257
self._append(f"\n[error waiting for process: {e.message}]\n")
254258
self._proc = None
255259
self._stream = None
260+
if self._cancelling:
261+
self._cancelling = False
262+
self._set_running(False, "Cancelled")
263+
self._append("\n✋ Build cancelled — partial artifacts stay in .cache/ "
264+
"(ownership handed back by the script's EXIT trap).\n")
265+
return
256266
if ok:
257267
self._set_running(False, "Done")
258268
iso = self._newest_iso()
@@ -269,9 +279,31 @@ def _on_build_done(self, proc, result):
269279
self._append(f"\n✗ Build failed (exit {code}){hint}\n")
270280

271281
def on_cancel(self, _btn):
272-
if self._proc is not None:
273-
self._append("\n[cancelling…]\n")
274-
self._proc.force_exit()
282+
if self._proc is None:
283+
return
284+
# The build tree runs as ROOT (pkexec): force_exit() from the
285+
# user-owned GUI is EPERM-ignored, which made this button look dead
286+
# (Daniel, 2026-07-04: two "[cancelling…]" with the build marching
287+
# on). Kill the tree root-side instead — children FIRST, so the
288+
# `sudo podman build` underneath aborts too, then the top script,
289+
# whose EXIT trap hands .cache//output ownership back to the user.
290+
# pkexec will show an auth dialog: stopping a root process is a
291+
# privileged action, that prompt is correct.
292+
pid = int(self._proc.get_identifier())
293+
self._append("\n[stopping the rootful build — authenticate in the "
294+
"polkit dialog…]\n")
295+
script = ('k(){ for c in $(pgrep -P "$1"); do k "$c"; done; '
296+
'kill -TERM "$1" 2>/dev/null || true; }; k %d' % pid)
297+
298+
def sent(ok, _out, err):
299+
if ok:
300+
self._cancelling = True
301+
self._append("[terminate sent to the build tree]\n")
302+
else:
303+
last = err.strip().splitlines()[-1] if err.strip() else "auth failed/cancelled"
304+
self._append(f"[cancel aborted: {last}]\n")
305+
306+
core.spawn_collect(["pkexec", "bash", "-c", script], sent)
275307

276308
# -- ISO inventory -------------------------------------------------------------
277309
def _apply_isos(self, gen, isos, rev):

0 commit comments

Comments
 (0)