1
# -*- test-case-name: twisted.test.test_process -*-
2
# Copyright (c) 2001-2009 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
32
from twisted.python import failure
34
from twisted.internet import _pollingfile
35
from twisted.internet._baseprocess import BaseProcess
42
class _Reaper(_pollingfile._PollableResource):
44
def __init__(self, proc):
48
if win32event.WaitForSingleObject(self.proc.hProcess, 0) != win32event.WAIT_OBJECT_0:
50
exitCode = win32process.GetExitCodeProcess(self.proc.hProcess)
52
self.proc.processEnded(exitCode)
56
def _findShebang(filename):
58
Look for a #! line, and return the value following the #! if one exists, or
59
None if this file is not a script.
61
I don't know if there are any conventions for quoting in Windows shebang
62
lines, so this doesn't support any; therefore, you may not pass any
63
arguments to scripts invoked as filters. That's probably wrong, so if
64
somebody knows more about the cultural expectations on Windows, please feel
67
This shebang line support was added in support of the CGI tests;
68
appropriately enough, I determined that shebang lines are culturally
69
accepted in the Windows world through this page::
71
http://www.cgi101.com/learn/connect/winxp.html
73
@param filename: str representing a filename
75
@return: a str representing another filename.
77
f = file(filename, 'rU')
79
exe = f.readline(1024).strip('\n')
82
def _invalidWin32App(pywinerr):
84
Determine if a pywintypes.error is telling us that the given process is
85
'not a valid win32 application', i.e. not a PE format executable.
87
@param pywinerr: a pywintypes.error instance raised by CreateProcess
92
# Let's do this better in the future, but I have no idea what this error
93
# is; MSDN doesn't mention it, and there is no symbolic constant in
94
# win32process module that represents 193.
96
return pywinerr.args[0] == 193
98
class Process(_pollingfile._PollingTimer, BaseProcess):
99
"""A process that integrates with the Twisted event loop.
101
If your subprocess is a python program, you need to:
103
- Run python.exe with the '-u' command line option - this turns on
104
unbuffered I/O. Buffering stdout/err/in can cause problems, see e.g.
105
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q1903
107
- If you don't want Windows messing with data passed over
108
stdin/out/err, set the pipes to be in binary mode::
110
import os, sys, mscvrt
111
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
112
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
113
msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
116
implements(IProcessTransport, IConsumer, IProducer)
120
def __init__(self, reactor, protocol, command, args, environment, path):
121
_pollingfile._PollingTimer.__init__(self, reactor)
122
BaseProcess.__init__(self, protocol)
124
# security attributes for pipes
125
sAttrs = win32security.SECURITY_ATTRIBUTES()
126
sAttrs.bInheritHandle = 1
128
# create the pipes which will connect to the secondary process
129
self.hStdoutR, hStdoutW = win32pipe.CreatePipe(sAttrs, 0)
130
self.hStderrR, hStderrW = win32pipe.CreatePipe(sAttrs, 0)
131
hStdinR, self.hStdinW = win32pipe.CreatePipe(sAttrs, 0)
133
win32pipe.SetNamedPipeHandleState(self.hStdinW,
134
win32pipe.PIPE_NOWAIT,
138
# set the info structure for the new process.
139
StartupInfo = win32process.STARTUPINFO()
140
StartupInfo.hStdOutput = hStdoutW
141
StartupInfo.hStdError = hStderrW
142
StartupInfo.hStdInput = hStdinR
143
StartupInfo.dwFlags = win32process.STARTF_USESTDHANDLES
145
# Create new handles whose inheritance property is false
146
currentPid = win32api.GetCurrentProcess()
148
tmp = win32api.DuplicateHandle(currentPid, self.hStdoutR, currentPid, 0, 0,
149
win32con.DUPLICATE_SAME_ACCESS)
150
win32file.CloseHandle(self.hStdoutR)
153
tmp = win32api.DuplicateHandle(currentPid, self.hStderrR, currentPid, 0, 0,
154
win32con.DUPLICATE_SAME_ACCESS)
155
win32file.CloseHandle(self.hStderrR)
158
tmp = win32api.DuplicateHandle(currentPid, self.hStdinW, currentPid, 0, 0,
159
win32con.DUPLICATE_SAME_ACCESS)
160
win32file.CloseHandle(self.hStdinW)
163
# Add the specified environment to the current environment - this is
164
# necessary because certain operations are only supported on Windows
165
# if certain environment variables are present.
167
env = os.environ.copy()
168
env.update(environment or {})
170
cmdline = quoteArguments(args)
171
# TODO: error detection here.
173
self.hProcess, self.hThread, self.pid, dwTid = win32process.CreateProcess(
174
command, cmdline, None, None, 1, 0, env, path, StartupInfo)
177
except pywintypes.error, pwte:
178
if not _invalidWin32App(pwte):
179
# This behavior isn't _really_ documented, but let's make it
180
# consistent with the behavior that is documented.
183
# look for a shebang line. Insert the original 'command'
184
# (actually a script) into the new arguments list.
185
sheb = _findShebang(command)
188
"%r is neither a Windows executable, "
189
"nor a script with a shebang line" % command)
192
args.insert(0, command)
193
cmdline = quoteArguments(args)
199
except pywintypes.error, pwte2:
200
# d'oh, failed again!
201
if _invalidWin32App(pwte2):
203
"%r has an invalid shebang line: "
204
"%r is not a valid executable" % (
208
# close handles which only the child will use
209
win32file.CloseHandle(hStderrW)
210
win32file.CloseHandle(hStdoutW)
211
win32file.CloseHandle(hStdinR)
214
self.stdout = _pollingfile._PollableReadPipe(
216
lambda data: self.proto.childDataReceived(1, data),
217
self.outConnectionLost)
219
self.stderr = _pollingfile._PollableReadPipe(
221
lambda data: self.proto.childDataReceived(2, data),
222
self.errConnectionLost)
224
self.stdin = _pollingfile._PollableWritePipe(
225
self.hStdinW, self.inConnectionLost)
227
for pipewatcher in self.stdout, self.stderr, self.stdin:
228
self._addPollableResource(pipewatcher)
232
self.proto.makeConnection(self)
234
self._addPollableResource(_Reaper(self))
237
def signalProcess(self, signalID):
239
raise error.ProcessExitedAlready()
240
if signalID in ("INT", "TERM", "KILL"):
241
win32process.TerminateProcess(self.hProcess, 1)
244
def _getReason(self, status):
246
return error.ProcessDone(status)
247
return error.ProcessTerminated(status)
250
def write(self, data):
251
"""Write data to the process' stdin."""
252
self.stdin.write(data)
254
def writeSequence(self, seq):
255
"""Write data to the process' stdin."""
256
self.stdin.writeSequence(seq)
258
def closeChildFD(self, fd):
266
raise NotImplementedError("Only standard-IO file descriptors available on win32")
268
def closeStdin(self):
269
"""Close the process' stdin.
273
def closeStderr(self):
276
def closeStdout(self):
279
def loseConnection(self):
280
"""Close the process' stdout, in and err."""
286
def outConnectionLost(self):
287
self.proto.childConnectionLost(1)
288
self.connectionLostNotify()
291
def errConnectionLost(self):
292
self.proto.childConnectionLost(2)
293
self.connectionLostNotify()
296
def inConnectionLost(self):
297
self.proto.childConnectionLost(0)
298
self.connectionLostNotify()
301
def connectionLostNotify(self):
303
Will be called 3 times, by stdout/err threads and process handle.
305
self.closedNotifies += 1
306
self.maybeCallProcessEnded()
309
def maybeCallProcessEnded(self):
310
if self.closedNotifies == 3 and self.lostProcess:
311
win32file.CloseHandle(self.hProcess)
312
win32file.CloseHandle(self.hThread)
315
BaseProcess.maybeCallProcessEnded(self)
319
def registerProducer(self, producer, streaming):
320
self.stdin.registerProducer(producer, streaming)
322
def unregisterProducer(self):
323
self.stdin.unregisterProducer()
326
def pauseProducing(self):
329
def resumeProducing(self):
332
def stopProducing(self):
333
self.loseConnection()
338
Return a string representation of the process.
340
return "<%s pid=%s>" % (self.__class__.__name__, self.pid)