1 # -*- test-case-name: twisted.test.test_process -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 http://isometric.sixsided.org/_/gates_in_the_head/
22 # security attributes for pipes
23 PIPE_ATTRS_INHERITABLE = win32security.SECURITY_ATTRIBUTES()
24 PIPE_ATTRS_INHERITABLE.bInheritHandle = 1
26 from zope.interface import implements
27 from twisted.internet.interfaces import IProcessTransport, IConsumer, IProducer
29 from twisted.python.win32 import quoteArguments
31 from twisted.internet import error
33 from twisted.internet import _pollingfile
34 from twisted.internet._baseprocess import BaseProcess
41 class _Reaper(_pollingfile._PollableResource):
43 def __init__(self, proc):
47 if win32event.WaitForSingleObject(self.proc.hProcess, 0) != win32event.WAIT_OBJECT_0:
49 exitCode = win32process.GetExitCodeProcess(self.proc.hProcess)
51 self.proc.processEnded(exitCode)
55 def _findShebang(filename):
57 Look for a #! line, and return the value following the #! if one exists, or
58 None if this file is not a script.
60 I don't know if there are any conventions for quoting in Windows shebang
61 lines, so this doesn't support any; therefore, you may not pass any
62 arguments to scripts invoked as filters. That's probably wrong, so if
63 somebody knows more about the cultural expectations on Windows, please feel
66 This shebang line support was added in support of the CGI tests;
67 appropriately enough, I determined that shebang lines are culturally
68 accepted in the Windows world through this page::
70 http://www.cgi101.com/learn/connect/winxp.html
72 @param filename: str representing a filename
74 @return: a str representing another filename.
76 f = file(filename, 'rU')
78 exe = f.readline(1024).strip('\n')
81 def _invalidWin32App(pywinerr):
83 Determine if a pywintypes.error is telling us that the given process is
84 'not a valid win32 application', i.e. not a PE format executable.
86 @param pywinerr: a pywintypes.error instance raised by CreateProcess
91 # Let's do this better in the future, but I have no idea what this error
92 # is; MSDN doesn't mention it, and there is no symbolic constant in
93 # win32process module that represents 193.
95 return pywinerr.args[0] == 193
97 class Process(_pollingfile._PollingTimer, BaseProcess):
98 """A process that integrates with the Twisted event loop.
100 If your subprocess is a python program, you need to:
102 - Run python.exe with the '-u' command line option - this turns on
103 unbuffered I/O. Buffering stdout/err/in can cause problems, see e.g.
104 http://support.microsoft.com/default.aspx?scid=kb;EN-US;q1903
106 - If you don't want Windows messing with data passed over
107 stdin/out/err, set the pipes to be in binary mode::
109 import os, sys, mscvrt
110 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
111 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
112 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
115 implements(IProcessTransport, IConsumer, IProducer)
119 def __init__(self, reactor, protocol, command, args, environment, path):
121 Create a new child process.
123 _pollingfile._PollingTimer.__init__(self, reactor)
124 BaseProcess.__init__(self, protocol)
126 # security attributes for pipes
127 sAttrs = win32security.SECURITY_ATTRIBUTES()
128 sAttrs.bInheritHandle = 1
130 # create the pipes which will connect to the secondary process
131 self.hStdoutR, hStdoutW = win32pipe.CreatePipe(sAttrs, 0)
132 self.hStderrR, hStderrW = win32pipe.CreatePipe(sAttrs, 0)
133 hStdinR, self.hStdinW = win32pipe.CreatePipe(sAttrs, 0)
135 win32pipe.SetNamedPipeHandleState(self.hStdinW,
136 win32pipe.PIPE_NOWAIT,
140 # set the info structure for the new process.
141 StartupInfo = win32process.STARTUPINFO()
142 StartupInfo.hStdOutput = hStdoutW
143 StartupInfo.hStdError = hStderrW
144 StartupInfo.hStdInput = hStdinR
145 StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES
147 # Create new handles whose inheritance property is false
148 currentPid = win32api.GetCurrentProcess()
150 tmp = win32api.DuplicateHandle(currentPid, self.hStdoutR, currentPid, 0, 0,
151 win32con.DUPLICATE_SAME_ACCESS)
152 win32file.CloseHandle(self.hStdoutR)
155 tmp = win32api.DuplicateHandle(currentPid, self.hStderrR, currentPid, 0, 0,
156 win32con.DUPLICATE_SAME_ACCESS)
157 win32file.CloseHandle(self.hStderrR)
160 tmp = win32api.DuplicateHandle(currentPid, self.hStdinW, currentPid, 0, 0,
161 win32con.DUPLICATE_SAME_ACCESS)
162 win32file.CloseHandle(self.hStdinW)
165 # Add the specified environment to the current environment - this is
166 # necessary because certain operations are only supported on Windows
167 # if certain environment variables are present.
169 env = os.environ.copy()
170 env.update(environment or {})
172 cmdline = quoteArguments(args)
173 # TODO: error detection here. See #2787 and #4184.
175 self.hProcess, self.hThread, self.pid, dwTid = win32process.CreateProcess(
176 command, cmdline, None, None, 1, 0, env, path, StartupInfo)
181 # win32process.CreateProcess cannot deal with mixed
182 # str/unicode environment, so we make it all Unicode
183 if e.args != ('All dictionary items must be strings, or '
184 'all must be unicode',):
187 for key, value in env.items():
188 newenv[unicode(key)] = unicode(value)
191 except pywintypes.error, pwte:
192 if not _invalidWin32App(pwte):
193 # This behavior isn't _really_ documented, but let's make it
194 # consistent with the behavior that is documented.
197 # look for a shebang line. Insert the original 'command'
198 # (actually a script) into the new arguments list.
199 sheb = _findShebang(command)
202 "%r is neither a Windows executable, "
203 "nor a script with a shebang line" % command)
206 args.insert(0, command)
207 cmdline = quoteArguments(args)
213 except pywintypes.error, pwte2:
214 # d'oh, failed again!
215 if _invalidWin32App(pwte2):
217 "%r has an invalid shebang line: "
218 "%r is not a valid executable" % (
222 # close handles which only the child will use
223 win32file.CloseHandle(hStderrW)
224 win32file.CloseHandle(hStdoutW)
225 win32file.CloseHandle(hStdinR)
228 self.stdout = _pollingfile._PollableReadPipe(
230 lambda data: self.proto.childDataReceived(1, data),
231 self.outConnectionLost)
233 self.stderr = _pollingfile._PollableReadPipe(
235 lambda data: self.proto.childDataReceived(2, data),
236 self.errConnectionLost)
238 self.stdin = _pollingfile._PollableWritePipe(
239 self.hStdinW, self.inConnectionLost)
241 for pipewatcher in self.stdout, self.stderr, self.stdin:
242 self._addPollableResource(pipewatcher)
246 self.proto.makeConnection(self)
248 self._addPollableResource(_Reaper(self))
251 def signalProcess(self, signalID):
253 raise error.ProcessExitedAlready()
254 if signalID in ("INT", "TERM", "KILL"):
255 win32process.TerminateProcess(self.hProcess, 1)
258 def _getReason(self, status):
260 return error.ProcessDone(status)
261 return error.ProcessTerminated(status)
264 def write(self, data):
266 Write data to the process' stdin.
270 self.stdin.write(data)
273 def writeSequence(self, seq):
275 Write data to the process' stdin.
277 @type data: C{list} of C{str}
279 self.stdin.writeSequence(seq)
282 def writeToChild(self, fd, data):
284 Similar to L{ITransport.write} but also allows the file descriptor in
285 the child process which will receive the bytes to be specified.
287 This implementation is limited to writing to the child's standard input.
289 @param fd: The file descriptor to which to write. Only stdin (C{0}) is
293 @param data: The bytes to write.
298 @raise KeyError: If C{fd} is anything other than the stdin file
302 self.stdin.write(data)
307 def closeChildFD(self, fd):
315 raise NotImplementedError("Only standard-IO file descriptors available on win32")
317 def closeStdin(self):
318 """Close the process' stdin.
322 def closeStderr(self):
325 def closeStdout(self):
328 def loseConnection(self):
329 """Close the process' stdout, in and err."""
335 def outConnectionLost(self):
336 self.proto.childConnectionLost(1)
337 self.connectionLostNotify()
340 def errConnectionLost(self):
341 self.proto.childConnectionLost(2)
342 self.connectionLostNotify()
345 def inConnectionLost(self):
346 self.proto.childConnectionLost(0)
347 self.connectionLostNotify()
350 def connectionLostNotify(self):
352 Will be called 3 times, by stdout/err threads and process handle.
354 self.closedNotifies += 1
355 self.maybeCallProcessEnded()
358 def maybeCallProcessEnded(self):
359 if self.closedNotifies == 3 and self.lostProcess:
360 win32file.CloseHandle(self.hProcess)
361 win32file.CloseHandle(self.hThread)
364 BaseProcess.maybeCallProcessEnded(self)
368 def registerProducer(self, producer, streaming):
369 self.stdin.registerProducer(producer, streaming)
371 def unregisterProducer(self):
372 self.stdin.unregisterProducer()
375 def pauseProducing(self):
378 def resumeProducing(self):
381 def stopProducing(self):
382 self.loseConnection()
386 Return a string representation of the process.
388 return "<%s pid=%s>" % (self.__class__.__name__, self.pid)