Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

process.py 38KB

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