1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Utility functions for processes.
22
23 """
24
25
26 import os
27 import sys
28 import subprocess
29 import errno
30 import select
31 import logging
32 import signal
33 import resource
34
35 from cStringIO import StringIO
36
37 from ganeti import errors
38 from ganeti import constants
39 from ganeti import compat
40
41 from ganeti.utils import retry as utils_retry
42 from ganeti.utils import wrapper as utils_wrapper
43 from ganeti.utils import text as utils_text
44 from ganeti.utils import io as utils_io
45 from ganeti.utils import algo as utils_algo
46
47
48
49 _no_fork = False
50
51 (_TIMEOUT_NONE,
52 _TIMEOUT_TERM,
53 _TIMEOUT_KILL) = range(3)
54
55
57 """Disables the use of fork(2).
58
59 """
60 global _no_fork
61
62 _no_fork = True
63
64
66 """Holds the result of running external programs.
67
68 @type exit_code: int
69 @ivar exit_code: the exit code of the program, or None (if the program
70 didn't exit())
71 @type signal: int or None
72 @ivar signal: the signal that caused the program to finish, or None
73 (if the program wasn't terminated by a signal)
74 @type stdout: str
75 @ivar stdout: the standard output of the program
76 @type stderr: str
77 @ivar stderr: the standard error of the program
78 @type failed: boolean
79 @ivar failed: True in case the program was
80 terminated by a signal or exited with a non-zero exit code
81 @ivar fail_reason: a string detailing the termination reason
82
83 """
84 __slots__ = ["exit_code", "signal", "stdout", "stderr",
85 "failed", "fail_reason", "cmd"]
86
87 - def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
88 timeout):
89 self.cmd = cmd
90 self.exit_code = exit_code
91 self.signal = signal_
92 self.stdout = stdout
93 self.stderr = stderr
94 self.failed = (signal_ is not None or exit_code != 0)
95
96 fail_msgs = []
97 if self.signal is not None:
98 fail_msgs.append("terminated by signal %s" % self.signal)
99 elif self.exit_code is not None:
100 fail_msgs.append("exited with exit code %s" % self.exit_code)
101 else:
102 fail_msgs.append("unable to determine termination reason")
103
104 if timeout_action == _TIMEOUT_TERM:
105 fail_msgs.append("terminated after timeout of %.2f seconds" % timeout)
106 elif timeout_action == _TIMEOUT_KILL:
107 fail_msgs.append(("force termination after timeout of %.2f seconds"
108 " and linger for another %.2f seconds") %
109 (timeout, constants.CHILD_LINGER_TIMEOUT))
110
111 if fail_msgs and self.failed:
112 self.fail_reason = utils_text.CommaJoin(fail_msgs)
113
114 if self.failed:
115 logging.debug("Command '%s' failed (%s); output: %s",
116 self.cmd, self.fail_reason, self.output)
117
119 """Returns the combined stdout and stderr for easier usage.
120
121 """
122 return self.stdout + self.stderr
123
124 output = property(_GetOutput, None, None, "Return full output")
125
126
128 """Builds the environment for an external program.
129
130 """
131 if reset:
132 cmd_env = {}
133 else:
134 cmd_env = os.environ.copy()
135 cmd_env["LC_ALL"] = "C"
136
137 if env is not None:
138 cmd_env.update(env)
139
140 return cmd_env
141
142
143 -def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
144 interactive=False, timeout=None, noclose_fds=None,
145 _postfork_fn=None):
146 """Execute a (shell) command.
147
148 The command should not read from its standard input, as it will be
149 closed.
150
151 @type cmd: string or list
152 @param cmd: Command to run
153 @type env: dict
154 @param env: Additional environment variables
155 @type output: str
156 @param output: if desired, the output of the command can be
157 saved in a file instead of the RunResult instance; this
158 parameter denotes the file name (if not None)
159 @type cwd: string
160 @param cwd: if specified, will be used as the working
161 directory for the command; the default will be /
162 @type reset_env: boolean
163 @param reset_env: whether to reset or keep the default os environment
164 @type interactive: boolean
165 @param interactive: whether we pipe stdin, stdout and stderr
166 (default behaviour) or run the command interactive
167 @type timeout: int
168 @param timeout: If not None, timeout in seconds until child process gets
169 killed
170 @type noclose_fds: list
171 @param noclose_fds: list of additional (fd >=3) file descriptors to leave
172 open for the child process
173 @param _postfork_fn: Callback run after fork but before timeout (unittest)
174 @rtype: L{RunResult}
175 @return: RunResult instance
176 @raise errors.ProgrammerError: if we call this when forks are disabled
177
178 """
179 if _no_fork:
180 raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
181
182 if output and interactive:
183 raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
184 " not be provided at the same time")
185
186 if isinstance(cmd, basestring):
187 strcmd = cmd
188 shell = True
189 else:
190 cmd = [str(val) for val in cmd]
191 strcmd = utils_text.ShellQuoteArgs(cmd)
192 shell = False
193
194 if output:
195 logging.debug("RunCmd %s, output file '%s'", strcmd, output)
196 else:
197 logging.debug("RunCmd %s", strcmd)
198
199 cmd_env = _BuildCmdEnvironment(env, reset_env)
200
201 try:
202 if output is None:
203 out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
204 interactive, timeout,
205 noclose_fds,
206 _postfork_fn=_postfork_fn)
207 else:
208 assert _postfork_fn is None, \
209 "_postfork_fn not supported if output provided"
210 timeout_action = _TIMEOUT_NONE
211 status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds)
212 out = err = ""
213 except OSError, err:
214 if err.errno == errno.ENOENT:
215 raise errors.OpExecError("Can't execute '%s': not found (%s)" %
216 (strcmd, err))
217 else:
218 raise
219
220 if status >= 0:
221 exitcode = status
222 signal_ = None
223 else:
224 exitcode = None
225 signal_ = -status
226
227 return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
228
229
231 """Setup a daemon's environment.
232
233 This should be called between the first and second fork, due to
234 setsid usage.
235
236 @param cwd: the directory to which to chdir
237 @param umask: the umask to setup
238
239 """
240 os.chdir(cwd)
241 os.umask(umask)
242 os.setsid()
243
244
246 """Setups up a daemon's file descriptors.
247
248 @param output_file: if not None, the file to which to redirect
249 stdout/stderr
250 @param output_fd: if not None, the file descriptor for stdout/stderr
251
252 """
253
254 assert [output_file, output_fd].count(None) >= 1
255
256
257 devnull_fd = os.open(os.devnull, os.O_RDONLY)
258
259 output_close = True
260
261 if output_fd is not None:
262 output_close = False
263 elif output_file is not None:
264
265 try:
266 output_fd = os.open(output_file,
267 os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
268 except EnvironmentError, err:
269 raise Exception("Opening output file failed: %s" % err)
270 else:
271 output_fd = os.open(os.devnull, os.O_WRONLY)
272
273
274 os.dup2(devnull_fd, 0)
275 os.dup2(output_fd, 1)
276 os.dup2(output_fd, 2)
277
278 if devnull_fd > 2:
279 utils_wrapper.CloseFdNoError(devnull_fd)
280
281 if output_close and output_fd > 2:
282 utils_wrapper.CloseFdNoError(output_fd)
283
284
285 -def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
286 pidfile=None):
287 """Start a daemon process after forking twice.
288
289 @type cmd: string or list
290 @param cmd: Command to run
291 @type env: dict
292 @param env: Additional environment variables
293 @type cwd: string
294 @param cwd: Working directory for the program
295 @type output: string
296 @param output: Path to file in which to save the output
297 @type output_fd: int
298 @param output_fd: File descriptor for output
299 @type pidfile: string
300 @param pidfile: Process ID file
301 @rtype: int
302 @return: Daemon process ID
303 @raise errors.ProgrammerError: if we call this when forks are disabled
304
305 """
306 if _no_fork:
307 raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
308 " disabled")
309
310 if output and not (bool(output) ^ (output_fd is not None)):
311 raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
312 " specified")
313
314 if isinstance(cmd, basestring):
315 cmd = ["/bin/sh", "-c", cmd]
316
317 strcmd = utils_text.ShellQuoteArgs(cmd)
318
319 if output:
320 logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
321 else:
322 logging.debug("StartDaemon %s", strcmd)
323
324 cmd_env = _BuildCmdEnvironment(env, False)
325
326
327 (pidpipe_read, pidpipe_write) = os.pipe()
328 try:
329 try:
330
331 (errpipe_read, errpipe_write) = os.pipe()
332 try:
333 try:
334
335 pid = os.fork()
336 if pid == 0:
337 try:
338
339 _StartDaemonChild(errpipe_read, errpipe_write,
340 pidpipe_read, pidpipe_write,
341 cmd, cmd_env, cwd,
342 output, output_fd, pidfile)
343 finally:
344
345 os._exit(1)
346 finally:
347 utils_wrapper.CloseFdNoError(errpipe_write)
348
349
350
351 errormsg = utils_wrapper.RetryOnSignal(os.read, errpipe_read,
352 100 * 1024)
353 finally:
354 utils_wrapper.CloseFdNoError(errpipe_read)
355 finally:
356 utils_wrapper.CloseFdNoError(pidpipe_write)
357
358
359 pidtext = utils_wrapper.RetryOnSignal(os.read, pidpipe_read, 128)
360 finally:
361 utils_wrapper.CloseFdNoError(pidpipe_read)
362
363
364 try:
365 os.waitpid(pid, 0)
366 except OSError:
367 pass
368
369 if errormsg:
370 raise errors.OpExecError("Error when starting daemon process: %r" %
371 errormsg)
372
373 try:
374 return int(pidtext)
375 except (ValueError, TypeError), err:
376 raise errors.OpExecError("Error while trying to parse PID %r: %s" %
377 (pidtext, err))
378
379
380 -def _StartDaemonChild(errpipe_read, errpipe_write,
381 pidpipe_read, pidpipe_write,
382 args, env, cwd,
383 output, fd_output, pidfile):
384 """Child process for starting daemon.
385
386 """
387 try:
388
389 utils_wrapper.CloseFdNoError(errpipe_read)
390 utils_wrapper.CloseFdNoError(pidpipe_read)
391
392
393 SetupDaemonEnv()
394
395
396 pid = os.fork()
397 if pid != 0:
398
399 os._exit(0)
400
401
402
403 utils_wrapper.SetCloseOnExecFlag(errpipe_write, True)
404
405
406 noclose_fds = [errpipe_write]
407
408
409 if pidfile:
410 fd_pidfile = utils_io.WritePidFile(pidfile)
411
412
413 noclose_fds.append(fd_pidfile)
414
415 utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
416 else:
417 fd_pidfile = None
418
419 SetupDaemonFDs(output, fd_output)
420
421
422 utils_wrapper.RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
423
424
425 CloseFDs(noclose_fds=noclose_fds)
426
427
428 os.chdir(cwd)
429
430 if env is None:
431 os.execvp(args[0], args)
432 else:
433 os.execvpe(args[0], args, env)
434 except:
435 try:
436
437 WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
438 except:
439
440 pass
441
442 os._exit(1)
443
444
446 """Possibly write an error message to a fd.
447
448 @type fd: None or int (file descriptor)
449 @param fd: if not None, the error will be written to this fd
450 @param err: string, the error message
451
452 """
453 if fd is None:
454 return
455
456 if not err:
457 err = "<unknown error>"
458
459 utils_wrapper.RetryOnSignal(os.write, fd, err)
460
461
463 """Raises L{utils_retry.RetryAgain} if child is still alive.
464
465 @raises utils_retry.RetryAgain: If child is still alive
466
467 """
468 if child.poll() is None:
469 raise utils_retry.RetryAgain()
470
471
473 """Waits for the child to terminate or until we reach timeout.
474
475 """
476 try:
477 utils_retry.Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout),
478 args=[child])
479 except utils_retry.RetryTimeout:
480 pass
481
482
486 """Run a command and return its output.
487
488 @type cmd: string or list
489 @param cmd: Command to run
490 @type env: dict
491 @param env: The environment to use
492 @type via_shell: bool
493 @param via_shell: if we should run via the shell
494 @type cwd: string
495 @param cwd: the working directory for the program
496 @type interactive: boolean
497 @param interactive: Run command interactive (without piping)
498 @type timeout: int
499 @param timeout: Timeout after the programm gets terminated
500 @type noclose_fds: list
501 @param noclose_fds: list of additional (fd >=3) file descriptors to leave
502 open for the child process
503 @param _postfork_fn: Function run after fork but before timeout (unittest)
504 @rtype: tuple
505 @return: (out, err, status)
506
507 """
508 poller = select.poll()
509
510 stderr = subprocess.PIPE
511 stdout = subprocess.PIPE
512 stdin = subprocess.PIPE
513
514 if interactive:
515 stderr = stdout = stdin = None
516
517 if noclose_fds:
518 preexec_fn = lambda: CloseFDs(noclose_fds)
519 close_fds = False
520 else:
521 preexec_fn = None
522 close_fds = True
523
524 child = subprocess.Popen(cmd, shell=via_shell,
525 stderr=stderr,
526 stdout=stdout,
527 stdin=stdin,
528 close_fds=close_fds, env=env,
529 cwd=cwd,
530 preexec_fn=preexec_fn)
531
532 if _postfork_fn:
533 _postfork_fn(child.pid)
534
535 out = StringIO()
536 err = StringIO()
537
538 linger_timeout = None
539
540 if timeout is None:
541 poll_timeout = None
542 else:
543 poll_timeout = utils_algo.RunningTimeout(timeout, True).Remaining
544
545 msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
546 (cmd, child.pid))
547 msg_linger = ("Command %s (%d) run into linger timeout, killing" %
548 (cmd, child.pid))
549
550 timeout_action = _TIMEOUT_NONE
551
552 if not interactive:
553 child.stdin.close()
554 poller.register(child.stdout, select.POLLIN)
555 poller.register(child.stderr, select.POLLIN)
556 fdmap = {
557 child.stdout.fileno(): (out, child.stdout),
558 child.stderr.fileno(): (err, child.stderr),
559 }
560 for fd in fdmap:
561 utils_wrapper.SetNonblockFlag(fd, True)
562
563 while fdmap:
564 if poll_timeout:
565 pt = poll_timeout() * 1000
566 if pt < 0:
567 if linger_timeout is None:
568 logging.warning(msg_timeout)
569 if child.poll() is None:
570 timeout_action = _TIMEOUT_TERM
571 utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid,
572 signal.SIGTERM)
573 linger_timeout = \
574 utils_algo.RunningTimeout(_linger_timeout, True).Remaining
575 pt = linger_timeout() * 1000
576 if pt < 0:
577 break
578 else:
579 pt = None
580
581 pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
582
583 for fd, event in pollresult:
584 if event & select.POLLIN or event & select.POLLPRI:
585 data = fdmap[fd][1].read()
586
587 if not data:
588 poller.unregister(fd)
589 del fdmap[fd]
590 continue
591 fdmap[fd][0].write(data)
592 if (event & select.POLLNVAL or event & select.POLLHUP or
593 event & select.POLLERR):
594 poller.unregister(fd)
595 del fdmap[fd]
596
597 if timeout is not None:
598 assert callable(poll_timeout)
599
600
601 if child.poll() is None:
602 _WaitForProcess(child, poll_timeout())
603
604
605 if child.poll() is None:
606 if linger_timeout is None:
607 logging.warning(msg_timeout)
608 timeout_action = _TIMEOUT_TERM
609 utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
610 lt = _linger_timeout
611 else:
612 lt = linger_timeout()
613 _WaitForProcess(child, lt)
614
615
616 if child.poll() is None:
617 timeout_action = _TIMEOUT_KILL
618 logging.warning(msg_linger)
619 utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
620
621 out = out.getvalue()
622 err = err.getvalue()
623
624 status = child.wait()
625 return out, err, status, timeout_action
626
627
628 -def _RunCmdFile(cmd, env, via_shell, output, cwd, noclose_fds):
629 """Run a command and save its output to a file.
630
631 @type cmd: string or list
632 @param cmd: Command to run
633 @type env: dict
634 @param env: The environment to use
635 @type via_shell: bool
636 @param via_shell: if we should run via the shell
637 @type output: str
638 @param output: the filename in which to save the output
639 @type cwd: string
640 @param cwd: the working directory for the program
641 @type noclose_fds: list
642 @param noclose_fds: list of additional (fd >=3) file descriptors to leave
643 open for the child process
644 @rtype: int
645 @return: the exit status
646
647 """
648 fh = open(output, "a")
649
650 if noclose_fds:
651 preexec_fn = lambda: CloseFDs(noclose_fds + [fh.fileno()])
652 close_fds = False
653 else:
654 preexec_fn = None
655 close_fds = True
656
657 try:
658 child = subprocess.Popen(cmd, shell=via_shell,
659 stderr=subprocess.STDOUT,
660 stdout=fh,
661 stdin=subprocess.PIPE,
662 close_fds=close_fds, env=env,
663 cwd=cwd,
664 preexec_fn=preexec_fn)
665
666 child.stdin.close()
667 status = child.wait()
668 finally:
669 fh.close()
670 return status
671
672
673 -def RunParts(dir_name, env=None, reset_env=False):
674 """Run Scripts or programs in a directory
675
676 @type dir_name: string
677 @param dir_name: absolute path to a directory
678 @type env: dict
679 @param env: The environment to use
680 @type reset_env: boolean
681 @param reset_env: whether to reset or keep the default os environment
682 @rtype: list of tuples
683 @return: list of (name, (one of RUNDIR_STATUS), RunResult)
684
685 """
686 rr = []
687
688 try:
689 dir_contents = utils_io.ListVisibleFiles(dir_name)
690 except OSError, err:
691 logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
692 return rr
693
694 for relname in sorted(dir_contents):
695 fname = utils_io.PathJoin(dir_name, relname)
696 if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
697 constants.EXT_PLUGIN_MASK.match(relname) is not None):
698 rr.append((relname, constants.RUNPARTS_SKIP, None))
699 else:
700 try:
701 result = RunCmd([fname], env=env, reset_env=reset_env)
702 except Exception, err:
703 rr.append((relname, constants.RUNPARTS_ERR, str(err)))
704 else:
705 rr.append((relname, constants.RUNPARTS_RUN, result))
706
707 return rr
708
709
711 """Returns the path for a PID's proc status file.
712
713 @type pid: int
714 @param pid: Process ID
715 @rtype: string
716
717 """
718 return "/proc/%d/status" % pid
719
720
722 """Check if a given pid exists on the system.
723
724 @note: zombie status is not handled, so zombie processes
725 will be returned as alive
726 @type pid: int
727 @param pid: the process ID to check
728 @rtype: boolean
729 @return: True if the process exists
730
731 """
732 def _TryStat(name):
733 try:
734 os.stat(name)
735 return True
736 except EnvironmentError, err:
737 if err.errno in (errno.ENOENT, errno.ENOTDIR):
738 return False
739 elif err.errno == errno.EINVAL:
740 raise utils_retry.RetryAgain(err)
741 raise
742
743 assert isinstance(pid, int), "pid must be an integer"
744 if pid <= 0:
745 return False
746
747
748
749 try:
750 return utils_retry.Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
751 args=[_GetProcStatusPath(pid)])
752 except utils_retry.RetryTimeout, err:
753 err.RaiseInner()
754
755
757 """Parse a rendered sigset_t value.
758
759 This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
760 function.
761
762 @type sigset: string
763 @param sigset: Rendered signal set from /proc/$pid/status
764 @rtype: set
765 @return: Set of all enabled signal numbers
766
767 """
768 result = set()
769
770 signum = 0
771 for ch in reversed(sigset):
772 chv = int(ch, 16)
773
774
775
776 if chv & 1:
777 result.add(signum + 1)
778 if chv & 2:
779 result.add(signum + 2)
780 if chv & 4:
781 result.add(signum + 3)
782 if chv & 8:
783 result.add(signum + 4)
784
785 signum += 4
786
787 return result
788
789
791 """Retrieves a field from the contents of a proc status file.
792
793 @type pstatus: string
794 @param pstatus: Contents of /proc/$pid/status
795 @type field: string
796 @param field: Name of field whose value should be returned
797 @rtype: string
798
799 """
800 for line in pstatus.splitlines():
801 parts = line.split(":", 1)
802
803 if len(parts) < 2 or parts[0] != field:
804 continue
805
806 return parts[1].strip()
807
808 return None
809
810
812 """Checks whether a process is handling a signal.
813
814 @type pid: int
815 @param pid: Process ID
816 @type signum: int
817 @param signum: Signal number
818 @rtype: bool
819
820 """
821 if status_path is None:
822 status_path = _GetProcStatusPath(pid)
823
824 try:
825 proc_status = utils_io.ReadFile(status_path)
826 except EnvironmentError, err:
827
828 if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
829 return False
830 raise
831
832 sigcgt = _GetProcStatusField(proc_status, "SigCgt")
833 if sigcgt is None:
834 raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
835
836
837 return signum in _ParseSigsetT(sigcgt)
838
839
841 """Daemonize the current process.
842
843 This detaches the current process from the controlling terminal and
844 runs it in the background as a daemon.
845
846 @type logfile: str
847 @param logfile: the logfile to which we should redirect stdout/stderr
848 @rtype: tuple; (int, callable)
849 @return: File descriptor of pipe(2) which must be closed to notify parent
850 process and a callable to reopen log files
851
852 """
853
854
855
856
857
858
859
860 (rpipe, wpipe) = os.pipe()
861
862
863 pid = os.fork()
864 if (pid == 0):
865 SetupDaemonEnv()
866
867
868 pid = os.fork()
869 if (pid == 0):
870 utils_wrapper.CloseFdNoError(rpipe)
871 else:
872
873 os._exit(0)
874 else:
875 utils_wrapper.CloseFdNoError(wpipe)
876
877
878 errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024)
879 if errormsg:
880 sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
881 rcode = 1
882 else:
883 rcode = 0
884 os._exit(rcode)
885
886 reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
887
888
889 reopen_fn()
890
891 return (wpipe, reopen_fn)
892
893
896 """Kill a process given by its pid.
897
898 @type pid: int
899 @param pid: The PID to terminate.
900 @type signal_: int
901 @param signal_: The signal to send, by default SIGTERM
902 @type timeout: int
903 @param timeout: The timeout after which, if the process is still alive,
904 a SIGKILL will be sent. If not positive, no such checking
905 will be done
906 @type waitpid: boolean
907 @param waitpid: If true, we should waitpid on this process after
908 sending signals, since it's our own child and otherwise it
909 would remain as zombie
910
911 """
912 def _helper(pid, signal_, wait):
913 """Simple helper to encapsulate the kill/waitpid sequence"""
914 if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
915 try:
916 os.waitpid(pid, os.WNOHANG)
917 except OSError:
918 pass
919
920 if pid <= 0:
921
922 raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
923
924 if not IsProcessAlive(pid):
925 return
926
927 _helper(pid, signal_, waitpid)
928
929 if timeout <= 0:
930 return
931
932 def _CheckProcess():
933 if not IsProcessAlive(pid):
934 return
935
936 try:
937 (result_pid, _) = os.waitpid(pid, os.WNOHANG)
938 except OSError:
939 raise utils_retry.RetryAgain()
940
941 if result_pid > 0:
942 return
943
944 raise utils_retry.RetryAgain()
945
946 try:
947
948 utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
949 except utils_retry.RetryTimeout:
950 pass
951
952 if IsProcessAlive(pid):
953
954 _helper(pid, signal.SIGKILL, waitpid)
955
956
958 """Runs a function in a separate process.
959
960 Note: Only boolean return values are supported.
961
962 @type fn: callable
963 @param fn: Function to be called
964 @rtype: bool
965 @return: Function's result
966
967 """
968 pid = os.fork()
969 if pid == 0:
970
971 try:
972
973 utils_wrapper.ResetTempfileModule()
974
975
976 result = int(bool(fn(*args)))
977 assert result in (0, 1)
978 except:
979 logging.exception("Error while calling function in separate process")
980
981 result = 33
982
983 os._exit(result)
984
985
986
987
988 (_, status) = os.waitpid(pid, 0)
989
990 if os.WIFSIGNALED(status):
991 exitcode = None
992 signum = os.WTERMSIG(status)
993 else:
994 exitcode = os.WEXITSTATUS(status)
995 signum = None
996
997 if not (exitcode in (0, 1) and signum is None):
998 raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
999 (exitcode, signum))
1000
1001 return bool(exitcode)
1002
1003
1005 """Close file descriptors.
1006
1007 This closes all file descriptors above 2 (i.e. except
1008 stdin/out/err).
1009
1010 @type noclose_fds: list or None
1011 @param noclose_fds: if given, it denotes a list of file descriptor
1012 that should not be closed
1013
1014 """
1015
1016 if 'SC_OPEN_MAX' in os.sysconf_names:
1017 try:
1018 MAXFD = os.sysconf('SC_OPEN_MAX')
1019 if MAXFD < 0:
1020 MAXFD = 1024
1021 except OSError:
1022 MAXFD = 1024
1023 else:
1024 MAXFD = 1024
1025
1026 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1027 if (maxfd == resource.RLIM_INFINITY):
1028 maxfd = MAXFD
1029
1030
1031 for fd in range(3, maxfd):
1032 if noclose_fds and fd in noclose_fds:
1033 continue
1034 utils_wrapper.CloseFdNoError(fd)
1035