Statistics
| Branch: | Tag: | Revision:

root / lib / utils / process.py @ b459a848

History | View | Annotate | Download (28.6 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
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)