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