__init__.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import os
  2. import re
  3. from .._core import SHELL_NAMES, ShellDetectionFailure
  4. from . import proc, ps
  5. # Based on QEMU docs: https://www.qemu.org/docs/master/user/main.html
  6. QEMU_BIN_REGEX = re.compile(
  7. r"""qemu-
  8. (alpha
  9. |armeb
  10. |arm
  11. |m68k
  12. |cris
  13. |i386
  14. |x86_64
  15. |microblaze
  16. |mips
  17. |mipsel
  18. |mips64
  19. |mips64el
  20. |mipsn32
  21. |mipsn32el
  22. |nios2
  23. |ppc64
  24. |ppc
  25. |sh4eb
  26. |sh4
  27. |sparc
  28. |sparc32plus
  29. |sparc64
  30. )""",
  31. re.VERBOSE,
  32. )
  33. def _iter_process_parents(pid, max_depth=10):
  34. """Select a way to obtain process information from the system.
  35. * `/proc` is used if supported.
  36. * The system `ps` utility is used as a fallback option.
  37. """
  38. for impl in (proc, ps):
  39. try:
  40. iterator = impl.iter_process_parents(pid, max_depth)
  41. except EnvironmentError:
  42. continue
  43. return iterator
  44. raise ShellDetectionFailure("compatible proc fs or ps utility is required")
  45. def _get_login_shell(proc_cmd):
  46. """Form shell information from SHELL environ if possible."""
  47. login_shell = os.environ.get("SHELL", "")
  48. if login_shell:
  49. proc_cmd = login_shell
  50. else:
  51. proc_cmd = proc_cmd[1:]
  52. return (os.path.basename(proc_cmd).lower(), proc_cmd)
  53. _INTERPRETER_SHELL_NAMES = [
  54. (re.compile(r"^python(\d+(\.\d+)?)?$"), {"xonsh"}),
  55. ]
  56. def _get_interpreter_shell(proc_name, proc_args):
  57. """Get shell invoked via an interpreter.
  58. Some shells are implemented on, and invoked with an interpreter, e.g. xonsh
  59. is commonly executed with an executable Python script. This detects what
  60. script the interpreter is actually running, and check whether that looks
  61. like a shell.
  62. See sarugaku/shellingham#26 for rational.
  63. """
  64. for pattern, shell_names in _INTERPRETER_SHELL_NAMES:
  65. if not pattern.match(proc_name):
  66. continue
  67. for arg in proc_args:
  68. name = os.path.basename(arg).lower()
  69. if os.path.isfile(arg) and name in shell_names:
  70. return (name, arg)
  71. return None
  72. def _get_shell(cmd, *args):
  73. if cmd.startswith("-"): # Login shell! Let's use this.
  74. return _get_login_shell(cmd)
  75. name = os.path.basename(cmd).lower()
  76. if name == "rosetta" or QEMU_BIN_REGEX.fullmatch(name):
  77. # If the current process is Rosetta or QEMU, this likely is a
  78. # containerized process. Parse out the actual command instead.
  79. cmd = args[0]
  80. args = args[1:]
  81. name = os.path.basename(cmd).lower()
  82. if name in SHELL_NAMES: # Command looks like a shell.
  83. return (name, cmd)
  84. shell = _get_interpreter_shell(name, args)
  85. if shell:
  86. return shell
  87. return None
  88. def get_shell(pid=None, max_depth=10):
  89. """Get the shell that the supplied pid or os.getpid() is running in."""
  90. pid = str(pid or os.getpid())
  91. for proc_args, _, _ in _iter_process_parents(pid, max_depth):
  92. shell = _get_shell(*proc_args)
  93. if shell:
  94. return shell
  95. return None