1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import os
19 import qm.common
20 import signal
21 import string
22 import sys
23 import time
24
25
26
27 if sys.platform == "win32":
28 import msvcrt
29 import pywintypes
30 from threading import *
31 import win32api
32 import win32con
33 import win32event
34 import win32file
35 import win32pipe
36 import win32process
37 else:
38 import cPickle
39 import fcntl
40 import select
41 import qm.sigmask
42
43
44
45
46
48 """An 'Executable' is a program that the operating system can run.
49
50 'Exectuable' (and classes derived from it) create child processes.
51 The 'Spawn' function creates child processes that execute
52 asynchronously. The 'Run' function creates child processes that
53 execute synchrounously, i.e,. the 'Run' function does not return
54 until the child process has completed its execution.
55
56 It is safe to reuse a particular 'Executable' instance (by calling
57 'Spawn' or 'Run' more than once), so long as the uses are not
58 interleaved."""
59
62 """Spawn the program.
63
64 'arguments' -- The sequence of arguments that should be passed
65 to the executable. The first argument provided in this
66 sequence will be 'argv[0]'; that is also the value used for
67 the path to the executable.
68
69 'environment' -- If not 'None', a dictionary giving the
70 environment that should be provided to the child.
71
72 'dir' -- If not 'None', the directory in which the child
73 should begin execution. If 'None', the child will execute in
74 the same directory as the parent.
75
76 'path' -- If not 'None', the path to the program to run. If
77 'None', 'arguments[0]' is used.
78
79 'exception_pipe' -- If not 'None', a pipe that the child can
80 use to communicate an exception to the parent. This pipe is
81 only used on UNIX systems. The write end of the pipe will be
82 closed by this function.
83
84 returns -- The PID of the child.
85
86 Before creating the child, the parent will call
87 'self._InitializeParent'. On UNIX systems, the child will
88 call 'self._InitializeChild' after 'fork', but before 'exec'.
89 On non-UNIX systems, 'self._InitializeChild' will never be
90 called.
91
92 After creating the child, 'self._HandleChild' is called in the
93 parent. This hook should be used to handle tasks that must be
94 performed after the child is running.
95
96 If the path to the program is absolute, or contains no
97 separator characters, it is not modified. Otherwise the path
98 to the program is relative, it is transformed into an absolute
99 path using 'dir' as the base, or the current directory if
100 'dir' is not set."""
101
102
103 self.__dir = dir
104
105
106
107 if not path:
108 path = arguments[0]
109
110
111
112
113 if os.path.isabs(path):
114
115 pass
116 elif (os.sep in path or (os.altsep and os.altsep in path)):
117
118 if dir:
119 path = os.path.normpath(os.path.join(dir, path))
120 if not os.path.isabs(path):
121 path = os.path.abspath(path)
122 else:
123 path = os.path.abspath(path)
124 else:
125
126
127
128 pass
129
130
131 startupinfo = self._InitializeParent()
132
133
134
135
136 self.__child = None
137
138 if sys.platform == "win32":
139
140
141
142 command_line = self.__CreateCommandLine(arguments)
143
144
145
146
147 if not os.path.isabs(path):
148 path = None
149
150
151
152
153
154
155
156
157 if environment is not None:
158
159 uses_unicode = 0
160 for (k, v) in environment.iteritems():
161 if (isinstance(k, unicode)
162 or isinstance(v, unicode)):
163 uses_unicode = 1
164 break
165
166
167 if uses_unicode:
168 new_environment = {}
169 for (k, v) in environment.iteritems():
170 new_environment[unicode(k)] = unicode(v)
171 environment = new_environment
172
173
174 self.__child \
175 = win32process.CreateProcess(path,
176 command_line,
177 None,
178 None,
179 1,
180 0,
181 environment,
182 self.__dir,
183 startupinfo)[0]
184 else:
185
186 self.__child = os.fork()
187
188 if self.__child == 0:
189 try:
190
191 if exception_pipe:
192 os.close(exception_pipe[0])
193
194 self._InitializeChild()
195
196 if environment:
197 os.execvpe(path, arguments, environment)
198 else:
199 os.execvp(path, arguments)
200 except:
201 if exception_pipe:
202
203 exc_info = sys.exc_info()
204
205
206
207 cPickle.dump(exc_info[:2],
208 os.fdopen(exception_pipe[1], "w"),
209 1)
210
211 os._exit(1)
212
213
214 assert None
215
216
217 if exception_pipe:
218 os.close(exception_pipe[1])
219
220
221
222 self._HandleChild()
223
224 return self.__child
225
226
227 - def Run(self, arguments=[], environment = None, dir = None,
228 path = None):
229 """Spawn the program and wait for it to finish.
230
231 'arguments' -- The sequence of arguments that should be passed
232 to the executable. The first argument provided in this
233 sequence will be 'argv[0]'.
234
235 'environment' -- If not 'None', a dictionary giving the
236 environment that should be provided to the child. If 'None',
237 the child will inherit the parents environment.
238
239 'dir' -- If not 'None', the directory in which the child
240 should begin execution. If 'None', the child will execute in
241 the same directory as the parent.
242
243 'path' -- If not 'None', the path to the program to run. If
244 'None', 'arguments[0]' is used.
245
246 returns -- The status returned by the program. Under UNIX,
247 this is the value returned by 'waitpid'; under Windows, it is
248 the value returned by 'GetExitCodeProcess'.
249
250 After invoking 'Spawn', this function invokes '_DoParent' to
251 allow the parent process to perform whatever actions are
252 required. After that function returns, the parent waits for
253 the child process to exit."""
254
255
256
257
258
259
260
261 if sys.platform != "win32":
262 exception_pipe = os.pipe()
263
264
265 qm.common.close_file_on_exec(exception_pipe[1])
266 else:
267 exception_pipe = None
268
269
270 child = self.Spawn(arguments, environment, dir, path, exception_pipe)
271
272
273 self._DoParent()
274
275
276 if sys.platform == "win32":
277 win32event.WaitForSingleObject(child, win32event.INFINITE)
278
279 return win32process.GetExitCodeProcess(child)
280 else:
281 status = os.waitpid(child, 0)[1]
282
283 data = os.fdopen(exception_pipe[0]).read()
284
285
286 if data:
287
288 exc_info = cPickle.loads(data)
289
290 raise exc_info[0], exc_info[1]
291
292 return status
293
294
296 """Initialize the parent process.
297
298 Before spawning the child, this method is invoked to give the
299 parent a chance to initialize itself.
300
301 returns -- Under Windows, a 'PySTARTUPINFO' structure
302 explaining how the child should be initialized. On other
303 systems, the return value is ignored."""
304
305 if sys.platform == "win32":
306 return win32process.STARTUPINFO()
307
308
310 """Kill the child process.
311
312 The child process is killed in a way that does not permit an
313 orderly shutdown. In other words, 'SIGKILL' is used under
314 UNIX, not 'SIGTERM'. On Windows, 'TerminateProcess' is used,
315 and the exit code from the child process will be '1'."""
316
317 if sys.platform == "win32":
318 win32process.TerminateProcess(self._GetChildPID(), 1)
319 else:
320 os.kill(self._GetChildPID(), signal.SIGKILL)
321
322
324 """Run in the parent process after the child has been created.
325
326 The child process has been spawned; its PID is avialable via
327 '_GetChildPID'. Take any actions in the parent that are
328 required now that the child exists.
329
330 Derived class versions must call this method."""
331
332 pass
333
334
336 """Initialize the child process.
337
338 After 'fork' is called this method is invoked to give the
339 child a chance to initialize itself. '_InitializeParent' will
340 already have been called in the parent process.
341
342 This method is not used under Windows."""
343
344 assert sys.platform != "win32"
345
346
347
348
349
350
351
352
353
354 qm.sigmask.restore_mask()
355
356 if self.__dir:
357 os.chdir(self.__dir)
358
359
361 """Perform actions required in the parent after 'Spawn'."""
362
363 pass
364
365
367 """Return the process ID for the child process.
368
369 returns -- The process ID for the child process. (On Windows,
370 the value returned is the process handle.) Returns 'None' if
371 the child has not yet been created, or if something went awry
372 when creating it. For example, if 'os.fork' throws an
373 exception, this value will return 'None'."""
374
375 return self.__child
376
377
379 """Return a string giving the process command line.
380
381 arguments -- A sequence of arguments (including argv[0])
382 indicating the command to be run.
383
384 returns -- A string that could be provided to the shell in
385 order to run the command."""
386
387 command = ""
388 need_space = 0
389 for a in arguments:
390
391 if need_space:
392 command += " "
393 else:
394 need_space = 1
395
396
397
398 if not a:
399 command += '""'
400 continue
401 whitespace = 0
402 for c in string.whitespace:
403 if c in a:
404 whitespace = 1
405 break
406 if whitespace:
407 command += '"' + a + '"'
408 else:
409 command += a
410
411 return command
412
413
414
416 """A 'TimeoutExecutable' runs for a limited time.
417
418 If the timer expires, the child process is killed and
419 self.timedout is set to 1. Otherwise, self.timedout is set to 0.
420
421 In order to implement this functionality under UNIX, the child
422 process is placed into its own process group. An additional
423 monitoring process is created whose sole job is to kill the
424 primary child's process group if the timeout expires. Process
425 groups are used so that if the child process spawns additional
426 processes they are killed too. A separate monitoring process is
427 used so as not to block the parent.
428
429 Under Windows, a monitoring thread is created. When the timer
430 expires, the child process is terminated. However, the child
431 process is not placed into a separate process group, so
432 granchildren kare not terminated. In the future, when Python
433 provides access to 'CreateJobObject' and related functions, jobs
434 will be used to provide functionality similar to UNIX process
435 groups.
436
437 The 'Run' method will automatically start the monitoring process.
438 The 'Spawn' method does not start the monitoring process. User's
439 of 'Spawn' should invoke '_DoParent' in order to start the
440 monitoring process. Derived class '_DoParent' functions should
441 call the version defined in this class."""
442
444 """Construct a new 'TimeoutExecutable'.
445
446 'timeout' -- The number of seconds that the child is permitted
447 to run. This value may be a floating-point value. However,
448 the value may be rounded to an integral value on some systems.
449 Once the timeout expires, the child and its entire process
450 group is killed. (The processes in the process group are sent
451 the 'SIGKILL' signal.) If the 'timeout' is -2, the child is
452 allowed to run forever, but when it terminates the child's
453 process group is killed.
454
455 If the 'timeout' is -1, this class behaves exactly like
456 'Executable'."""
457
458 super(TimeoutExecutable, self).__init__()
459 self.__timeout = float(timeout)
460
461
472
473
475
476 super(TimeoutExecutable, self)._HandleChild()
477
478 if self.__UseSeparateProcessGroupForChild():
479
480
481
482
483 child_pid = self._GetChildPID()
484 try:
485 os.setpgid(child_pid, child_pid)
486 except:
487
488
489
490
491 pass
492
493
494
495
496
497
498
499
500
501
502 self.__monitor_pid = os.fork()
503 if self.__monitor_pid != 0:
504
505
506
507
508 os.setpgid(self.__monitor_pid, child_pid)
509 else:
510
511
512
513
514 os.setpgid(0, child_pid)
515
516
517
518
519
520
521
522 try:
523 max_fds = os.sysconf("SC_OPEN_MAX")
524 except:
525 max_fds = 256
526 for fd in xrange(max_fds):
527 try:
528 os.close(fd)
529 except:
530 pass
531
532 try:
533 if self.__timeout >= 0:
534
535 time.sleep (self.__timeout)
536
537 os.kill(0, signal.SIGKILL)
538 else:
539
540 select.select ([], [], [])
541 finally:
542
543
544 os._exit(0)
545 elif self.__timeout >= 0 and sys.platform == "win32":
546
547 self.__monitor_thread = Thread(target = self.__Monitor)
548 self.__monitor_thread.start()
549
550
551 - def Run(self, arguments=[], environment = None, dir = None,
552 path = None):
579
580
582 """Returns true if the child wil be placed in its own process group.
583
584 returns -- True if the child will be placed in its own process
585 group. In that case, a separate monitoring process will also
586 be created."""
587
588 if sys.platform == "win32":
589
590
591
592
593 return 0
594
595 return self.__timeout >= 0 or self.__timeout == -2
596
597
598 if sys.platform == "win32":
599
600 def __Monitor(self):
601 """Kill the child if the timeout expires.
602
603 This function is run in the monitoring thread."""
604
605
606
607
608 timeout = int(self.__timeout * 1000)
609
610
611 result = win32event.WaitForSingleObject(self._GetChildPID(),
612 timeout)
613
614 if result == win32con.WAIT_TIMEOUT:
615 self.Kill()