|
16 | 16 | from ._linux._capabilities import try_use_cap_kill |
17 | 17 | from ._linux._sudo import find_sudo_child_process_group_id |
18 | 18 | from ._logging import LoggerAdapter, LogContent, LogExtraInfo |
19 | | -from ._os_checker import is_linux, is_posix, is_windows |
| 19 | +from ._os_checker import is_linux, is_macos, is_posix, is_windows |
20 | 20 | from ._session_user import PosixSessionUser, WindowsSessionUser, SessionUser |
21 | 21 | from ._action_filter import redact_openjd_redacted_env_requests |
22 | 22 |
|
|
28 | 28 |
|
29 | 29 | __all__ = ("LoggingSubprocess",) |
30 | 30 |
|
| 31 | +# macOS has no `setsid(1)` binary (it is a Linux/util-linux tool), yet the new-session |
| 32 | +# behavior it provides is still required: `sudo -u <user> -i <cmd>` places the workload in |
| 33 | +# sudo's own (root-owned) process group, which the jobRunAsUser cannot signal and which |
| 34 | +# openjd must not signal (it would hit the root sudo process). We reproduce `setsid` with a |
| 35 | +# tiny pure-Python shim, run as the workload, that makes the workload a new session/process- |
| 36 | +# group leader and then exec's the real command. |
| 37 | +# |
| 38 | +# Details: |
| 39 | +# * `os.getpgrp() == os.getpid() or os.setsid()` calls setsid() only when the process is |
| 40 | +# NOT already a group leader; os.setsid() raises EPERM if the caller already leads a |
| 41 | +# group, so the short-circuit avoids that. Either way the workload ends up in a process |
| 42 | +# group distinct from sudo's, which find_sudo_child_process_group_id() then discovers. |
| 43 | +# * Single line (no newlines) so it passes cleanly through `sudo -i` argv without any |
| 44 | +# shell-quoting fragility. |
| 45 | +# * /usr/bin/python3 (the OS-provided interpreter) is used rather than sys.executable so |
| 46 | +# the jobRunAsUser can execute it without traverse/read permission on the agent's venv. |
| 47 | +# On macOS /usr/bin/python3 is the Command Line Tools shim; the host must have the |
| 48 | +# Command Line Tools (or Xcode) installed for it to resolve to a working interpreter. |
| 49 | +# * `-I` (isolated mode) drops the current working directory from sys.path and ignores |
| 50 | +# PYTHON* environment variables, so a file such as os.py in the session working directory |
| 51 | +# cannot be imported ahead of the standard library before os.execvp() runs. |
| 52 | +# |
| 53 | +# Signal-target discovery (find_sudo_child_process_group_id) locates the workload by walking |
| 54 | +# sudo's single child and comparing process groups. This relies on `sudo -i` exec'ing the |
| 55 | +# command into a single child rather than leaving extra long-lived processes in between; the |
| 56 | +# same assumption already holds for the Linux `setsid -w` path. |
| 57 | +_MACOS_SETSID_SHIM = ( |
| 58 | + "import os,sys;os.getpgrp()==os.getpid() or os.setsid();os.execvp(sys.argv[1],sys.argv[1:])" |
| 59 | +) |
| 60 | +_MACOS_SETSID_INTERPRETER_ARGS = ["/usr/bin/python3", "-I"] |
| 61 | + |
31 | 62 | # ======================================================================== |
32 | 63 | # ======================================================================== |
33 | 64 | # DEVELOPER NOTE: |
@@ -257,7 +288,23 @@ def _start_subprocess(self) -> Optional[Popen]: |
257 | 288 | # same process group as the `sudo` command. If that happens, then |
258 | 289 | # we're stuck: 1/ Our user cannot kill processes by the self._user; and |
259 | 290 | # 2/ The self._user cannot kill the root-owned sudo process group. |
260 | | - command.extend(["sudo", "-u", user.user, "-i", "setsid", "-w"]) |
| 291 | + if is_macos(): |
| 292 | + # macOS has no setsid(1); use a pure-Python setsid shim (see |
| 293 | + # _MACOS_SETSID_SHIM) run as the workload to get the same |
| 294 | + # new-session behavior that `setsid -w` provides on Linux. |
| 295 | + command.extend( |
| 296 | + [ |
| 297 | + "sudo", |
| 298 | + "-u", |
| 299 | + user.user, |
| 300 | + "-i", |
| 301 | + *_MACOS_SETSID_INTERPRETER_ARGS, |
| 302 | + "-c", |
| 303 | + _MACOS_SETSID_SHIM, |
| 304 | + ] |
| 305 | + ) |
| 306 | + else: |
| 307 | + command.extend(["sudo", "-u", user.user, "-i", "setsid", "-w"]) |
261 | 308 | elif is_windows(): |
262 | 309 | user = cast(WindowsSessionUser, self._user) # type: ignore |
263 | 310 |
|
|
0 commit comments