113 lines
3.1 KiB
Python
113 lines
3.1 KiB
Python
import os
|
|
import re
|
|
|
|
from .._core import SHELL_NAMES, ShellDetectionFailure
|
|
from . import proc, ps
|
|
|
|
# Based on QEMU docs: https://www.qemu.org/docs/master/user/main.html
|
|
QEMU_BIN_REGEX = re.compile(
|
|
r"""qemu-
|
|
(alpha
|
|
|armeb
|
|
|arm
|
|
|m68k
|
|
|cris
|
|
|i386
|
|
|x86_64
|
|
|microblaze
|
|
|mips
|
|
|mipsel
|
|
|mips64
|
|
|mips64el
|
|
|mipsn32
|
|
|mipsn32el
|
|
|nios2
|
|
|ppc64
|
|
|ppc
|
|
|sh4eb
|
|
|sh4
|
|
|sparc
|
|
|sparc32plus
|
|
|sparc64
|
|
)""",
|
|
re.VERBOSE,
|
|
)
|
|
|
|
|
|
def _iter_process_parents(pid, max_depth=10):
|
|
"""Select a way to obtain process information from the system.
|
|
|
|
* `/proc` is used if supported.
|
|
* The system `ps` utility is used as a fallback option.
|
|
"""
|
|
for impl in (proc, ps):
|
|
try:
|
|
iterator = impl.iter_process_parents(pid, max_depth)
|
|
except EnvironmentError:
|
|
continue
|
|
return iterator
|
|
raise ShellDetectionFailure("compatible proc fs or ps utility is required")
|
|
|
|
|
|
def _get_login_shell(proc_cmd):
|
|
"""Form shell information from SHELL environ if possible."""
|
|
login_shell = os.environ.get("SHELL", "")
|
|
if login_shell:
|
|
proc_cmd = login_shell
|
|
else:
|
|
proc_cmd = proc_cmd[1:]
|
|
return (os.path.basename(proc_cmd).lower(), proc_cmd)
|
|
|
|
|
|
_INTERPRETER_SHELL_NAMES = [
|
|
(re.compile(r"^python(\d+(\.\d+)?)?$"), {"xonsh"}),
|
|
]
|
|
|
|
|
|
def _get_interpreter_shell(proc_name, proc_args):
|
|
"""Get shell invoked via an interpreter.
|
|
|
|
Some shells are implemented on, and invoked with an interpreter, e.g. xonsh
|
|
is commonly executed with an executable Python script. This detects what
|
|
script the interpreter is actually running, and check whether that looks
|
|
like a shell.
|
|
|
|
See sarugaku/shellingham#26 for rational.
|
|
"""
|
|
for pattern, shell_names in _INTERPRETER_SHELL_NAMES:
|
|
if not pattern.match(proc_name):
|
|
continue
|
|
for arg in proc_args:
|
|
name = os.path.basename(arg).lower()
|
|
if os.path.isfile(arg) and name in shell_names:
|
|
return (name, arg)
|
|
return None
|
|
|
|
|
|
def _get_shell(cmd, *args):
|
|
if cmd.startswith("-"): # Login shell! Let's use this.
|
|
return _get_login_shell(cmd)
|
|
name = os.path.basename(cmd).lower()
|
|
if name == "rosetta" or QEMU_BIN_REGEX.fullmatch(name):
|
|
# If the current process is Rosetta or QEMU, this likely is a
|
|
# containerized process. Parse out the actual command instead.
|
|
cmd = args[0]
|
|
args = args[1:]
|
|
name = os.path.basename(cmd).lower()
|
|
if name in SHELL_NAMES: # Command looks like a shell.
|
|
return (name, cmd)
|
|
shell = _get_interpreter_shell(name, args)
|
|
if shell:
|
|
return shell
|
|
return None
|
|
|
|
|
|
def get_shell(pid=None, max_depth=10):
|
|
"""Get the shell that the supplied pid or os.getpid() is running in."""
|
|
pid = str(pid or os.getpid())
|
|
for proc_args, _, _ in _iter_process_parents(pid, max_depth):
|
|
shell = _get_shell(*proc_args)
|
|
if shell:
|
|
return shell
|
|
return None
|