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 _ParseSigsetT(sigset):
795 """Parse a rendered sigset_t value. 796 797 This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t 798 function. 799 800 @type sigset: string 801 @param sigset: Rendered signal set from /proc/$pid/status 802 @rtype: set 803 @return: Set of all enabled signal numbers 804 805 """ 806 result = set() 807 808 signum = 0 809 for ch in reversed(sigset): 810 chv = int(ch, 16) 811 812 # The following could be done in a loop, but it's easier to read and 813 # understand in the unrolled form 814 if chv & 1: 815 result.add(signum + 1) 816 if chv & 2: 817 result.add(signum + 2) 818 if chv & 4: 819 result.add(signum + 3) 820 if chv & 8: 821 result.add(signum + 4) 822 823 signum += 4 824 825 return result
826 827
828 -def _GetProcStatusField(pstatus, field):
829 """Retrieves a field from the contents of a proc status file. 830 831 @type pstatus: string 832 @param pstatus: Contents of /proc/$pid/status 833 @type field: string 834 @param field: Name of field whose value should be returned 835 @rtype: string 836 837 """ 838 for line in pstatus.splitlines(): 839 parts = line.split(":", 1) 840 841 if len(parts) < 2 or parts[0] != field: 842 continue 843 844 return parts[1].strip() 845 846 return None
847 848
849 -def IsProcessHandlingSignal(pid, signum, status_path=None):
850 """Checks whether a process is handling a signal. 851 852 @type pid: int 853 @param pid: Process ID 854 @type signum: int 855 @param signum: Signal number 856 @rtype: bool 857 858 """ 859 if status_path is None: 860 status_path = _GetProcStatusPath(pid) 861 862 try: 863 proc_status = utils_io.ReadFile(status_path) 864 except EnvironmentError, err: 865 # In at least one case, reading /proc/$pid/status failed with ESRCH. 866 if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH): 867 return False 868 raise 869 870 sigcgt = _GetProcStatusField(proc_status, "SigCgt") 871 if sigcgt is None: 872 raise RuntimeError("%s is missing 'SigCgt' field" % status_path) 873 874 # Now check whether signal is handled 875 return signum in _ParseSigsetT(sigcgt)
876 877
878 -def Daemonize(logfile):
879 """Daemonize the current process. 880 881 This detaches the current process from the controlling terminal and 882 runs it in the background as a daemon. 883 884 @type logfile: str 885 @param logfile: the logfile to which we should redirect stdout/stderr 886 @rtype: tuple; (int, callable) 887 @return: File descriptor of pipe(2) which must be closed to notify parent 888 process and a callable to reopen log files 889 890 """ 891 # pylint: disable=W0212 892 # yes, we really want os._exit 893 894 # TODO: do another attempt to merge Daemonize and StartDaemon, or at 895 # least abstract the pipe functionality between them 896 897 # Create pipe for sending error messages 898 (rpipe, wpipe) = os.pipe() 899 900 # this might fail 901 pid = os.fork() 902 if (pid == 0): # The first child. 903 SetupDaemonEnv() 904 905 # this might fail 906 pid = os.fork() # Fork a second child. 907 if (pid == 0): # The second child. 908 utils_wrapper.CloseFdNoError(rpipe) 909 else: 910 # exit() or _exit()? See below. 911 os._exit(0) # Exit parent (the first child) of the second child. 912 else: 913 utils_wrapper.CloseFdNoError(wpipe) 914 # Wait for daemon to be started (or an error message to 915 # arrive) and read up to 100 KB as an error message 916 errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024) 917 if errormsg: 918 sys.stderr.write("Error when starting daemon process: %r\n" % errormsg) 919 rcode = 1 920 else: 921 rcode = 0 922 os._exit(rcode) # Exit parent of the first child. 923 924 reopen_fn = compat.partial(SetupDaemonFDs, logfile, None) 925 926 # Open logs for the first time 927 reopen_fn() 928 929 return (wpipe, reopen_fn)
930 931
932 -def KillProcess(pid, signal_=signal.SIGTERM, timeout=30, 933 waitpid=False):
934 """Kill a process given by its pid. 935 936 @type pid: int 937 @param pid: The PID to terminate. 938 @type signal_: int 939 @param signal_: The signal to send, by default SIGTERM 940 @type timeout: int 941 @param timeout: The timeout after which, if the process is still alive, 942 a SIGKILL will be sent. If not positive, no such checking 943 will be done 944 @type waitpid: boolean 945 @param waitpid: If true, we should waitpid on this process after 946 sending signals, since it's our own child and otherwise it 947 would remain as zombie 948 949 """ 950 def _helper(pid, signal_, wait): 951 """Simple helper to encapsulate the kill/waitpid sequence""" 952 if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait: 953 try: 954 os.waitpid(pid, os.WNOHANG) 955 except OSError: 956 pass
957 958 if pid <= 0: 959 # kill with pid=0 == suicide 960 raise errors.ProgrammerError("Invalid pid given '%s'" % pid) 961 962 if not IsProcessAlive(pid): 963 return 964 965 _helper(pid, signal_, waitpid) 966 967 if timeout <= 0: 968 return 969 970 def _CheckProcess(): 971 if not IsProcessAlive(pid): 972 return 973 974 try: 975 (result_pid, _) = os.waitpid(pid, os.WNOHANG) 976 except OSError: 977 raise utils_retry.RetryAgain() 978 979 if result_pid > 0: 980 return 981 982 raise utils_retry.RetryAgain() 983 984 try: 985 # Wait up to $timeout seconds 986 utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout) 987 except utils_retry.RetryTimeout: 988 pass 989 990 if IsProcessAlive(pid): 991 # Kill process if it's still alive 992 _helper(pid, signal.SIGKILL, waitpid) 993 994
995 -def RunInSeparateProcess(fn, *args):
996 """Runs a function in a separate process. 997 998 Note: Only boolean return values are supported. 999 1000 @type fn: callable 1001 @param fn: Function to be called 1002 @rtype: bool 1003 @return: Function's result 1004 1005 """ 1006 pid = os.fork() 1007 if pid == 0: 1008 # Child process 1009 try: 1010 # In case the function uses temporary files 1011 utils_wrapper.ResetTempfileModule() 1012 1013 # Call function 1014 result = int(bool(fn(*args))) 1015 assert result in (0, 1) 1016 except: # pylint: disable=W0702 1017 logging.exception("Error while calling function in separate process") 1018 # 0 and 1 are reserved for the return value 1019 result = 33 1020 1021 os._exit(result) # pylint: disable=W0212 1022 1023 # Parent process 1024 1025 # Avoid zombies and check exit code 1026 (_, status) = os.waitpid(pid, 0) 1027 1028 if os.WIFSIGNALED(status): 1029 exitcode = None 1030 signum = os.WTERMSIG(status) 1031 else: 1032 exitcode = os.WEXITSTATUS(status) 1033 signum = None 1034 1035 if not (exitcode in (0, 1) and signum is None): 1036 raise errors.GenericError("Child program failed (code=%s, signal=%s)" % 1037 (exitcode, signum)) 1038 1039 return bool(exitcode)
1040 1041
1042 -def CloseFDs(noclose_fds=None):
1043 """Close file descriptors. 1044 1045 This closes all file descriptors above 2 (i.e. except 1046 stdin/out/err). 1047 1048 @type noclose_fds: list or None 1049 @param noclose_fds: if given, it denotes a list of file descriptor 1050 that should not be closed 1051 1052 """ 1053 # Default maximum for the number of available file descriptors. 1054 if 'SC_OPEN_MAX' in os.sysconf_names: 1055 try: 1056 MAXFD = os.sysconf('SC_OPEN_MAX') 1057 if MAXFD < 0: 1058 MAXFD = 1024 1059 except OSError: 1060 MAXFD = 1024 1061 else: 1062 MAXFD = 1024 1063 1064 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] 1065 if (maxfd == resource.RLIM_INFINITY): 1066 maxfd = MAXFD 1067 1068 # Iterate through and close all file descriptors (except the standard ones) 1069 for fd in range(3, maxfd): 1070 if noclose_fds and fd in noclose_fds: 1071 continue 1072 utils_wrapper.CloseFdNoError(fd)
1073