process.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293
  1. # -*- test-case-name: twisted.test.test_process -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. UNIX Process management.
  6. Do NOT use this module directly - use reactor.spawnProcess() instead.
  7. Maintainer: Itamar Shtull-Trauring
  8. """
  9. from __future__ import annotations
  10. import errno
  11. import gc
  12. import io
  13. import os
  14. import signal
  15. import stat
  16. import sys
  17. import traceback
  18. from collections import defaultdict
  19. from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
  20. _PS_CLOSE: int
  21. _PS_DUP2: int
  22. if not TYPE_CHECKING:
  23. try:
  24. from os import POSIX_SPAWN_CLOSE as _PS_CLOSE, POSIX_SPAWN_DUP2 as _PS_DUP2
  25. except ImportError:
  26. pass
  27. from zope.interface import implementer
  28. from twisted.internet import abstract, error, fdesc
  29. from twisted.internet._baseprocess import BaseProcess
  30. from twisted.internet.interfaces import IProcessTransport
  31. from twisted.internet.main import CONNECTION_DONE, CONNECTION_LOST
  32. from twisted.python import failure, log
  33. from twisted.python.runtime import platform
  34. from twisted.python.util import switchUID
  35. if platform.isWindows():
  36. raise ImportError(
  37. "twisted.internet.process does not work on Windows. "
  38. "Use the reactor.spawnProcess() API instead."
  39. )
  40. try:
  41. import pty as _pty
  42. except ImportError:
  43. pty = None
  44. else:
  45. pty = _pty
  46. try:
  47. import fcntl as _fcntl
  48. import termios
  49. except ImportError:
  50. fcntl = None
  51. else:
  52. fcntl = _fcntl
  53. # Some people were importing this, which is incorrect, just keeping it
  54. # here for backwards compatibility:
  55. ProcessExitedAlready = error.ProcessExitedAlready
  56. reapProcessHandlers: Dict[int, _BaseProcess] = {}
  57. def reapAllProcesses() -> None:
  58. """
  59. Reap all registered processes.
  60. """
  61. # Coerce this to a list, as reaping the process changes the dictionary and
  62. # causes a "size changed during iteration" exception
  63. for process in list(reapProcessHandlers.values()):
  64. process.reapProcess()
  65. def registerReapProcessHandler(pid, process):
  66. """
  67. Register a process handler for the given pid, in case L{reapAllProcesses}
  68. is called.
  69. @param pid: the pid of the process.
  70. @param process: a process handler.
  71. """
  72. if pid in reapProcessHandlers:
  73. raise RuntimeError("Try to register an already registered process.")
  74. try:
  75. auxPID, status = os.waitpid(pid, os.WNOHANG)
  76. except BaseException:
  77. log.msg(f"Failed to reap {pid}:")
  78. log.err()
  79. if pid is None:
  80. return
  81. auxPID = None
  82. if auxPID:
  83. process.processEnded(status)
  84. else:
  85. # if auxPID is 0, there are children but none have exited
  86. reapProcessHandlers[pid] = process
  87. def unregisterReapProcessHandler(pid, process):
  88. """
  89. Unregister a process handler previously registered with
  90. L{registerReapProcessHandler}.
  91. """
  92. if not (pid in reapProcessHandlers and reapProcessHandlers[pid] == process):
  93. raise RuntimeError("Try to unregister a process not registered.")
  94. del reapProcessHandlers[pid]
  95. class ProcessWriter(abstract.FileDescriptor):
  96. """
  97. (Internal) Helper class to write into a Process's input pipe.
  98. I am a helper which describes a selectable asynchronous writer to a
  99. process's input pipe, including stdin.
  100. @ivar enableReadHack: A flag which determines how readability on this
  101. write descriptor will be handled. If C{True}, then readability may
  102. indicate the reader for this write descriptor has been closed (ie,
  103. the connection has been lost). If C{False}, then readability events
  104. are ignored.
  105. """
  106. connected = 1
  107. ic = 0
  108. enableReadHack = False
  109. def __init__(self, reactor, proc, name, fileno, forceReadHack=False):
  110. """
  111. Initialize, specifying a Process instance to connect to.
  112. """
  113. abstract.FileDescriptor.__init__(self, reactor)
  114. fdesc.setNonBlocking(fileno)
  115. self.proc = proc
  116. self.name = name
  117. self.fd = fileno
  118. if not stat.S_ISFIFO(os.fstat(self.fileno()).st_mode):
  119. # If the fd is not a pipe, then the read hack is never
  120. # applicable. This case arises when ProcessWriter is used by
  121. # StandardIO and stdout is redirected to a normal file.
  122. self.enableReadHack = False
  123. elif forceReadHack:
  124. self.enableReadHack = True
  125. else:
  126. # Detect if this fd is actually a write-only fd. If it's
  127. # valid to read, don't try to detect closing via read.
  128. # This really only means that we cannot detect a TTY's write
  129. # pipe being closed.
  130. try:
  131. os.read(self.fileno(), 0)
  132. except OSError:
  133. # It's a write-only pipe end, enable hack
  134. self.enableReadHack = True
  135. if self.enableReadHack:
  136. self.startReading()
  137. def fileno(self):
  138. """
  139. Return the fileno() of my process's stdin.
  140. """
  141. return self.fd
  142. def writeSomeData(self, data):
  143. """
  144. Write some data to the open process.
  145. """
  146. rv = fdesc.writeToFD(self.fd, data)
  147. if rv == len(data) and self.enableReadHack:
  148. # If the send buffer is now empty and it is necessary to monitor
  149. # this descriptor for readability to detect close, try detecting
  150. # readability now.
  151. self.startReading()
  152. return rv
  153. def write(self, data):
  154. self.stopReading()
  155. abstract.FileDescriptor.write(self, data)
  156. def doRead(self):
  157. """
  158. The only way a write pipe can become "readable" is at EOF, because the
  159. child has closed it, and we're using a reactor which doesn't
  160. distinguish between readable and closed (such as the select reactor).
  161. Except that's not true on linux < 2.6.11. It has the following
  162. characteristics: write pipe is completely empty => POLLOUT (writable in
  163. select), write pipe is not completely empty => POLLIN (readable in
  164. select), write pipe's reader closed => POLLIN|POLLERR (readable and
  165. writable in select)
  166. That's what this funky code is for. If linux was not broken, this
  167. function could be simply "return CONNECTION_LOST".
  168. """
  169. if self.enableReadHack:
  170. return CONNECTION_LOST
  171. else:
  172. self.stopReading()
  173. def connectionLost(self, reason):
  174. """
  175. See abstract.FileDescriptor.connectionLost.
  176. """
  177. # At least on macOS 10.4, exiting while stdout is non-blocking can
  178. # result in data loss. For some reason putting the file descriptor
  179. # back into blocking mode seems to resolve this issue.
  180. fdesc.setBlocking(self.fd)
  181. abstract.FileDescriptor.connectionLost(self, reason)
  182. self.proc.childConnectionLost(self.name, reason)
  183. class ProcessReader(abstract.FileDescriptor):
  184. """
  185. ProcessReader
  186. I am a selectable representation of a process's output pipe, such as
  187. stdout and stderr.
  188. """
  189. connected = True
  190. def __init__(self, reactor, proc, name, fileno):
  191. """
  192. Initialize, specifying a process to connect to.
  193. """
  194. abstract.FileDescriptor.__init__(self, reactor)
  195. fdesc.setNonBlocking(fileno)
  196. self.proc = proc
  197. self.name = name
  198. self.fd = fileno
  199. self.startReading()
  200. def fileno(self):
  201. """
  202. Return the fileno() of my process's stderr.
  203. """
  204. return self.fd
  205. def writeSomeData(self, data):
  206. # the only time this is actually called is after .loseConnection Any
  207. # actual write attempt would fail, so we must avoid that. This hack
  208. # allows us to use .loseConnection on both readers and writers.
  209. assert data == b""
  210. return CONNECTION_LOST
  211. def doRead(self):
  212. """
  213. This is called when the pipe becomes readable.
  214. """
  215. return fdesc.readFromFD(self.fd, self.dataReceived)
  216. def dataReceived(self, data):
  217. self.proc.childDataReceived(self.name, data)
  218. def loseConnection(self):
  219. if self.connected and not self.disconnecting:
  220. self.disconnecting = 1
  221. self.stopReading()
  222. self.reactor.callLater(
  223. 0, self.connectionLost, failure.Failure(CONNECTION_DONE)
  224. )
  225. def connectionLost(self, reason):
  226. """
  227. Close my end of the pipe, signal the Process (which signals the
  228. ProcessProtocol).
  229. """
  230. abstract.FileDescriptor.connectionLost(self, reason)
  231. self.proc.childConnectionLost(self.name, reason)
  232. class _BaseProcess(BaseProcess):
  233. """
  234. Base class for Process and PTYProcess.
  235. """
  236. status: Optional[int] = None
  237. pid = None
  238. def reapProcess(self):
  239. """
  240. Try to reap a process (without blocking) via waitpid.
  241. This is called when sigchild is caught or a Process object loses its
  242. "connection" (stdout is closed) This ought to result in reaping all
  243. zombie processes, since it will be called twice as often as it needs
  244. to be.
  245. (Unfortunately, this is a slightly experimental approach, since
  246. UNIX has no way to be really sure that your process is going to
  247. go away w/o blocking. I don't want to block.)
  248. """
  249. try:
  250. try:
  251. pid, status = os.waitpid(self.pid, os.WNOHANG)
  252. except OSError as e:
  253. if e.errno == errno.ECHILD:
  254. # no child process
  255. pid = None
  256. else:
  257. raise
  258. except BaseException:
  259. log.msg(f"Failed to reap {self.pid}:")
  260. log.err()
  261. pid = None
  262. if pid:
  263. unregisterReapProcessHandler(pid, self)
  264. self.processEnded(status)
  265. def _getReason(self, status):
  266. exitCode = sig = None
  267. if os.WIFEXITED(status):
  268. exitCode = os.WEXITSTATUS(status)
  269. else:
  270. sig = os.WTERMSIG(status)
  271. if exitCode or sig:
  272. return error.ProcessTerminated(exitCode, sig, status)
  273. return error.ProcessDone(status)
  274. def signalProcess(self, signalID):
  275. """
  276. Send the given signal C{signalID} to the process. It'll translate a
  277. few signals ('HUP', 'STOP', 'INT', 'KILL', 'TERM') from a string
  278. representation to its int value, otherwise it'll pass directly the
  279. value provided
  280. @type signalID: C{str} or C{int}
  281. """
  282. if signalID in ("HUP", "STOP", "INT", "KILL", "TERM"):
  283. signalID = getattr(signal, f"SIG{signalID}")
  284. if self.pid is None:
  285. raise ProcessExitedAlready()
  286. try:
  287. os.kill(self.pid, signalID)
  288. except OSError as e:
  289. if e.errno == errno.ESRCH:
  290. raise ProcessExitedAlready()
  291. else:
  292. raise
  293. def _resetSignalDisposition(self):
  294. # The Python interpreter ignores some signals, and our child
  295. # process will inherit that behaviour. To have a child process
  296. # that responds to signals normally, we need to reset our
  297. # child process's signal handling (just) after we fork and
  298. # before we execvpe.
  299. for signalnum in range(1, signal.NSIG):
  300. if signal.getsignal(signalnum) == signal.SIG_IGN:
  301. # Reset signal handling to the default
  302. signal.signal(signalnum, signal.SIG_DFL)
  303. def _trySpawnInsteadOfFork(
  304. self, path, uid, gid, executable, args, environment, kwargs
  305. ):
  306. """
  307. Try to use posix_spawnp() instead of fork(), if possible.
  308. This implementation returns False because the non-PTY subclass
  309. implements the actual logic; we can't yet use this for pty processes.
  310. @return: a boolean indicating whether posix_spawnp() was used or not.
  311. """
  312. return False
  313. def _fork(self, path, uid, gid, executable, args, environment, **kwargs):
  314. """
  315. Fork and then exec sub-process.
  316. @param path: the path where to run the new process.
  317. @type path: L{bytes} or L{unicode}
  318. @param uid: if defined, the uid used to run the new process.
  319. @type uid: L{int}
  320. @param gid: if defined, the gid used to run the new process.
  321. @type gid: L{int}
  322. @param executable: the executable to run in a new process.
  323. @type executable: L{str}
  324. @param args: arguments used to create the new process.
  325. @type args: L{list}.
  326. @param environment: environment used for the new process.
  327. @type environment: L{dict}.
  328. @param kwargs: keyword arguments to L{_setupChild} method.
  329. """
  330. if self._trySpawnInsteadOfFork(
  331. path, uid, gid, executable, args, environment, kwargs
  332. ):
  333. return
  334. collectorEnabled = gc.isenabled()
  335. gc.disable()
  336. try:
  337. self.pid = os.fork()
  338. except BaseException:
  339. # Still in the parent process
  340. if collectorEnabled:
  341. gc.enable()
  342. raise
  343. else:
  344. if self.pid == 0:
  345. # A return value of 0 from fork() indicates that we are now
  346. # executing in the child process.
  347. # Do not put *ANY* code outside the try block. The child
  348. # process must either exec or _exit. If it gets outside this
  349. # block (due to an exception that is not handled here, but
  350. # which might be handled higher up), there will be two copies
  351. # of the parent running in parallel, doing all kinds of damage.
  352. # After each change to this code, review it to make sure there
  353. # are no exit paths.
  354. try:
  355. # Stop debugging. If I am, I don't care anymore.
  356. sys.settrace(None)
  357. self._setupChild(**kwargs)
  358. self._execChild(path, uid, gid, executable, args, environment)
  359. except BaseException:
  360. # If there are errors, try to write something descriptive
  361. # to stderr before exiting.
  362. # The parent's stderr isn't *necessarily* fd 2 anymore, or
  363. # even still available; however, even libc assumes that
  364. # write(2, err) is a useful thing to attempt.
  365. try:
  366. # On Python 3, print_exc takes a text stream, but
  367. # on Python 2 it still takes a byte stream. So on
  368. # Python 3 we will wrap up the byte stream returned
  369. # by os.fdopen using TextIOWrapper.
  370. # We hard-code UTF-8 as the encoding here, rather
  371. # than looking at something like
  372. # getfilesystemencoding() or sys.stderr.encoding,
  373. # because we want an encoding that will be able to
  374. # encode the full range of code points. We are
  375. # (most likely) talking to the parent process on
  376. # the other end of this pipe and not the filesystem
  377. # or the original sys.stderr, so there's no point
  378. # in trying to match the encoding of one of those
  379. # objects.
  380. stderr = io.TextIOWrapper(os.fdopen(2, "wb"), encoding="utf-8")
  381. msg = ("Upon execvpe {} {} in environment id {}" "\n:").format(
  382. executable, str(args), id(environment)
  383. )
  384. stderr.write(msg)
  385. traceback.print_exc(file=stderr)
  386. stderr.flush()
  387. for fd in range(3):
  388. os.close(fd)
  389. except BaseException:
  390. # Handle all errors during the error-reporting process
  391. # silently to ensure that the child terminates.
  392. pass
  393. # See comment above about making sure that we reach this line
  394. # of code.
  395. os._exit(1)
  396. # we are now in parent process
  397. if collectorEnabled:
  398. gc.enable()
  399. self.status = -1 # this records the exit status of the child
  400. def _setupChild(self, *args, **kwargs):
  401. """
  402. Setup the child process. Override in subclasses.
  403. """
  404. raise NotImplementedError()
  405. def _execChild(self, path, uid, gid, executable, args, environment):
  406. """
  407. The exec() which is done in the forked child.
  408. """
  409. if path:
  410. os.chdir(path)
  411. if uid is not None or gid is not None:
  412. if uid is None:
  413. uid = os.geteuid()
  414. if gid is None:
  415. gid = os.getegid()
  416. # set the UID before I actually exec the process
  417. os.setuid(0)
  418. os.setgid(0)
  419. switchUID(uid, gid)
  420. os.execvpe(executable, args, environment)
  421. def __repr__(self) -> str:
  422. """
  423. String representation of a process.
  424. """
  425. return "<{} pid={} status={}>".format(
  426. self.__class__.__name__,
  427. self.pid,
  428. self.status,
  429. )
  430. class _FDDetector:
  431. """
  432. This class contains the logic necessary to decide which of the available
  433. system techniques should be used to detect the open file descriptors for
  434. the current process. The chosen technique gets monkey-patched into the
  435. _listOpenFDs method of this class so that the detection only needs to occur
  436. once.
  437. @ivar listdir: The implementation of listdir to use. This gets overwritten
  438. by the test cases.
  439. @ivar getpid: The implementation of getpid to use, returns the PID of the
  440. running process.
  441. @ivar openfile: The implementation of open() to use, by default the Python
  442. builtin.
  443. """
  444. # So that we can unit test this
  445. listdir = os.listdir
  446. getpid = os.getpid
  447. openfile = open
  448. def __init__(self):
  449. self._implementations = [
  450. self._procFDImplementation,
  451. self._devFDImplementation,
  452. self._fallbackFDImplementation,
  453. ]
  454. def _listOpenFDs(self):
  455. """
  456. Return an iterable of file descriptors which I{may} be open in this
  457. process.
  458. This will try to return the fewest possible descriptors without missing
  459. any.
  460. """
  461. self._listOpenFDs = self._getImplementation()
  462. return self._listOpenFDs()
  463. def _getImplementation(self):
  464. """
  465. Pick a method which gives correct results for C{_listOpenFDs} in this
  466. runtime environment.
  467. This involves a lot of very platform-specific checks, some of which may
  468. be relatively expensive. Therefore the returned method should be saved
  469. and re-used, rather than always calling this method to determine what it
  470. is.
  471. See the implementation for the details of how a method is selected.
  472. """
  473. for impl in self._implementations:
  474. try:
  475. before = impl()
  476. except BaseException:
  477. continue
  478. with self.openfile("/dev/null", "r"):
  479. after = impl()
  480. if before != after:
  481. return impl
  482. # If no implementation can detect the newly opened file above, then just
  483. # return the last one. The last one should therefore always be one
  484. # which makes a simple static guess which includes all possible open
  485. # file descriptors, but perhaps also many other values which do not
  486. # correspond to file descriptors. For example, the scheme implemented
  487. # by _fallbackFDImplementation is suitable to be the last entry.
  488. return impl
  489. def _devFDImplementation(self):
  490. """
  491. Simple implementation for systems where /dev/fd actually works.
  492. See: http://www.freebsd.org/cgi/man.cgi?fdescfs
  493. """
  494. dname = "/dev/fd"
  495. result = [int(fd) for fd in self.listdir(dname)]
  496. return result
  497. def _procFDImplementation(self):
  498. """
  499. Simple implementation for systems where /proc/pid/fd exists (we assume
  500. it works).
  501. """
  502. dname = "/proc/%d/fd" % (self.getpid(),)
  503. return [int(fd) for fd in self.listdir(dname)]
  504. def _fallbackFDImplementation(self):
  505. """
  506. Fallback implementation where either the resource module can inform us
  507. about the upper bound of how many FDs to expect, or where we just guess
  508. a constant maximum if there is no resource module.
  509. All possible file descriptors from 0 to that upper bound are returned
  510. with no attempt to exclude invalid file descriptor values.
  511. """
  512. try:
  513. import resource
  514. except ImportError:
  515. maxfds = 1024
  516. else:
  517. # OS-X reports 9223372036854775808. That's a lot of fds to close.
  518. # OS-X should get the /dev/fd implementation instead, so mostly
  519. # this check probably isn't necessary.
  520. maxfds = min(1024, resource.getrlimit(resource.RLIMIT_NOFILE)[1])
  521. return range(maxfds)
  522. detector = _FDDetector()
  523. def _listOpenFDs():
  524. """
  525. Use the global detector object to figure out which FD implementation to
  526. use.
  527. """
  528. return detector._listOpenFDs()
  529. def _getFileActions(
  530. fdState: List[Tuple[int, bool]],
  531. childToParentFD: Dict[int, int],
  532. doClose: int,
  533. doDup2: int,
  534. ) -> List[Tuple[int, ...]]:
  535. """
  536. Get the C{file_actions} parameter for C{posix_spawn} based on the
  537. parameters describing the current process state.
  538. @param fdState: A list of 2-tuples of (file descriptor, close-on-exec
  539. flag).
  540. @param doClose: the integer to use for the 'close' instruction
  541. @param doDup2: the integer to use for the 'dup2' instruction
  542. """
  543. fdStateDict = dict(fdState)
  544. parentToChildren: Dict[int, List[int]] = defaultdict(list)
  545. for inChild, inParent in childToParentFD.items():
  546. parentToChildren[inParent].append(inChild)
  547. allocated = set(fdStateDict)
  548. allocated |= set(childToParentFD.values())
  549. allocated |= set(childToParentFD.keys())
  550. nextFD = 0
  551. def allocateFD() -> int:
  552. nonlocal nextFD
  553. while nextFD in allocated:
  554. nextFD += 1
  555. allocated.add(nextFD)
  556. return nextFD
  557. result: List[Tuple[int, ...]] = []
  558. relocations = {}
  559. for inChild, inParent in sorted(childToParentFD.items()):
  560. # The parent FD will later be reused by a child FD.
  561. parentToChildren[inParent].remove(inChild)
  562. if parentToChildren[inChild]:
  563. new = relocations[inChild] = allocateFD()
  564. result.append((doDup2, inChild, new))
  565. if inParent in relocations:
  566. result.append((doDup2, relocations[inParent], inChild))
  567. if not parentToChildren[inParent]:
  568. result.append((doClose, relocations[inParent]))
  569. else:
  570. if inParent == inChild:
  571. if fdStateDict[inParent]:
  572. # If the child is attempting to inherit the parent as-is,
  573. # and it is not close-on-exec, the job is already done; we
  574. # can bail. Otherwise...
  575. tempFD = allocateFD()
  576. # The child wants to inherit the parent as-is, so the
  577. # handle must be heritable.. dup2 makes the new descriptor
  578. # inheritable by default, *but*, per the man page, “if
  579. # fildes and fildes2 are equal, then dup2() just returns
  580. # fildes2; no other changes are made to the existing
  581. # descriptor”, so we need to dup it somewhere else and dup
  582. # it back before closing the temporary place we put it.
  583. result.extend(
  584. [
  585. (doDup2, inParent, tempFD),
  586. (doDup2, tempFD, inChild),
  587. (doClose, tempFD),
  588. ]
  589. )
  590. else:
  591. result.append((doDup2, inParent, inChild))
  592. for eachFD, uninheritable in fdStateDict.items():
  593. if eachFD not in childToParentFD and not uninheritable:
  594. result.append((doClose, eachFD))
  595. return result
  596. @implementer(IProcessTransport)
  597. class Process(_BaseProcess):
  598. """
  599. An operating-system Process.
  600. This represents an operating-system process with arbitrary input/output
  601. pipes connected to it. Those pipes may represent standard input, standard
  602. output, and standard error, or any other file descriptor.
  603. On UNIX, this is implemented using posix_spawnp() when possible (or fork(),
  604. exec(), pipe() and fcntl() when not). These calls may not exist elsewhere
  605. so this code is not cross-platform. (also, windows can only select on
  606. sockets...)
  607. """
  608. debug = False
  609. debug_child = False
  610. status = -1
  611. pid = None
  612. processWriterFactory = ProcessWriter
  613. processReaderFactory = ProcessReader
  614. def __init__(
  615. self,
  616. reactor,
  617. executable,
  618. args,
  619. environment,
  620. path,
  621. proto,
  622. uid=None,
  623. gid=None,
  624. childFDs=None,
  625. ):
  626. """
  627. Spawn an operating-system process.
  628. This is where the hard work of disconnecting all currently open
  629. files / forking / executing the new process happens. (This is
  630. executed automatically when a Process is instantiated.)
  631. This will also run the subprocess as a given user ID and group ID, if
  632. specified. (Implementation Note: this doesn't support all the arcane
  633. nuances of setXXuid on UNIX: it will assume that either your effective
  634. or real UID is 0.)
  635. """
  636. self._reactor = reactor
  637. if not proto:
  638. assert "r" not in childFDs.values()
  639. assert "w" not in childFDs.values()
  640. _BaseProcess.__init__(self, proto)
  641. self.pipes = {}
  642. # keys are childFDs, we can sense them closing
  643. # values are ProcessReader/ProcessWriters
  644. helpers = {}
  645. # keys are childFDs
  646. # values are parentFDs
  647. if childFDs is None:
  648. childFDs = {
  649. 0: "w", # we write to the child's stdin
  650. 1: "r", # we read from their stdout
  651. 2: "r", # and we read from their stderr
  652. }
  653. debug = self.debug
  654. if debug:
  655. print("childFDs", childFDs)
  656. _openedPipes = []
  657. def pipe():
  658. r, w = os.pipe()
  659. _openedPipes.extend([r, w])
  660. return r, w
  661. # fdmap.keys() are filenos of pipes that are used by the child.
  662. fdmap = {} # maps childFD to parentFD
  663. try:
  664. for childFD, target in childFDs.items():
  665. if debug:
  666. print("[%d]" % childFD, target)
  667. if target == "r":
  668. # we need a pipe that the parent can read from
  669. readFD, writeFD = pipe()
  670. if debug:
  671. print("readFD=%d, writeFD=%d" % (readFD, writeFD))
  672. fdmap[childFD] = writeFD # child writes to this
  673. helpers[childFD] = readFD # parent reads from this
  674. elif target == "w":
  675. # we need a pipe that the parent can write to
  676. readFD, writeFD = pipe()
  677. if debug:
  678. print("readFD=%d, writeFD=%d" % (readFD, writeFD))
  679. fdmap[childFD] = readFD # child reads from this
  680. helpers[childFD] = writeFD # parent writes to this
  681. else:
  682. assert type(target) == int, f"{target!r} should be an int"
  683. fdmap[childFD] = target # parent ignores this
  684. if debug:
  685. print("fdmap", fdmap)
  686. if debug:
  687. print("helpers", helpers)
  688. # the child only cares about fdmap.values()
  689. self._fork(path, uid, gid, executable, args, environment, fdmap=fdmap)
  690. except BaseException:
  691. for pipe in _openedPipes:
  692. os.close(pipe)
  693. raise
  694. # we are the parent process:
  695. self.proto = proto
  696. # arrange for the parent-side pipes to be read and written
  697. for childFD, parentFD in helpers.items():
  698. os.close(fdmap[childFD])
  699. if childFDs[childFD] == "r":
  700. reader = self.processReaderFactory(reactor, self, childFD, parentFD)
  701. self.pipes[childFD] = reader
  702. if childFDs[childFD] == "w":
  703. writer = self.processWriterFactory(
  704. reactor, self, childFD, parentFD, forceReadHack=True
  705. )
  706. self.pipes[childFD] = writer
  707. try:
  708. # the 'transport' is used for some compatibility methods
  709. if self.proto is not None:
  710. self.proto.makeConnection(self)
  711. except BaseException:
  712. log.err()
  713. # The reactor might not be running yet. This might call back into
  714. # processEnded synchronously, triggering an application-visible
  715. # callback. That's probably not ideal. The replacement API for
  716. # spawnProcess should improve upon this situation.
  717. registerReapProcessHandler(self.pid, self)
  718. def _trySpawnInsteadOfFork(
  719. self, path, uid, gid, executable, args, environment, kwargs
  720. ):
  721. """
  722. Try to use posix_spawnp() instead of fork(), if possible.
  723. @return: a boolean indicating whether posix_spawnp() was used or not.
  724. """
  725. if (
  726. # no support for setuid/setgid anywhere but in QNX's
  727. # posix_spawnattr_setcred
  728. (uid is not None)
  729. or (gid is not None)
  730. or ((path is not None) and (os.path.abspath(path) != os.path.abspath(".")))
  731. or getattr(self._reactor, "_neverUseSpawn", False)
  732. ):
  733. return False
  734. fdmap = kwargs.get("fdmap")
  735. fdState = []
  736. for eachFD in _listOpenFDs():
  737. try:
  738. isCloseOnExec = fcntl.fcntl(eachFD, fcntl.F_GETFD, fcntl.FD_CLOEXEC)
  739. except OSError:
  740. pass
  741. else:
  742. fdState.append((eachFD, isCloseOnExec))
  743. if environment is None:
  744. environment = os.environ
  745. setSigDef = [
  746. everySignal
  747. for everySignal in range(1, signal.NSIG)
  748. if signal.getsignal(everySignal) == signal.SIG_IGN
  749. ]
  750. self.pid = os.posix_spawnp(
  751. executable,
  752. args,
  753. environment,
  754. file_actions=_getFileActions(
  755. fdState, fdmap, doClose=_PS_CLOSE, doDup2=_PS_DUP2
  756. ),
  757. setsigdef=setSigDef,
  758. )
  759. self.status = -1
  760. return True
  761. if getattr(os, "posix_spawnp", None) is None:
  762. # If there's no posix_spawn implemented, let the superclass handle it
  763. del _trySpawnInsteadOfFork
  764. def _setupChild(self, fdmap):
  765. """
  766. fdmap[childFD] = parentFD
  767. The child wants to end up with 'childFD' attached to what used to be
  768. the parent's parentFD. As an example, a bash command run like
  769. 'command 2>&1' would correspond to an fdmap of {0:0, 1:1, 2:1}.
  770. 'command >foo.txt' would be {0:0, 1:os.open('foo.txt'), 2:2}.
  771. This is accomplished in two steps::
  772. 1. close all file descriptors that aren't values of fdmap. This
  773. means 0 .. maxfds (or just the open fds within that range, if
  774. the platform supports '/proc/<pid>/fd').
  775. 2. for each childFD::
  776. - if fdmap[childFD] == childFD, the descriptor is already in
  777. place. Make sure the CLOEXEC flag is not set, then delete
  778. the entry from fdmap.
  779. - if childFD is in fdmap.values(), then the target descriptor
  780. is busy. Use os.dup() to move it elsewhere, update all
  781. fdmap[childFD] items that point to it, then close the
  782. original. Then fall through to the next case.
  783. - now fdmap[childFD] is not in fdmap.values(), and is free.
  784. Use os.dup2() to move it to the right place, then close the
  785. original.
  786. """
  787. debug = self.debug_child
  788. if debug:
  789. errfd = sys.stderr
  790. errfd.write("starting _setupChild\n")
  791. destList = fdmap.values()
  792. for fd in _listOpenFDs():
  793. if fd in destList:
  794. continue
  795. if debug and fd == errfd.fileno():
  796. continue
  797. try:
  798. os.close(fd)
  799. except BaseException:
  800. pass
  801. # at this point, the only fds still open are the ones that need to
  802. # be moved to their appropriate positions in the child (the targets
  803. # of fdmap, i.e. fdmap.values() )
  804. if debug:
  805. print("fdmap", fdmap, file=errfd)
  806. for child in sorted(fdmap.keys()):
  807. target = fdmap[child]
  808. if target == child:
  809. # fd is already in place
  810. if debug:
  811. print("%d already in place" % target, file=errfd)
  812. fdesc._unsetCloseOnExec(child)
  813. else:
  814. if child in fdmap.values():
  815. # we can't replace child-fd yet, as some other mapping
  816. # still needs the fd it wants to target. We must preserve
  817. # that old fd by duping it to a new home.
  818. newtarget = os.dup(child) # give it a safe home
  819. if debug:
  820. print("os.dup(%d) -> %d" % (child, newtarget), file=errfd)
  821. os.close(child) # close the original
  822. for c, p in list(fdmap.items()):
  823. if p == child:
  824. fdmap[c] = newtarget # update all pointers
  825. # now it should be available
  826. if debug:
  827. print("os.dup2(%d,%d)" % (target, child), file=errfd)
  828. os.dup2(target, child)
  829. # At this point, the child has everything it needs. We want to close
  830. # everything that isn't going to be used by the child, i.e.
  831. # everything not in fdmap.keys(). The only remaining fds open are
  832. # those in fdmap.values().
  833. # Any given fd may appear in fdmap.values() multiple times, so we
  834. # need to remove duplicates first.
  835. old = []
  836. for fd in fdmap.values():
  837. if fd not in old:
  838. if fd not in fdmap.keys():
  839. old.append(fd)
  840. if debug:
  841. print("old", old, file=errfd)
  842. for fd in old:
  843. os.close(fd)
  844. self._resetSignalDisposition()
  845. def writeToChild(self, childFD, data):
  846. self.pipes[childFD].write(data)
  847. def closeChildFD(self, childFD):
  848. # for writer pipes, loseConnection tries to write the remaining data
  849. # out to the pipe before closing it
  850. # if childFD is not in the list of pipes, assume that it is already
  851. # closed
  852. if childFD in self.pipes:
  853. self.pipes[childFD].loseConnection()
  854. def pauseProducing(self):
  855. for p in self.pipes.values():
  856. if isinstance(p, ProcessReader):
  857. p.stopReading()
  858. def resumeProducing(self):
  859. for p in self.pipes.values():
  860. if isinstance(p, ProcessReader):
  861. p.startReading()
  862. # compatibility
  863. def closeStdin(self):
  864. """
  865. Call this to close standard input on this process.
  866. """
  867. self.closeChildFD(0)
  868. def closeStdout(self):
  869. self.closeChildFD(1)
  870. def closeStderr(self):
  871. self.closeChildFD(2)
  872. def loseConnection(self):
  873. self.closeStdin()
  874. self.closeStderr()
  875. self.closeStdout()
  876. def write(self, data):
  877. """
  878. Call this to write to standard input on this process.
  879. NOTE: This will silently lose data if there is no standard input.
  880. """
  881. if 0 in self.pipes:
  882. self.pipes[0].write(data)
  883. def registerProducer(self, producer, streaming):
  884. """
  885. Call this to register producer for standard input.
  886. If there is no standard input producer.stopProducing() will
  887. be called immediately.
  888. """
  889. if 0 in self.pipes:
  890. self.pipes[0].registerProducer(producer, streaming)
  891. else:
  892. producer.stopProducing()
  893. def unregisterProducer(self):
  894. """
  895. Call this to unregister producer for standard input."""
  896. if 0 in self.pipes:
  897. self.pipes[0].unregisterProducer()
  898. def writeSequence(self, seq):
  899. """
  900. Call this to write to standard input on this process.
  901. NOTE: This will silently lose data if there is no standard input.
  902. """
  903. if 0 in self.pipes:
  904. self.pipes[0].writeSequence(seq)
  905. def childDataReceived(self, name, data):
  906. self.proto.childDataReceived(name, data)
  907. def childConnectionLost(self, childFD, reason):
  908. # this is called when one of the helpers (ProcessReader or
  909. # ProcessWriter) notices their pipe has been closed
  910. os.close(self.pipes[childFD].fileno())
  911. del self.pipes[childFD]
  912. try:
  913. self.proto.childConnectionLost(childFD)
  914. except BaseException:
  915. log.err()
  916. self.maybeCallProcessEnded()
  917. def maybeCallProcessEnded(self):
  918. # we don't call ProcessProtocol.processEnded until:
  919. # the child has terminated, AND
  920. # all writers have indicated an error status, AND
  921. # all readers have indicated EOF
  922. # This insures that we've gathered all output from the process.
  923. if self.pipes:
  924. return
  925. if not self.lostProcess:
  926. self.reapProcess()
  927. return
  928. _BaseProcess.maybeCallProcessEnded(self)
  929. def getHost(self):
  930. # ITransport.getHost
  931. raise NotImplementedError()
  932. def getPeer(self):
  933. # ITransport.getPeer
  934. raise NotImplementedError()
  935. @implementer(IProcessTransport)
  936. class PTYProcess(abstract.FileDescriptor, _BaseProcess):
  937. """
  938. An operating-system Process that uses PTY support.
  939. """
  940. status = -1
  941. pid = None
  942. def __init__(
  943. self,
  944. reactor,
  945. executable,
  946. args,
  947. environment,
  948. path,
  949. proto,
  950. uid=None,
  951. gid=None,
  952. usePTY=None,
  953. ):
  954. """
  955. Spawn an operating-system process.
  956. This is where the hard work of disconnecting all currently open
  957. files / forking / executing the new process happens. (This is
  958. executed automatically when a Process is instantiated.)
  959. This will also run the subprocess as a given user ID and group ID, if
  960. specified. (Implementation Note: this doesn't support all the arcane
  961. nuances of setXXuid on UNIX: it will assume that either your effective
  962. or real UID is 0.)
  963. """
  964. if pty is None and not isinstance(usePTY, (tuple, list)):
  965. # no pty module and we didn't get a pty to use
  966. raise NotImplementedError(
  967. "cannot use PTYProcess on platforms without the pty module."
  968. )
  969. abstract.FileDescriptor.__init__(self, reactor)
  970. _BaseProcess.__init__(self, proto)
  971. if isinstance(usePTY, (tuple, list)):
  972. masterfd, slavefd, _ = usePTY
  973. else:
  974. masterfd, slavefd = pty.openpty()
  975. try:
  976. self._fork(
  977. path,
  978. uid,
  979. gid,
  980. executable,
  981. args,
  982. environment,
  983. masterfd=masterfd,
  984. slavefd=slavefd,
  985. )
  986. except BaseException:
  987. if not isinstance(usePTY, (tuple, list)):
  988. os.close(masterfd)
  989. os.close(slavefd)
  990. raise
  991. # we are now in parent process:
  992. os.close(slavefd)
  993. fdesc.setNonBlocking(masterfd)
  994. self.fd = masterfd
  995. self.startReading()
  996. self.connected = 1
  997. self.status = -1
  998. try:
  999. self.proto.makeConnection(self)
  1000. except BaseException:
  1001. log.err()
  1002. registerReapProcessHandler(self.pid, self)
  1003. def _setupChild(self, masterfd, slavefd):
  1004. """
  1005. Set up child process after C{fork()} but before C{exec()}.
  1006. This involves:
  1007. - closing C{masterfd}, since it is not used in the subprocess
  1008. - creating a new session with C{os.setsid}
  1009. - changing the controlling terminal of the process (and the new
  1010. session) to point at C{slavefd}
  1011. - duplicating C{slavefd} to standard input, output, and error
  1012. - closing all other open file descriptors (according to
  1013. L{_listOpenFDs})
  1014. - re-setting all signal handlers to C{SIG_DFL}
  1015. @param masterfd: The master end of a PTY file descriptors opened with
  1016. C{openpty}.
  1017. @type masterfd: L{int}
  1018. @param slavefd: The slave end of a PTY opened with C{openpty}.
  1019. @type slavefd: L{int}
  1020. """
  1021. os.close(masterfd)
  1022. os.setsid()
  1023. fcntl.ioctl(slavefd, termios.TIOCSCTTY, "")
  1024. for fd in range(3):
  1025. if fd != slavefd:
  1026. os.close(fd)
  1027. os.dup2(slavefd, 0) # stdin
  1028. os.dup2(slavefd, 1) # stdout
  1029. os.dup2(slavefd, 2) # stderr
  1030. for fd in _listOpenFDs():
  1031. if fd > 2:
  1032. try:
  1033. os.close(fd)
  1034. except BaseException:
  1035. pass
  1036. self._resetSignalDisposition()
  1037. def closeStdin(self):
  1038. # PTYs do not have stdin/stdout/stderr. They only have in and out, just
  1039. # like sockets. You cannot close one without closing off the entire PTY
  1040. pass
  1041. def closeStdout(self):
  1042. pass
  1043. def closeStderr(self):
  1044. pass
  1045. def doRead(self):
  1046. """
  1047. Called when my standard output stream is ready for reading.
  1048. """
  1049. return fdesc.readFromFD(
  1050. self.fd, lambda data: self.proto.childDataReceived(1, data)
  1051. )
  1052. def fileno(self):
  1053. """
  1054. This returns the file number of standard output on this process.
  1055. """
  1056. return self.fd
  1057. def maybeCallProcessEnded(self):
  1058. # two things must happen before we call the ProcessProtocol's
  1059. # processEnded method. 1: the child process must die and be reaped
  1060. # (which calls our own processEnded method). 2: the child must close
  1061. # their stdin/stdout/stderr fds, causing the pty to close, causing
  1062. # our connectionLost method to be called. #2 can also be triggered
  1063. # by calling .loseConnection().
  1064. if self.lostProcess == 2:
  1065. _BaseProcess.maybeCallProcessEnded(self)
  1066. def connectionLost(self, reason):
  1067. """
  1068. I call this to clean up when one or all of my connections has died.
  1069. """
  1070. abstract.FileDescriptor.connectionLost(self, reason)
  1071. os.close(self.fd)
  1072. self.lostProcess += 1
  1073. self.maybeCallProcessEnded()
  1074. def writeSomeData(self, data):
  1075. """
  1076. Write some data to the open process.
  1077. """
  1078. return fdesc.writeToFD(self.fd, data)
  1079. def closeChildFD(self, descriptor):
  1080. # IProcessTransport
  1081. raise NotImplementedError()
  1082. def writeToChild(self, childFD, data):
  1083. # IProcessTransport
  1084. raise NotImplementedError()