Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / internet / _dumbwin32proc.py
1 # -*- test-case-name: twisted.test.test_process -*-
2 # Copyright (c) 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
33 from twisted.internet import _pollingfile
34 from twisted.internet._baseprocess import BaseProcess
35
36 def debug(msg):
37     import sys
38     print msg
39     sys.stdout.flush()
40
41 class _Reaper(_pollingfile._PollableResource):
42
43     def __init__(self, proc):
44         self.proc = proc
45
46     def checkWork(self):
47         if win32event.WaitForSingleObject(self.proc.hProcess, 0) != win32event.WAIT_OBJECT_0:
48             return 0
49         exitCode = win32process.GetExitCodeProcess(self.proc.hProcess)
50         self.deactivate()
51         self.proc.processEnded(exitCode)
52         return 0
53
54
55 def _findShebang(filename):
56     """
57     Look for a #! line, and return the value following the #! if one exists, or
58     None if this file is not a script.
59
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
64     free to fix.
65
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::
69
70         http://www.cgi101.com/learn/connect/winxp.html
71
72     @param filename: str representing a filename
73
74     @return: a str representing another filename.
75     """
76     f = file(filename, 'rU')
77     if f.read(2) == '#!':
78         exe = f.readline(1024).strip('\n')
79         return exe
80
81 def _invalidWin32App(pywinerr):
82     """
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.
85
86     @param pywinerr: a pywintypes.error instance raised by CreateProcess
87
88     @return: a boolean
89     """
90
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.
94
95     return pywinerr.args[0] == 193
96
97 class Process(_pollingfile._PollingTimer, BaseProcess):
98     """A process that integrates with the Twisted event loop.
99
100     If your subprocess is a python program, you need to:
101
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
105
106      - If you don't want Windows messing with data passed over
107        stdin/out/err, set the pipes to be in binary mode::
108
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)
113
114     """
115     implements(IProcessTransport, IConsumer, IProducer)
116
117     closedNotifies = 0
118
119     def __init__(self, reactor, protocol, command, args, environment, path):
120         """
121         Create a new child process.
122         """
123         _pollingfile._PollingTimer.__init__(self, reactor)
124         BaseProcess.__init__(self, protocol)
125
126         # security attributes for pipes
127         sAttrs = win32security.SECURITY_ATTRIBUTES()
128         sAttrs.bInheritHandle = 1
129
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)
134
135         win32pipe.SetNamedPipeHandleState(self.hStdinW,
136                                           win32pipe.PIPE_NOWAIT,
137                                           None,
138                                           None)
139
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
146
147         # Create new handles whose inheritance property is false
148         currentPid = win32api.GetCurrentProcess()
149
150         tmp = win32api.DuplicateHandle(currentPid, self.hStdoutR, currentPid, 0, 0,
151                                        win32con.DUPLICATE_SAME_ACCESS)
152         win32file.CloseHandle(self.hStdoutR)
153         self.hStdoutR = tmp
154
155         tmp = win32api.DuplicateHandle(currentPid, self.hStderrR, currentPid, 0, 0,
156                                        win32con.DUPLICATE_SAME_ACCESS)
157         win32file.CloseHandle(self.hStderrR)
158         self.hStderrR = tmp
159
160         tmp = win32api.DuplicateHandle(currentPid, self.hStdinW, currentPid, 0, 0,
161                                        win32con.DUPLICATE_SAME_ACCESS)
162         win32file.CloseHandle(self.hStdinW)
163         self.hStdinW = tmp
164
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.
168
169         env = os.environ.copy()
170         env.update(environment or {})
171
172         cmdline = quoteArguments(args)
173         # TODO: error detection here.  See #2787 and #4184.
174         def doCreate():
175             self.hProcess, self.hThread, self.pid, dwTid = win32process.CreateProcess(
176                 command, cmdline, None, None, 1, 0, env, path, StartupInfo)
177         try:
178             try:
179                 doCreate()
180             except TypeError, e:
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',):
185                     raise
186                 newenv = {}
187                 for key, value in env.items():
188                     newenv[unicode(key)] = unicode(value)
189                 env = newenv
190                 doCreate()
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.
195                 raise OSError(pwte)
196             else:
197                 # look for a shebang line.  Insert the original 'command'
198                 # (actually a script) into the new arguments list.
199                 sheb = _findShebang(command)
200                 if sheb is None:
201                     raise OSError(
202                         "%r is neither a Windows executable, "
203                         "nor a script with a shebang line" % command)
204                 else:
205                     args = list(args)
206                     args.insert(0, command)
207                     cmdline = quoteArguments(args)
208                     origcmd = command
209                     command = sheb
210                     try:
211                         # Let's try again.
212                         doCreate()
213                     except pywintypes.error, pwte2:
214                         # d'oh, failed again!
215                         if _invalidWin32App(pwte2):
216                             raise OSError(
217                                 "%r has an invalid shebang line: "
218                                 "%r is not a valid executable" % (
219                                     origcmd, sheb))
220                         raise OSError(pwte2)
221
222         # close handles which only the child will use
223         win32file.CloseHandle(hStderrW)
224         win32file.CloseHandle(hStdoutW)
225         win32file.CloseHandle(hStdinR)
226
227         # set up everything
228         self.stdout = _pollingfile._PollableReadPipe(
229             self.hStdoutR,
230             lambda data: self.proto.childDataReceived(1, data),
231             self.outConnectionLost)
232
233         self.stderr = _pollingfile._PollableReadPipe(
234                 self.hStderrR,
235                 lambda data: self.proto.childDataReceived(2, data),
236                 self.errConnectionLost)
237
238         self.stdin = _pollingfile._PollableWritePipe(
239             self.hStdinW, self.inConnectionLost)
240
241         for pipewatcher in self.stdout, self.stderr, self.stdin:
242             self._addPollableResource(pipewatcher)
243
244
245         # notify protocol
246         self.proto.makeConnection(self)
247
248         self._addPollableResource(_Reaper(self))
249
250
251     def signalProcess(self, signalID):
252         if self.pid is None:
253             raise error.ProcessExitedAlready()
254         if signalID in ("INT", "TERM", "KILL"):
255             win32process.TerminateProcess(self.hProcess, 1)
256
257
258     def _getReason(self, status):
259         if status == 0:
260             return error.ProcessDone(status)
261         return error.ProcessTerminated(status)
262
263
264     def write(self, data):
265         """
266         Write data to the process' stdin.
267
268         @type data: C{str}
269         """
270         self.stdin.write(data)
271
272
273     def writeSequence(self, seq):
274         """
275         Write data to the process' stdin.
276
277         @type data: C{list} of C{str}
278         """
279         self.stdin.writeSequence(seq)
280
281
282     def writeToChild(self, fd, data):
283         """
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.
286
287         This implementation is limited to writing to the child's standard input.
288
289         @param fd: The file descriptor to which to write.  Only stdin (C{0}) is
290             supported.
291         @type fd: C{int}
292
293         @param data: The bytes to write.
294         @type data: C{str}
295
296         @return: C{None}
297
298         @raise KeyError: If C{fd} is anything other than the stdin file
299             descriptor (C{0}).
300         """
301         if fd == 0:
302             self.stdin.write(data)
303         else:
304             raise KeyError(fd)
305
306
307     def closeChildFD(self, fd):
308         if fd == 0:
309             self.closeStdin()
310         elif fd == 1:
311             self.closeStdout()
312         elif fd == 2:
313             self.closeStderr()
314         else:
315             raise NotImplementedError("Only standard-IO file descriptors available on win32")
316
317     def closeStdin(self):
318         """Close the process' stdin.
319         """
320         self.stdin.close()
321
322     def closeStderr(self):
323         self.stderr.close()
324
325     def closeStdout(self):
326         self.stdout.close()
327
328     def loseConnection(self):
329         """Close the process' stdout, in and err."""
330         self.closeStdin()
331         self.closeStdout()
332         self.closeStderr()
333
334
335     def outConnectionLost(self):
336         self.proto.childConnectionLost(1)
337         self.connectionLostNotify()
338
339
340     def errConnectionLost(self):
341         self.proto.childConnectionLost(2)
342         self.connectionLostNotify()
343
344
345     def inConnectionLost(self):
346         self.proto.childConnectionLost(0)
347         self.connectionLostNotify()
348
349
350     def connectionLostNotify(self):
351         """
352         Will be called 3 times, by stdout/err threads and process handle.
353         """
354         self.closedNotifies += 1
355         self.maybeCallProcessEnded()
356
357
358     def maybeCallProcessEnded(self):
359         if self.closedNotifies == 3 and self.lostProcess:
360             win32file.CloseHandle(self.hProcess)
361             win32file.CloseHandle(self.hThread)
362             self.hProcess = None
363             self.hThread = None
364             BaseProcess.maybeCallProcessEnded(self)
365
366
367     # IConsumer
368     def registerProducer(self, producer, streaming):
369         self.stdin.registerProducer(producer, streaming)
370
371     def unregisterProducer(self):
372         self.stdin.unregisterProducer()
373
374     # IProducer
375     def pauseProducing(self):
376         self._pause()
377
378     def resumeProducing(self):
379         self._unpause()
380
381     def stopProducing(self):
382         self.loseConnection()
383
384     def __repr__(self):
385         """
386         Return a string representation of the process.
387         """
388         return "<%s pid=%s>" % (self.__class__.__name__, self.pid)