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