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