From 89a0c04d19cc87c561fcb16b3ad9c1ebd4289f0b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 30 Aug 2022 19:03:32 +0530 Subject: [PATCH] Fix exec in prewarm forked process sometimes inheriting env vars from grandparent process --- kitty/child.c | 18 ++++++++++++++++++ kitty/fast_data_types.pyi | 1 + kitty/prewarm.py | 13 ++++++++++--- kitty_tests/prewarm.py | 9 ++++++--- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/kitty/child.c b/kitty/child.c index bbed311c1..228bbcc51 100644 --- a/kitty/child.c +++ b/kitty/child.c @@ -181,8 +181,26 @@ spawn(PyObject *self UNUSED, PyObject *args) { return PyLong_FromLong(pid); } +#ifdef __APPLE__ +#include +#else +extern char **environ; +#endif + +static PyObject* +clearenv_py(PyObject *self UNUSED, PyObject *args UNUSED) { +#ifdef __APPLE__ + char **e = *_NSGetEnviron(); + if (e) *e = NULL; +#else + if (environ) *environ = NULL; +#endif + Py_RETURN_NONE; +} + static PyMethodDef module_methods[] = { METHODB(spawn, METH_VARARGS), + {"clearenv", clearenv_py, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 23169de1c..d5098391e 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1485,3 +1485,4 @@ class SingleKey: def set_use_os_log(yes: bool) -> None: ... def get_docs_ref_map() -> bytes: ... +def clearenv() -> None: ... diff --git a/kitty/prewarm.py b/kitty/prewarm.py index 8ce3543bd..3e98de5ed 100644 --- a/kitty/prewarm.py +++ b/kitty/prewarm.py @@ -24,9 +24,9 @@ from typing import ( from kitty.constants import kitty_exe, running_in_kitty from kitty.entry_points import main as main_entry_point from kitty.fast_data_types import ( - CLD_EXITED, CLD_KILLED, CLD_STOPPED, get_options, install_signal_handlers, - read_signals, remove_signal_handlers, safe_pipe, set_options, - set_use_os_log + CLD_EXITED, CLD_KILLED, CLD_STOPPED, clearenv, get_options, + install_signal_handlers, read_signals, remove_signal_handlers, safe_pipe, + set_options, set_use_os_log ) from kitty.options.types import Options from kitty.shm import SharedMemory @@ -293,6 +293,13 @@ def child_main(cmd: Dict[str, Any], ready_fd: int = -1, prewarm_type: str = 'dir env = cmd.get('env') if env is not None: os.environ.clear() + # os.environ.clear() does not delete all existing env vars from the + # libc environ pointer in some circumstances, I havent figured out + # which exactly. Presumably there is something that alters the + # libc environ pointer?? The environ pointer is used by os.exec and + # therefore by subprocess and friends, so we need to ensure it is + # cleared. + clearenv() os.environ.update(env) argv = cmd.get('argv') if argv: diff --git a/kitty_tests/prewarm.py b/kitty_tests/prewarm.py index bed9362bf..4cd4c7309 100644 --- a/kitty_tests/prewarm.py +++ b/kitty_tests/prewarm.py @@ -28,13 +28,15 @@ class Prewarm(BaseTest): cwd = tempfile.gettempdir() env = {'TEST_ENV_PASS': 'xyz'} - cols = 117 + cols = 317 stdin_data = 'from_stdin' pty = self.create_pty(cols=cols) ttyname = os.ttyname(pty.slave_fd) opts = get_options() opts.config_overrides = 'font_family prewarm', + os.environ['SHOULD_NOT_BE_PRESENT'] = '1' p = fork_prewarm_process(opts, use_exec=True) + del os.environ['SHOULD_NOT_BE_PRESENT'] if p is None: return p.take_from_worker_fd(create_file=True) @@ -44,7 +46,7 @@ import os, json; from kitty.utils import *; from kitty.fast_data_types import ge 'ttyname': os.ttyname(sys.stdout.fileno()), 'cols': read_screen_size().cols, 'cwd': os.getcwd(), - 'env': os.environ.get('TEST_ENV_PASS'), + 'env': os.environ.copy(), 'pid': os.getpid(), 'font_family': get_options().font_family, 'stdin': sys.stdin.read(), @@ -59,7 +61,8 @@ import os, json; from kitty.utils import *; from kitty.fast_data_types import ge self.assertTrue(data['cterm']) self.ae(data['ttyname'], ttyname) self.ae(os.path.realpath(data['cwd']), os.path.realpath(cwd)) - self.ae(data['env'], env['TEST_ENV_PASS']) + self.ae(data['env']['TEST_ENV_PASS'], env['TEST_ENV_PASS']) + self.assertNotIn('SHOULD_NOT_BE_PRESENT', data['env']) self.ae(data['font_family'], 'prewarm') self.ae(int(p.from_worker.readline()), data['pid'])