root / trunk / twisted / internet / process.py

Revision 26050, 30.6 kB (checked in by exarkun, 6 months ago)

Merge cloexec-3576

Author: glyph, exarkun, itamar
Reviewer: zseil, glyph
Fixes: #3576

Add (private) close-on-exec togglers to twisted.internet.fdesc and change
the rest of Twisted to use them instead of redoing the fcntl stuff directly
each time. Also add close-on-exec to several file descriptor sources which
previously left that flag unset - the reactor waker's pipes and accepted
SOCK_STREAM sockets.

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