Statistics
| Branch: | Tag: | Revision:

root / lib / utils / process.py @ a4ccecf6

History | View | Annotate | Download (26.9 kB)

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

    
40
from ganeti.utils import retry as utils_retry
41
from ganeti.utils import wrapper as utils_wrapper
42
from ganeti.utils import text as utils_text
43
from ganeti.utils import io as utils_io
44
from ganeti.utils import algo as utils_algo
45

    
46

    
47
#: when set to True, L{RunCmd} is disabled
48
_no_fork = False
49

    
50
(_TIMEOUT_NONE,
51
 _TIMEOUT_TERM,
52
 _TIMEOUT_KILL) = range(3)
53

    
54

    
55
def DisableFork():
56
  """Disables the use of fork(2).
57

58
  """
59
  global _no_fork # pylint: disable-msg=W0603
60

    
61
  _no_fork = True
62

    
63

    
64
class RunResult(object):
65
  """Holds the result of running external programs.
66

67
  @type exit_code: int
68
  @ivar exit_code: the exit code of the program, or None (if the program
69
      didn't exit())
70
  @type signal: int or None
71
  @ivar signal: the signal that caused the program to finish, or None
72
      (if the program wasn't terminated by a signal)
73
  @type stdout: str
74
  @ivar stdout: the standard output of the program
75
  @type stderr: str
76
  @ivar stderr: the standard error of the program
77
  @type failed: boolean
78
  @ivar failed: True in case the program was
79
      terminated by a signal or exited with a non-zero exit code
80
  @ivar fail_reason: a string detailing the termination reason
81

82
  """
83
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
84
               "failed", "fail_reason", "cmd"]
85

    
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):
145
  """Execute a (shell) command.
146

147
  The command should not read from its standard input, as it will be
148
  closed.
149

150
  @type cmd: string or list
151
  @param cmd: Command to run
152
  @type env: dict
153
  @param env: Additional environment variables
154
  @type output: str
155
  @param output: if desired, the output of the command can be
156
      saved in a file instead of the RunResult instance; this
157
      parameter denotes the file name (if not None)
158
  @type cwd: string
159
  @param cwd: if specified, will be used as the working
160
      directory for the command; the default will be /
161
  @type reset_env: boolean
162
  @param reset_env: whether to reset or keep the default os environment
163
  @type interactive: boolean
164
  @param interactive: weather we pipe stdin, stdout and stderr
165
                      (default behaviour) or run the command interactive
166
  @type timeout: int
167
  @param timeout: If not None, timeout in seconds until child process gets
168
                  killed
169
  @rtype: L{RunResult}
170
  @return: RunResult instance
171
  @raise errors.ProgrammerError: if we call this when forks are disabled
172

173
  """
174
  if _no_fork:
175
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
176

    
177
  if output and interactive:
178
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
179
                                 " not be provided at the same time")
180

    
181
  if isinstance(cmd, basestring):
182
    strcmd = cmd
183
    shell = True
184
  else:
185
    cmd = [str(val) for val in cmd]
186
    strcmd = utils_text.ShellQuoteArgs(cmd)
187
    shell = False
188

    
189
  if output:
190
    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
191
  else:
192
    logging.debug("RunCmd %s", strcmd)
193

    
194
  cmd_env = _BuildCmdEnvironment(env, reset_env)
195

    
196
  try:
197
    if output is None:
198
      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
199
                                                     interactive, timeout)
200
    else:
201
      timeout_action = _TIMEOUT_NONE
202
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
203
      out = err = ""
204
  except OSError, err:
205
    if err.errno == errno.ENOENT:
206
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
207
                               (strcmd, err))
208
    else:
209
      raise
210

    
211
  if status >= 0:
212
    exitcode = status
213
    signal_ = None
214
  else:
215
    exitcode = None
216
    signal_ = -status
217

    
218
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
219

    
220

    
221
def SetupDaemonEnv(cwd="/", umask=077):
222
  """Setup a daemon's environment.
223

224
  This should be called between the first and second fork, due to
225
  setsid usage.
226

227
  @param cwd: the directory to which to chdir
228
  @param umask: the umask to setup
229

230
  """
231
  os.chdir(cwd)
232
  os.umask(umask)
233
  os.setsid()
234

    
235

    
236
def SetupDaemonFDs(output_file, output_fd):
237
  """Setups up a daemon's file descriptors.
238

239
  @param output_file: if not None, the file to which to redirect
240
      stdout/stderr
241
  @param output_fd: if not None, the file descriptor for stdout/stderr
242

243
  """
244
  # check that at most one is defined
245
  assert [output_file, output_fd].count(None) >= 1
246

    
247
  # Open /dev/null (read-only, only for stdin)
248
  devnull_fd = os.open(os.devnull, os.O_RDONLY)
249

    
250
  if output_fd is not None:
251
    pass
252
  elif output_file is not None:
253
    # Open output file
254
    try:
255
      output_fd = os.open(output_file,
256
                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
257
    except EnvironmentError, err:
258
      raise Exception("Opening output file failed: %s" % err)
259
  else:
260
    output_fd = os.open(os.devnull, os.O_WRONLY)
261

    
262
  # Redirect standard I/O
263
  os.dup2(devnull_fd, 0)
264
  os.dup2(output_fd, 1)
265
  os.dup2(output_fd, 2)
266

    
267

    
268
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
269
                pidfile=None):
270
  """Start a daemon process after forking twice.
271

272
  @type cmd: string or list
273
  @param cmd: Command to run
274
  @type env: dict
275
  @param env: Additional environment variables
276
  @type cwd: string
277
  @param cwd: Working directory for the program
278
  @type output: string
279
  @param output: Path to file in which to save the output
280
  @type output_fd: int
281
  @param output_fd: File descriptor for output
282
  @type pidfile: string
283
  @param pidfile: Process ID file
284
  @rtype: int
285
  @return: Daemon process ID
286
  @raise errors.ProgrammerError: if we call this when forks are disabled
287

288
  """
289
  if _no_fork:
290
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
291
                                 " disabled")
292

    
293
  if output and not (bool(output) ^ (output_fd is not None)):
294
    raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
295
                                 " specified")
296

    
297
  if isinstance(cmd, basestring):
298
    cmd = ["/bin/sh", "-c", cmd]
299

    
300
  strcmd = utils_text.ShellQuoteArgs(cmd)
301

    
302
  if output:
303
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
304
  else:
305
    logging.debug("StartDaemon %s", strcmd)
306

    
307
  cmd_env = _BuildCmdEnvironment(env, False)
308

    
309
  # Create pipe for sending PID back
310
  (pidpipe_read, pidpipe_write) = os.pipe()
311
  try:
312
    try:
313
      # Create pipe for sending error messages
314
      (errpipe_read, errpipe_write) = os.pipe()
315
      try:
316
        try:
317
          # First fork
318
          pid = os.fork()
319
          if pid == 0:
320
            try:
321
              # Child process, won't return
322
              _StartDaemonChild(errpipe_read, errpipe_write,
323
                                pidpipe_read, pidpipe_write,
324
                                cmd, cmd_env, cwd,
325
                                output, output_fd, pidfile)
326
            finally:
327
              # Well, maybe child process failed
328
              os._exit(1) # pylint: disable-msg=W0212
329
        finally:
330
          utils_wrapper.CloseFdNoError(errpipe_write)
331

    
332
        # Wait for daemon to be started (or an error message to
333
        # arrive) and read up to 100 KB as an error message
334
        errormsg = utils_wrapper.RetryOnSignal(os.read, errpipe_read,
335
                                               100 * 1024)
336
      finally:
337
        utils_wrapper.CloseFdNoError(errpipe_read)
338
    finally:
339
      utils_wrapper.CloseFdNoError(pidpipe_write)
340

    
341
    # Read up to 128 bytes for PID
342
    pidtext = utils_wrapper.RetryOnSignal(os.read, pidpipe_read, 128)
343
  finally:
344
    utils_wrapper.CloseFdNoError(pidpipe_read)
345

    
346
  # Try to avoid zombies by waiting for child process
347
  try:
348
    os.waitpid(pid, 0)
349
  except OSError:
350
    pass
351

    
352
  if errormsg:
353
    raise errors.OpExecError("Error when starting daemon process: %r" %
354
                             errormsg)
355

    
356
  try:
357
    return int(pidtext)
358
  except (ValueError, TypeError), err:
359
    raise errors.OpExecError("Error while trying to parse PID %r: %s" %
360
                             (pidtext, err))
361

    
362

    
363
def _StartDaemonChild(errpipe_read, errpipe_write,
364
                      pidpipe_read, pidpipe_write,
365
                      args, env, cwd,
366
                      output, fd_output, pidfile):
367
  """Child process for starting daemon.
368

369
  """
370
  try:
371
    # Close parent's side
372
    utils_wrapper.CloseFdNoError(errpipe_read)
373
    utils_wrapper.CloseFdNoError(pidpipe_read)
374

    
375
    # First child process
376
    SetupDaemonEnv()
377

    
378
    # And fork for the second time
379
    pid = os.fork()
380
    if pid != 0:
381
      # Exit first child process
382
      os._exit(0) # pylint: disable-msg=W0212
383

    
384
    # Make sure pipe is closed on execv* (and thereby notifies
385
    # original process)
386
    utils_wrapper.SetCloseOnExecFlag(errpipe_write, True)
387

    
388
    # List of file descriptors to be left open
389
    noclose_fds = [errpipe_write]
390

    
391
    # Open PID file
392
    if pidfile:
393
      fd_pidfile = utils_io.WritePidFile(pidfile)
394

    
395
      # Keeping the file open to hold the lock
396
      noclose_fds.append(fd_pidfile)
397

    
398
      utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
399
    else:
400
      fd_pidfile = None
401

    
402
    SetupDaemonFDs(output, fd_output)
403

    
404
    # Send daemon PID to parent
405
    utils_wrapper.RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
406

    
407
    # Close all file descriptors except stdio and error message pipe
408
    CloseFDs(noclose_fds=noclose_fds)
409

    
410
    # Change working directory
411
    os.chdir(cwd)
412

    
413
    if env is None:
414
      os.execvp(args[0], args)
415
    else:
416
      os.execvpe(args[0], args, env)
417
  except: # pylint: disable-msg=W0702
418
    try:
419
      # Report errors to original process
420
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
421
    except: # pylint: disable-msg=W0702
422
      # Ignore errors in error handling
423
      pass
424

    
425
  os._exit(1) # pylint: disable-msg=W0212
426

    
427

    
428
def WriteErrorToFD(fd, err):
429
  """Possibly write an error message to a fd.
430

431
  @type fd: None or int (file descriptor)
432
  @param fd: if not None, the error will be written to this fd
433
  @param err: string, the error message
434

435
  """
436
  if fd is None:
437
    return
438

    
439
  if not err:
440
    err = "<unknown error>"
441

    
442
  utils_wrapper.RetryOnSignal(os.write, fd, err)
443

    
444

    
445
def _CheckIfAlive(child):
446
  """Raises L{utils_retry.RetryAgain} if child is still alive.
447

448
  @raises utils_retry.RetryAgain: If child is still alive
449

450
  """
451
  if child.poll() is None:
452
    raise utils_retry.RetryAgain()
453

    
454

    
455
def _WaitForProcess(child, timeout):
456
  """Waits for the child to terminate or until we reach timeout.
457

458
  """
459
  try:
460
    utils_retry.Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout),
461
                      args=[child])
462
  except utils_retry.RetryTimeout:
463
    pass
464

    
465

    
466
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
467
                _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
468
  """Run a command and return its output.
469

470
  @type  cmd: string or list
471
  @param cmd: Command to run
472
  @type env: dict
473
  @param env: The environment to use
474
  @type via_shell: bool
475
  @param via_shell: if we should run via the shell
476
  @type cwd: string
477
  @param cwd: the working directory for the program
478
  @type interactive: boolean
479
  @param interactive: Run command interactive (without piping)
480
  @type timeout: int
481
  @param timeout: Timeout after the programm gets terminated
482
  @rtype: tuple
483
  @return: (out, err, status)
484

485
  """
486
  poller = select.poll()
487

    
488
  stderr = subprocess.PIPE
489
  stdout = subprocess.PIPE
490
  stdin = subprocess.PIPE
491

    
492
  if interactive:
493
    stderr = stdout = stdin = None
494

    
495
  child = subprocess.Popen(cmd, shell=via_shell,
496
                           stderr=stderr,
497
                           stdout=stdout,
498
                           stdin=stdin,
499
                           close_fds=True, env=env,
500
                           cwd=cwd)
501

    
502
  out = StringIO()
503
  err = StringIO()
504

    
505
  linger_timeout = None
506

    
507
  if timeout is None:
508
    poll_timeout = None
509
  else:
510
    poll_timeout = utils_algo.RunningTimeout(timeout, True).Remaining
511

    
512
  msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
513
                 (cmd, child.pid))
514
  msg_linger = ("Command %s (%d) run into linger timeout, killing" %
515
                (cmd, child.pid))
516

    
517
  timeout_action = _TIMEOUT_NONE
518

    
519
  if not interactive:
520
    child.stdin.close()
521
    poller.register(child.stdout, select.POLLIN)
522
    poller.register(child.stderr, select.POLLIN)
523
    fdmap = {
524
      child.stdout.fileno(): (out, child.stdout),
525
      child.stderr.fileno(): (err, child.stderr),
526
      }
527
    for fd in fdmap:
528
      utils_wrapper.SetNonblockFlag(fd, True)
529

    
530
    while fdmap:
531
      if poll_timeout:
532
        pt = poll_timeout() * 1000
533
        if pt < 0:
534
          if linger_timeout is None:
535
            logging.warning(msg_timeout)
536
            if child.poll() is None:
537
              timeout_action = _TIMEOUT_TERM
538
              utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid,
539
                                                  signal.SIGTERM)
540
            linger_timeout = \
541
              utils_algo.RunningTimeout(_linger_timeout, True).Remaining
542
          pt = linger_timeout() * 1000
543
          if pt < 0:
544
            break
545
      else:
546
        pt = None
547

    
548
      pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
549

    
550
      for fd, event in pollresult:
551
        if event & select.POLLIN or event & select.POLLPRI:
552
          data = fdmap[fd][1].read()
553
          # no data from read signifies EOF (the same as POLLHUP)
554
          if not data:
555
            poller.unregister(fd)
556
            del fdmap[fd]
557
            continue
558
          fdmap[fd][0].write(data)
559
        if (event & select.POLLNVAL or event & select.POLLHUP or
560
            event & select.POLLERR):
561
          poller.unregister(fd)
562
          del fdmap[fd]
563

    
564
  if timeout is not None:
565
    assert callable(poll_timeout)
566

    
567
    # We have no I/O left but it might still run
568
    if child.poll() is None:
569
      _WaitForProcess(child, poll_timeout())
570

    
571
    # Terminate if still alive after timeout
572
    if child.poll() is None:
573
      if linger_timeout is None:
574
        logging.warning(msg_timeout)
575
        timeout_action = _TIMEOUT_TERM
576
        utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
577
        lt = _linger_timeout
578
      else:
579
        lt = linger_timeout()
580
      _WaitForProcess(child, lt)
581

    
582
    # Okay, still alive after timeout and linger timeout? Kill it!
583
    if child.poll() is None:
584
      timeout_action = _TIMEOUT_KILL
585
      logging.warning(msg_linger)
586
      utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
587

    
588
  out = out.getvalue()
589
  err = err.getvalue()
590

    
591
  status = child.wait()
592
  return out, err, status, timeout_action
593

    
594

    
595
def _RunCmdFile(cmd, env, via_shell, output, cwd):
596
  """Run a command and save its output to a file.
597

598
  @type  cmd: string or list
599
  @param cmd: Command to run
600
  @type env: dict
601
  @param env: The environment to use
602
  @type via_shell: bool
603
  @param via_shell: if we should run via the shell
604
  @type output: str
605
  @param output: the filename in which to save the output
606
  @type cwd: string
607
  @param cwd: the working directory for the program
608
  @rtype: int
609
  @return: the exit status
610

611
  """
612
  fh = open(output, "a")
613
  try:
614
    child = subprocess.Popen(cmd, shell=via_shell,
615
                             stderr=subprocess.STDOUT,
616
                             stdout=fh,
617
                             stdin=subprocess.PIPE,
618
                             close_fds=True, env=env,
619
                             cwd=cwd)
620

    
621
    child.stdin.close()
622
    status = child.wait()
623
  finally:
624
    fh.close()
625
  return status
626

    
627

    
628
def RunParts(dir_name, env=None, reset_env=False):
629
  """Run Scripts or programs in a directory
630

631
  @type dir_name: string
632
  @param dir_name: absolute path to a directory
633
  @type env: dict
634
  @param env: The environment to use
635
  @type reset_env: boolean
636
  @param reset_env: whether to reset or keep the default os environment
637
  @rtype: list of tuples
638
  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
639

640
  """
641
  rr = []
642

    
643
  try:
644
    dir_contents = utils_io.ListVisibleFiles(dir_name)
645
  except OSError, err:
646
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
647
    return rr
648

    
649
  for relname in sorted(dir_contents):
650
    fname = utils_io.PathJoin(dir_name, relname)
651
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
652
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
653
      rr.append((relname, constants.RUNPARTS_SKIP, None))
654
    else:
655
      try:
656
        result = RunCmd([fname], env=env, reset_env=reset_env)
657
      except Exception, err: # pylint: disable-msg=W0703
658
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
659
      else:
660
        rr.append((relname, constants.RUNPARTS_RUN, result))
661

    
662
  return rr
663

    
664

    
665
def _GetProcStatusPath(pid):
666
  """Returns the path for a PID's proc status file.
667

668
  @type pid: int
669
  @param pid: Process ID
670
  @rtype: string
671

672
  """
673
  return "/proc/%d/status" % pid
674

    
675

    
676
def IsProcessAlive(pid):
677
  """Check if a given pid exists on the system.
678

679
  @note: zombie status is not handled, so zombie processes
680
      will be returned as alive
681
  @type pid: int
682
  @param pid: the process ID to check
683
  @rtype: boolean
684
  @return: True if the process exists
685

686
  """
687
  def _TryStat(name):
688
    try:
689
      os.stat(name)
690
      return True
691
    except EnvironmentError, err:
692
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
693
        return False
694
      elif err.errno == errno.EINVAL:
695
        raise utils_retry.RetryAgain(err)
696
      raise
697

    
698
  assert isinstance(pid, int), "pid must be an integer"
699
  if pid <= 0:
700
    return False
701

    
702
  # /proc in a multiprocessor environment can have strange behaviors.
703
  # Retry the os.stat a few times until we get a good result.
704
  try:
705
    return utils_retry.Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
706
                             args=[_GetProcStatusPath(pid)])
707
  except utils_retry.RetryTimeout, err:
708
    err.RaiseInner()
709

    
710

    
711
def _ParseSigsetT(sigset):
712
  """Parse a rendered sigset_t value.
713

714
  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
715
  function.
716

717
  @type sigset: string
718
  @param sigset: Rendered signal set from /proc/$pid/status
719
  @rtype: set
720
  @return: Set of all enabled signal numbers
721

722
  """
723
  result = set()
724

    
725
  signum = 0
726
  for ch in reversed(sigset):
727
    chv = int(ch, 16)
728

    
729
    # The following could be done in a loop, but it's easier to read and
730
    # understand in the unrolled form
731
    if chv & 1:
732
      result.add(signum + 1)
733
    if chv & 2:
734
      result.add(signum + 2)
735
    if chv & 4:
736
      result.add(signum + 3)
737
    if chv & 8:
738
      result.add(signum + 4)
739

    
740
    signum += 4
741

    
742
  return result
743

    
744

    
745
def _GetProcStatusField(pstatus, field):
746
  """Retrieves a field from the contents of a proc status file.
747

748
  @type pstatus: string
749
  @param pstatus: Contents of /proc/$pid/status
750
  @type field: string
751
  @param field: Name of field whose value should be returned
752
  @rtype: string
753

754
  """
755
  for line in pstatus.splitlines():
756
    parts = line.split(":", 1)
757

    
758
    if len(parts) < 2 or parts[0] != field:
759
      continue
760

    
761
    return parts[1].strip()
762

    
763
  return None
764

    
765

    
766
def IsProcessHandlingSignal(pid, signum, status_path=None):
767
  """Checks whether a process is handling a signal.
768

769
  @type pid: int
770
  @param pid: Process ID
771
  @type signum: int
772
  @param signum: Signal number
773
  @rtype: bool
774

775
  """
776
  if status_path is None:
777
    status_path = _GetProcStatusPath(pid)
778

    
779
  try:
780
    proc_status = utils_io.ReadFile(status_path)
781
  except EnvironmentError, err:
782
    # In at least one case, reading /proc/$pid/status failed with ESRCH.
783
    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
784
      return False
785
    raise
786

    
787
  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
788
  if sigcgt is None:
789
    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
790

    
791
  # Now check whether signal is handled
792
  return signum in _ParseSigsetT(sigcgt)
793

    
794

    
795
def Daemonize(logfile):
796
  """Daemonize the current process.
797

798
  This detaches the current process from the controlling terminal and
799
  runs it in the background as a daemon.
800

801
  @type logfile: str
802
  @param logfile: the logfile to which we should redirect stdout/stderr
803
  @rtype: int
804
  @return: the value zero
805

806
  """
807
  # pylint: disable-msg=W0212
808
  # yes, we really want os._exit
809

    
810
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
811
  # least abstract the pipe functionality between them
812

    
813
  # Create pipe for sending error messages
814
  (rpipe, wpipe) = os.pipe()
815

    
816
  # this might fail
817
  pid = os.fork()
818
  if (pid == 0):  # The first child.
819
    SetupDaemonEnv()
820

    
821
    # this might fail
822
    pid = os.fork() # Fork a second child.
823
    if (pid == 0):  # The second child.
824
      utils_wrapper.CloseFdNoError(rpipe)
825
    else:
826
      # exit() or _exit()?  See below.
827
      os._exit(0) # Exit parent (the first child) of the second child.
828
  else:
829
    utils_wrapper.CloseFdNoError(wpipe)
830
    # Wait for daemon to be started (or an error message to
831
    # arrive) and read up to 100 KB as an error message
832
    errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024)
833
    if errormsg:
834
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
835
      rcode = 1
836
    else:
837
      rcode = 0
838
    os._exit(rcode) # Exit parent of the first child.
839

    
840
  SetupDaemonFDs(logfile, None)
841
  return wpipe
842

    
843

    
844
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
845
                waitpid=False):
846
  """Kill a process given by its pid.
847

848
  @type pid: int
849
  @param pid: The PID to terminate.
850
  @type signal_: int
851
  @param signal_: The signal to send, by default SIGTERM
852
  @type timeout: int
853
  @param timeout: The timeout after which, if the process is still alive,
854
                  a SIGKILL will be sent. If not positive, no such checking
855
                  will be done
856
  @type waitpid: boolean
857
  @param waitpid: If true, we should waitpid on this process after
858
      sending signals, since it's our own child and otherwise it
859
      would remain as zombie
860

861
  """
862
  def _helper(pid, signal_, wait):
863
    """Simple helper to encapsulate the kill/waitpid sequence"""
864
    if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
865
      try:
866
        os.waitpid(pid, os.WNOHANG)
867
      except OSError:
868
        pass
869

    
870
  if pid <= 0:
871
    # kill with pid=0 == suicide
872
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
873

    
874
  if not IsProcessAlive(pid):
875
    return
876

    
877
  _helper(pid, signal_, waitpid)
878

    
879
  if timeout <= 0:
880
    return
881

    
882
  def _CheckProcess():
883
    if not IsProcessAlive(pid):
884
      return
885

    
886
    try:
887
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
888
    except OSError:
889
      raise utils_retry.RetryAgain()
890

    
891
    if result_pid > 0:
892
      return
893

    
894
    raise utils_retry.RetryAgain()
895

    
896
  try:
897
    # Wait up to $timeout seconds
898
    utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
899
  except utils_retry.RetryTimeout:
900
    pass
901

    
902
  if IsProcessAlive(pid):
903
    # Kill process if it's still alive
904
    _helper(pid, signal.SIGKILL, waitpid)
905

    
906

    
907
def RunInSeparateProcess(fn, *args):
908
  """Runs a function in a separate process.
909

910
  Note: Only boolean return values are supported.
911

912
  @type fn: callable
913
  @param fn: Function to be called
914
  @rtype: bool
915
  @return: Function's result
916

917
  """
918
  pid = os.fork()
919
  if pid == 0:
920
    # Child process
921
    try:
922
      # In case the function uses temporary files
923
      utils_wrapper.ResetTempfileModule()
924

    
925
      # Call function
926
      result = int(bool(fn(*args)))
927
      assert result in (0, 1)
928
    except: # pylint: disable-msg=W0702
929
      logging.exception("Error while calling function in separate process")
930
      # 0 and 1 are reserved for the return value
931
      result = 33
932

    
933
    os._exit(result) # pylint: disable-msg=W0212
934

    
935
  # Parent process
936

    
937
  # Avoid zombies and check exit code
938
  (_, status) = os.waitpid(pid, 0)
939

    
940
  if os.WIFSIGNALED(status):
941
    exitcode = None
942
    signum = os.WTERMSIG(status)
943
  else:
944
    exitcode = os.WEXITSTATUS(status)
945
    signum = None
946

    
947
  if not (exitcode in (0, 1) and signum is None):
948
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
949
                              (exitcode, signum))
950

    
951
  return bool(exitcode)
952

    
953

    
954
def CloseFDs(noclose_fds=None):
955
  """Close file descriptors.
956

957
  This closes all file descriptors above 2 (i.e. except
958
  stdin/out/err).
959

960
  @type noclose_fds: list or None
961
  @param noclose_fds: if given, it denotes a list of file descriptor
962
      that should not be closed
963

964
  """
965
  # Default maximum for the number of available file descriptors.
966
  if 'SC_OPEN_MAX' in os.sysconf_names:
967
    try:
968
      MAXFD = os.sysconf('SC_OPEN_MAX')
969
      if MAXFD < 0:
970
        MAXFD = 1024
971
    except OSError:
972
      MAXFD = 1024
973
  else:
974
    MAXFD = 1024
975

    
976
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
977
  if (maxfd == resource.RLIM_INFINITY):
978
    maxfd = MAXFD
979

    
980
  # Iterate through and close all file descriptors (except the standard ones)
981
  for fd in range(3, maxfd):
982
    if noclose_fds and fd in noclose_fds:
983
      continue
984
    utils_wrapper.CloseFdNoError(fd)