Package ganeti :: Package utils :: Module process
[hide private]
[frames] | no frames]

Source Code for Module ganeti.utils.process

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