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