~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/internet/_dumbwin32proc.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.test.test_process -*-
 
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
http://isometric.sixsided.org/_/gates_in_the_head/
 
7
"""
 
8
 
 
9
import os
 
10
 
 
11
# Win32 imports
 
12
import win32api
 
13
import win32con
 
14
import win32event
 
15
import win32file
 
16
import win32pipe
 
17
import win32process
 
18
import win32security
 
19
 
 
20
import pywintypes
 
21
 
 
22
# security attributes for pipes
 
23
PIPE_ATTRS_INHERITABLE = win32security.SECURITY_ATTRIBUTES()
 
24
PIPE_ATTRS_INHERITABLE.bInheritHandle = 1
 
25
 
 
26
from zope.interface import implements
 
27
from twisted.internet.interfaces import IProcessTransport, IConsumer, IProducer
 
28
 
 
29
from twisted.python.win32 import quoteArguments
 
30
 
 
31
from twisted.internet import error
 
32
from twisted.python import failure
 
33
 
 
34
from twisted.internet import _pollingfile
 
35
from twisted.internet._baseprocess import BaseProcess
 
36
 
 
37
def debug(msg):
 
38
    import sys
 
39
    print msg
 
40
    sys.stdout.flush()
 
41
 
 
42
class _Reaper(_pollingfile._PollableResource):
 
43
 
 
44
    def __init__(self, proc):
 
45
        self.proc = proc
 
46
 
 
47
    def checkWork(self):
 
48
        if win32event.WaitForSingleObject(self.proc.hProcess, 0) != win32event.WAIT_OBJECT_0:
 
49
            return 0
 
50
        exitCode = win32process.GetExitCodeProcess(self.proc.hProcess)
 
51
        self.deactivate()
 
52
        self.proc.processEnded(exitCode)
 
53
        return 0
 
54
 
 
55
 
 
56
def _findShebang(filename):
 
57
    """
 
58
    Look for a #! line, and return the value following the #! if one exists, or
 
59
    None if this file is not a script.
 
60
 
 
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
 
65
    free to fix.
 
66
 
 
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::
 
70
 
 
71
        http://www.cgi101.com/learn/connect/winxp.html
 
72
 
 
73
    @param filename: str representing a filename
 
74
 
 
75
    @return: a str representing another filename.
 
76
    """
 
77
    f = file(filename, 'rU')
 
78
    if f.read(2) == '#!':
 
79
        exe = f.readline(1024).strip('\n')
 
80
        return exe
 
81
 
 
82
def _invalidWin32App(pywinerr):
 
83
    """
 
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.
 
86
 
 
87
    @param pywinerr: a pywintypes.error instance raised by CreateProcess
 
88
 
 
89
    @return: a boolean
 
90
    """
 
91
 
 
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.
 
95
 
 
96
    return pywinerr.args[0] == 193
 
97
 
 
98
class Process(_pollingfile._PollingTimer, BaseProcess):
 
99
    """A process that integrates with the Twisted event loop.
 
100
 
 
101
    If your subprocess is a python program, you need to:
 
102
 
 
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
 
106
 
 
107
     - If you don't want Windows messing with data passed over
 
108
       stdin/out/err, set the pipes to be in binary mode::
 
109
 
 
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)
 
114
 
 
115
    """
 
116
    implements(IProcessTransport, IConsumer, IProducer)
 
117
 
 
118
    closedNotifies = 0
 
119
 
 
120
    def __init__(self, reactor, protocol, command, args, environment, path):
 
121
        _pollingfile._PollingTimer.__init__(self, reactor)
 
122
        BaseProcess.__init__(self, protocol)
 
123
 
 
124
        # security attributes for pipes
 
125
        sAttrs = win32security.SECURITY_ATTRIBUTES()
 
126
        sAttrs.bInheritHandle = 1
 
127
 
 
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)
 
132
 
 
133
        win32pipe.SetNamedPipeHandleState(self.hStdinW,
 
134
                                          win32pipe.PIPE_NOWAIT,
 
135
                                          None,
 
136
                                          None)
 
137
 
 
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
 
144
 
 
145
        # Create new handles whose inheritance property is false
 
146
        currentPid = win32api.GetCurrentProcess()
 
147
 
 
148
        tmp = win32api.DuplicateHandle(currentPid, self.hStdoutR, currentPid, 0, 0,
 
149
                                       win32con.DUPLICATE_SAME_ACCESS)
 
150
        win32file.CloseHandle(self.hStdoutR)
 
151
        self.hStdoutR = tmp
 
152
 
 
153
        tmp = win32api.DuplicateHandle(currentPid, self.hStderrR, currentPid, 0, 0,
 
154
                                       win32con.DUPLICATE_SAME_ACCESS)
 
155
        win32file.CloseHandle(self.hStderrR)
 
156
        self.hStderrR = tmp
 
157
 
 
158
        tmp = win32api.DuplicateHandle(currentPid, self.hStdinW, currentPid, 0, 0,
 
159
                                       win32con.DUPLICATE_SAME_ACCESS)
 
160
        win32file.CloseHandle(self.hStdinW)
 
161
        self.hStdinW = tmp
 
162
 
 
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.
 
166
 
 
167
        env = os.environ.copy()
 
168
        env.update(environment or {})
 
169
 
 
170
        cmdline = quoteArguments(args)
 
171
        # TODO: error detection here.
 
172
        def doCreate():
 
173
            self.hProcess, self.hThread, self.pid, dwTid = win32process.CreateProcess(
 
174
                command, cmdline, None, None, 1, 0, env, path, StartupInfo)
 
175
        try:
 
176
            doCreate()
 
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.
 
181
                raise OSError(pwte)
 
182
            else:
 
183
                # look for a shebang line.  Insert the original 'command'
 
184
                # (actually a script) into the new arguments list.
 
185
                sheb = _findShebang(command)
 
186
                if sheb is None:
 
187
                    raise OSError(
 
188
                        "%r is neither a Windows executable, "
 
189
                        "nor a script with a shebang line" % command)
 
190
                else:
 
191
                    args = list(args)
 
192
                    args.insert(0, command)
 
193
                    cmdline = quoteArguments(args)
 
194
                    origcmd = command
 
195
                    command = sheb
 
196
                    try:
 
197
                        # Let's try again.
 
198
                        doCreate()
 
199
                    except pywintypes.error, pwte2:
 
200
                        # d'oh, failed again!
 
201
                        if _invalidWin32App(pwte2):
 
202
                            raise OSError(
 
203
                                "%r has an invalid shebang line: "
 
204
                                "%r is not a valid executable" % (
 
205
                                    origcmd, sheb))
 
206
                        raise OSError(pwte2)
 
207
 
 
208
        # close handles which only the child will use
 
209
        win32file.CloseHandle(hStderrW)
 
210
        win32file.CloseHandle(hStdoutW)
 
211
        win32file.CloseHandle(hStdinR)
 
212
 
 
213
        # set up everything
 
214
        self.stdout = _pollingfile._PollableReadPipe(
 
215
            self.hStdoutR,
 
216
            lambda data: self.proto.childDataReceived(1, data),
 
217
            self.outConnectionLost)
 
218
 
 
219
        self.stderr = _pollingfile._PollableReadPipe(
 
220
                self.hStderrR,
 
221
                lambda data: self.proto.childDataReceived(2, data),
 
222
                self.errConnectionLost)
 
223
 
 
224
        self.stdin = _pollingfile._PollableWritePipe(
 
225
            self.hStdinW, self.inConnectionLost)
 
226
 
 
227
        for pipewatcher in self.stdout, self.stderr, self.stdin:
 
228
            self._addPollableResource(pipewatcher)
 
229
 
 
230
 
 
231
        # notify protocol
 
232
        self.proto.makeConnection(self)
 
233
 
 
234
        self._addPollableResource(_Reaper(self))
 
235
 
 
236
 
 
237
    def signalProcess(self, signalID):
 
238
        if self.pid is None:
 
239
            raise error.ProcessExitedAlready()
 
240
        if signalID in ("INT", "TERM", "KILL"):
 
241
            win32process.TerminateProcess(self.hProcess, 1)
 
242
 
 
243
 
 
244
    def _getReason(self, status):
 
245
        if status == 0:
 
246
            return error.ProcessDone(status)
 
247
        return error.ProcessTerminated(status)
 
248
 
 
249
 
 
250
    def write(self, data):
 
251
        """Write data to the process' stdin."""
 
252
        self.stdin.write(data)
 
253
 
 
254
    def writeSequence(self, seq):
 
255
        """Write data to the process' stdin."""
 
256
        self.stdin.writeSequence(seq)
 
257
 
 
258
    def closeChildFD(self, fd):
 
259
        if fd == 0:
 
260
            self.closeStdin()
 
261
        elif fd == 1:
 
262
            self.closeStdout()
 
263
        elif fd == 2:
 
264
            self.closeStderr()
 
265
        else:
 
266
            raise NotImplementedError("Only standard-IO file descriptors available on win32")
 
267
 
 
268
    def closeStdin(self):
 
269
        """Close the process' stdin.
 
270
        """
 
271
        self.stdin.close()
 
272
 
 
273
    def closeStderr(self):
 
274
        self.stderr.close()
 
275
 
 
276
    def closeStdout(self):
 
277
        self.stdout.close()
 
278
 
 
279
    def loseConnection(self):
 
280
        """Close the process' stdout, in and err."""
 
281
        self.closeStdin()
 
282
        self.closeStdout()
 
283
        self.closeStderr()
 
284
 
 
285
 
 
286
    def outConnectionLost(self):
 
287
        self.proto.childConnectionLost(1)
 
288
        self.connectionLostNotify()
 
289
 
 
290
 
 
291
    def errConnectionLost(self):
 
292
        self.proto.childConnectionLost(2)
 
293
        self.connectionLostNotify()
 
294
 
 
295
 
 
296
    def inConnectionLost(self):
 
297
        self.proto.childConnectionLost(0)
 
298
        self.connectionLostNotify()
 
299
 
 
300
 
 
301
    def connectionLostNotify(self):
 
302
        """
 
303
        Will be called 3 times, by stdout/err threads and process handle.
 
304
        """
 
305
        self.closedNotifies += 1
 
306
        self.maybeCallProcessEnded()
 
307
 
 
308
 
 
309
    def maybeCallProcessEnded(self):
 
310
        if self.closedNotifies == 3 and self.lostProcess:
 
311
            win32file.CloseHandle(self.hProcess)
 
312
            win32file.CloseHandle(self.hThread)
 
313
            self.hProcess = None
 
314
            self.hThread = None
 
315
            BaseProcess.maybeCallProcessEnded(self)
 
316
 
 
317
 
 
318
    # IConsumer
 
319
    def registerProducer(self, producer, streaming):
 
320
        self.stdin.registerProducer(producer, streaming)
 
321
 
 
322
    def unregisterProducer(self):
 
323
        self.stdin.unregisterProducer()
 
324
 
 
325
    # IProducer
 
326
    def pauseProducing(self):
 
327
        self._pause()
 
328
 
 
329
    def resumeProducing(self):
 
330
        self._unpause()
 
331
 
 
332
    def stopProducing(self):
 
333
        self.loseConnection()
 
334
 
 
335
 
 
336
    def __repr__(self):
 
337
        """
 
338
        Return a string representation of the process.
 
339
        """
 
340
        return "<%s pid=%s>" % (self.__class__.__name__, self.pid)