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