Skip to content

Commit f8311b7

Browse files
committed
Handle subprocess disallowing preexec during shutdown
In Python 3.12, you cannot pass a preexec_fn to subprocess.Popen during interpreter shutdown: python/cpython#104826 This avoids the problem by giving startProgram an arg to not use a preexec_fn, and passing that all the way from anaconda's exitHandler through execWithRedirect and _run_program. Signed-off-by: Adam Williamson <awilliam@redhat.com>
1 parent d61b8cc commit f8311b7

2 files changed

Lines changed: 46 additions & 38 deletions

File tree

‎anaconda.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ def exitHandler(rebootData):
8989
util.dracut_eject(device_path)
9090

9191
if flags.kexec:
92-
util.execWithRedirect("systemctl", ["--no-wall", "kexec"])
92+
util.execWithRedirect("systemctl", ["--no-wall", "kexec"], do_preexec=False)
9393
while True:
9494
time.sleep(10000)
9595
elif rebootData.action == KS_SHUTDOWN:
96-
util.execWithRedirect("systemctl", ["--no-wall", "poweroff"])
96+
util.execWithRedirect("systemctl", ["--no-wall", "poweroff"], do_preexec=False)
9797
elif rebootData.action == KS_WAIT:
98-
util.execWithRedirect("systemctl", ["--no-wall", "halt"])
98+
util.execWithRedirect("systemctl", ["--no-wall", "halt"], do_preexec=False)
9999
else: # reboot action is KS_REBOOT or None
100-
util.execWithRedirect("systemctl", ["--no-wall", "reboot"])
100+
util.execWithRedirect("systemctl", ["--no-wall", "reboot"], do_preexec=False)
101101

102102

103103
def parse_arguments(argv=None, boot_cmdline=None):

‎pyanaconda/core/util.py‎

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ def augmentEnv():
7676

7777

7878
def startProgram(argv, root='/', stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
79-
env_prune=None, env_add=None, reset_handlers=True, reset_lang=True, **kwargs):
79+
env_prune=None, env_add=None, reset_handlers=True, reset_lang=True,
80+
do_preexec=False, **kwargs):
8081
""" Start an external program and return the Popen object.
8182
8283
The root and reset_handlers arguments are handled by passing a
@@ -93,6 +94,7 @@ def startProgram(argv, root='/', stdin=None, stdout=subprocess.PIPE, stderr=subp
9394
:param env_add: environment variables to add before execution
9495
:param reset_handlers: whether to reset to SIG_DFL any signal handlers set to SIG_IGN
9596
:param reset_lang: whether to set the locale of the child process to C
97+
:param do_preexec: whether to use the preexec function
9698
:param kwargs: Additional parameters to pass to subprocess.Popen
9799
:return: A Popen object for the running command.
98100
"""
@@ -105,32 +107,11 @@ def startProgram(argv, root='/', stdin=None, stdout=subprocess.PIPE, stderr=subp
105107
if target_root == conf.target.physical_root:
106108
target_root = conf.target.system_root
107109

108-
# Check for and save a preexec_fn argument
109-
preexec_fn = kwargs.pop("preexec_fn", None)
110-
111110
# Map reset_handlers to the restore_signals Popen argument.
112111
# restore_signals handles SIGPIPE, and preexec below handles any additional
113112
# signals ignored by anaconda.
114113
restore_signals = reset_handlers
115114

116-
def preexec():
117-
# If a target root was specificed, chroot into it
118-
if target_root and target_root != '/':
119-
os.chroot(target_root)
120-
os.chdir("/")
121-
122-
# Signal handlers set to SIG_IGN persist across exec. Reset
123-
# these to SIG_DFL if requested. In particular this will include the
124-
# SIGPIPE handler set by python.
125-
if reset_handlers:
126-
for signum in range(1, signal.NSIG):
127-
if signal.getsignal(signum) == signal.SIG_IGN:
128-
signal.signal(signum, signal.SIG_DFL)
129-
130-
# If the user specified an additional preexec_fn argument, run it
131-
if preexec_fn is not None:
132-
preexec_fn()
133-
134115
with program_log_lock:
135116
if target_root != '/':
136117
program_log.info("Running in chroot '%s'... %s", target_root, " ".join(argv))
@@ -148,13 +129,38 @@ def preexec():
148129
env.update(env_add)
149130

150131
# pylint: disable=subprocess-popen-preexec-fn
151-
return subprocess.Popen(argv,
152-
stdin=stdin,
153-
stdout=stdout,
154-
stderr=stderr,
155-
close_fds=True,
156-
restore_signals=restore_signals,
157-
preexec_fn=preexec, cwd=root, env=env, **kwargs)
132+
partsubp = functools.partial(subprocess.Popen,
133+
argv,
134+
stdin=stdin,
135+
stdout=stdout,
136+
stderr=stderr,
137+
close_fds=True,
138+
restore_signals=restore_signals,
139+
cwd=root, env=env, **kwargs)
140+
if do_preexec:
141+
# Check for and save a preexec_fn argument
142+
preexec_fn = kwargs.pop("preexec_fn", None)
143+
144+
def preexec():
145+
# If a target root was specificed, chroot into it
146+
if target_root and target_root != '/':
147+
os.chroot(target_root)
148+
os.chdir("/")
149+
150+
# Signal handlers set to SIG_IGN persist across exec. Reset
151+
# these to SIG_DFL if requested. In particular this will include the
152+
# SIGPIPE handler set by python.
153+
if reset_handlers:
154+
for signum in range(1, signal.NSIG):
155+
if signal.getsignal(signum) == signal.SIG_IGN:
156+
signal.signal(signum, signal.SIG_DFL)
157+
158+
# If the user specified an additional preexec_fn argument, run it
159+
if preexec_fn is not None:
160+
preexec_fn()
161+
162+
return partsubp(preexec_fn=preexec)
163+
return partsubp()
158164

159165

160166
class X11Status:
@@ -253,7 +259,7 @@ def sigusr1_preexec():
253259

254260

255261
def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_output=True,
256-
binary_output=False, filter_stderr=False):
262+
binary_output=False, filter_stderr=False, do_preexec=False):
257263
""" Run an external program, log the output and return it to the caller
258264
259265
NOTE/WARNING: UnicodeDecodeError will be raised if the output of the of the
@@ -267,6 +273,7 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_ou
267273
:param log_output: whether to log the output of command
268274
:param binary_output: whether to treat the output of command as binary data
269275
:param filter_stderr: whether to exclude the contents of stderr from the returned output
276+
:param do_preexec: whether to use a preexec_fn for subprocess.Popen
270277
:return: The return code of the command and the output
271278
"""
272279
try:
@@ -276,7 +283,7 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_ou
276283
stderr = subprocess.STDOUT
277284

278285
proc = startProgram(argv, root=root, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr,
279-
env_prune=env_prune)
286+
env_prune=env_prune, do_preexec=do_preexec)
280287

281288
(output_string, err_string) = proc.communicate()
282289
if not binary_output:
@@ -322,8 +329,8 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_ou
322329
return (proc.returncode, output_string)
323330

324331

325-
def execWithRedirect(command, argv, stdin=None, stdout=None,
326-
root='/', env_prune=None, log_output=True, binary_output=False):
332+
def execWithRedirect(command, argv, stdin=None, stdout=None, root='/', env_prune=None,
333+
log_output=True, binary_output=False, do_preexec=False):
327334
""" Run an external program and redirect the output to a file.
328335
329336
:param command: The command to run
@@ -334,11 +341,12 @@ def execWithRedirect(command, argv, stdin=None, stdout=None,
334341
:param env_prune: environment variable to remove before execution
335342
:param log_output: whether to log the output of command
336343
:param binary_output: whether to treat the output of command as binary data
344+
:param do_preexec: whether to use a preexec_fn for subprocess.Popen
337345
:return: The return code of the command
338346
"""
339347
argv = [command] + argv
340348
return _run_program(argv, stdin=stdin, stdout=stdout, root=root, env_prune=env_prune,
341-
log_output=log_output, binary_output=binary_output)[0]
349+
log_output=log_output, binary_output=binary_output, do_preexec=do_preexec)[0]
342350

343351

344352
def execWithCapture(command, argv, stdin=None, root='/', log_output=True, filter_stderr=False):

0 commit comments

Comments
 (0)