Statistics
| Branch: | Tag: | Revision:

root / lib / utils / process.py @ d6491981

History | View | Annotate | Download (28.2 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, 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: weather 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
  if output_fd is not None:
260
    pass
261
  elif output_file is not None:
262
    # Open output file
263
    try:
264
      output_fd = os.open(output_file,
265
                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
266
    except EnvironmentError, err:
267
      raise Exception("Opening output file failed: %s" % err)
268
  else:
269
    output_fd = os.open(os.devnull, os.O_WRONLY)
270

    
271
  # Redirect standard I/O
272
  os.dup2(devnull_fd, 0)
273
  os.dup2(output_fd, 1)
274
  os.dup2(output_fd, 2)
275

    
276

    
277
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
278
                pidfile=None):
279
  """Start a daemon process after forking twice.
280

281
  @type cmd: string or list
282
  @param cmd: Command to run
283
  @type env: dict
284
  @param env: Additional environment variables
285
  @type cwd: string
286
  @param cwd: Working directory for the program
287
  @type output: string
288
  @param output: Path to file in which to save the output
289
  @type output_fd: int
290
  @param output_fd: File descriptor for output
291
  @type pidfile: string
292
  @param pidfile: Process ID file
293
  @rtype: int
294
  @return: Daemon process ID
295
  @raise errors.ProgrammerError: if we call this when forks are disabled
296

297
  """
298
  if _no_fork:
299
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
300
                                 " disabled")
301

    
302
  if output and not (bool(output) ^ (output_fd is not None)):
303
    raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
304
                                 " specified")
305

    
306
  if isinstance(cmd, basestring):
307
    cmd = ["/bin/sh", "-c", cmd]
308

    
309
  strcmd = utils_text.ShellQuoteArgs(cmd)
310

    
311
  if output:
312
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
313
  else:
314
    logging.debug("StartDaemon %s", strcmd)
315

    
316
  cmd_env = _BuildCmdEnvironment(env, False)
317

    
318
  # Create pipe for sending PID back
319
  (pidpipe_read, pidpipe_write) = os.pipe()
320
  try:
321
    try:
322
      # Create pipe for sending error messages
323
      (errpipe_read, errpipe_write) = os.pipe()
324
      try:
325
        try:
326
          # First fork
327
          pid = os.fork()
328
          if pid == 0:
329
            try:
330
              # Child process, won't return
331
              _StartDaemonChild(errpipe_read, errpipe_write,
332
                                pidpipe_read, pidpipe_write,
333
                                cmd, cmd_env, cwd,
334
                                output, output_fd, pidfile)
335
            finally:
336
              # Well, maybe child process failed
337
              os._exit(1) # pylint: disable-msg=W0212
338
        finally:
339
          utils_wrapper.CloseFdNoError(errpipe_write)
340

    
341
        # Wait for daemon to be started (or an error message to
342
        # arrive) and read up to 100 KB as an error message
343
        errormsg = utils_wrapper.RetryOnSignal(os.read, errpipe_read,
344
                                               100 * 1024)
345
      finally:
346
        utils_wrapper.CloseFdNoError(errpipe_read)
347
    finally:
348
      utils_wrapper.CloseFdNoError(pidpipe_write)
349

    
350
    # Read up to 128 bytes for PID
351
    pidtext = utils_wrapper.RetryOnSignal(os.read, pidpipe_read, 128)
352
  finally:
353
    utils_wrapper.CloseFdNoError(pidpipe_read)
354

    
355
  # Try to avoid zombies by waiting for child process
356
  try:
357
    os.waitpid(pid, 0)
358
  except OSError:
359
    pass
360

    
361
  if errormsg:
362
    raise errors.OpExecError("Error when starting daemon process: %r" %
363
                             errormsg)
364

    
365
  try:
366
    return int(pidtext)
367
  except (ValueError, TypeError), err:
368
    raise errors.OpExecError("Error while trying to parse PID %r: %s" %
369
                             (pidtext, err))
370

    
371

    
372
def _StartDaemonChild(errpipe_read, errpipe_write,
373
                      pidpipe_read, pidpipe_write,
374
                      args, env, cwd,
375
                      output, fd_output, pidfile):
376
  """Child process for starting daemon.
377

378
  """
379
  try:
380
    # Close parent's side
381
    utils_wrapper.CloseFdNoError(errpipe_read)
382
    utils_wrapper.CloseFdNoError(pidpipe_read)
383

    
384
    # First child process
385
    SetupDaemonEnv()
386

    
387
    # And fork for the second time
388
    pid = os.fork()
389
    if pid != 0:
390
      # Exit first child process
391
      os._exit(0) # pylint: disable-msg=W0212
392

    
393
    # Make sure pipe is closed on execv* (and thereby notifies
394
    # original process)
395
    utils_wrapper.SetCloseOnExecFlag(errpipe_write, True)
396

    
397
    # List of file descriptors to be left open
398
    noclose_fds = [errpipe_write]
399

    
400
    # Open PID file
401
    if pidfile:
402
      fd_pidfile = utils_io.WritePidFile(pidfile)
403

    
404
      # Keeping the file open to hold the lock
405
      noclose_fds.append(fd_pidfile)
406

    
407
      utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
408
    else:
409
      fd_pidfile = None
410

    
411
    SetupDaemonFDs(output, fd_output)
412

    
413
    # Send daemon PID to parent
414
    utils_wrapper.RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
415

    
416
    # Close all file descriptors except stdio and error message pipe
417
    CloseFDs(noclose_fds=noclose_fds)
418

    
419
    # Change working directory
420
    os.chdir(cwd)
421

    
422
    if env is None:
423
      os.execvp(args[0], args)
424
    else:
425
      os.execvpe(args[0], args, env)
426
  except: # pylint: disable-msg=W0702
427
    try:
428
      # Report errors to original process
429
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
430
    except: # pylint: disable-msg=W0702
431
      # Ignore errors in error handling
432
      pass
433

    
434
  os._exit(1) # pylint: disable-msg=W0212
435

    
436

    
437
def WriteErrorToFD(fd, err):
438
  """Possibly write an error message to a fd.
439

440
  @type fd: None or int (file descriptor)
441
  @param fd: if not None, the error will be written to this fd
442
  @param err: string, the error message
443

444
  """
445
  if fd is None:
446
    return
447

    
448
  if not err:
449
    err = "<unknown error>"
450

    
451
  utils_wrapper.RetryOnSignal(os.write, fd, err)
452

    
453

    
454
def _CheckIfAlive(child):
455
  """Raises L{utils_retry.RetryAgain} if child is still alive.
456

457
  @raises utils_retry.RetryAgain: If child is still alive
458

459
  """
460
  if child.poll() is None:
461
    raise utils_retry.RetryAgain()
462

    
463

    
464
def _WaitForProcess(child, timeout):
465
  """Waits for the child to terminate or until we reach timeout.
466

467
  """
468
  try:
469
    utils_retry.Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout),
470
                      args=[child])
471
  except utils_retry.RetryTimeout:
472
    pass
473

    
474

    
475
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
476
                _linger_timeout=constants.CHILD_LINGER_TIMEOUT,
477
                _postfork_fn=None):
478
  """Run a command and return its output.
479

480
  @type  cmd: string or list
481
  @param cmd: Command to run
482
  @type env: dict
483
  @param env: The environment to use
484
  @type via_shell: bool
485
  @param via_shell: if we should run via the shell
486
  @type cwd: string
487
  @param cwd: the working directory for the program
488
  @type interactive: boolean
489
  @param interactive: Run command interactive (without piping)
490
  @type timeout: int
491
  @param timeout: Timeout after the programm gets terminated
492
  @type noclose_fds: list
493
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
494
                      open for the child process
495
  @param _postfork_fn: Function run after fork but before timeout (unittest)
496
  @rtype: tuple
497
  @return: (out, err, status)
498

499
  """
500
  poller = select.poll()
501

    
502
  stderr = subprocess.PIPE
503
  stdout = subprocess.PIPE
504
  stdin = subprocess.PIPE
505

    
506
  if interactive:
507
    stderr = stdout = stdin = None
508

    
509
  if noclose_fds:
510
    preexec_fn = lambda: CloseFDs(noclose_fds)
511
    close_fds = False
512
  else:
513
    preexec_fn = None
514
    close_fds = True
515

    
516
  child = subprocess.Popen(cmd, shell=via_shell,
517
                           stderr=stderr,
518
                           stdout=stdout,
519
                           stdin=stdin,
520
                           close_fds=close_fds, env=env,
521
                           cwd=cwd,
522
                           preexec_fn=preexec_fn)
523

    
524
  if _postfork_fn:
525
    _postfork_fn(child.pid)
526

    
527
  out = StringIO()
528
  err = StringIO()
529

    
530
  linger_timeout = None
531

    
532
  if timeout is None:
533
    poll_timeout = None
534
  else:
535
    poll_timeout = utils_algo.RunningTimeout(timeout, True).Remaining
536

    
537
  msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
538
                 (cmd, child.pid))
539
  msg_linger = ("Command %s (%d) run into linger timeout, killing" %
540
                (cmd, child.pid))
541

    
542
  timeout_action = _TIMEOUT_NONE
543

    
544
  if not interactive:
545
    child.stdin.close()
546
    poller.register(child.stdout, select.POLLIN)
547
    poller.register(child.stderr, select.POLLIN)
548
    fdmap = {
549
      child.stdout.fileno(): (out, child.stdout),
550
      child.stderr.fileno(): (err, child.stderr),
551
      }
552
    for fd in fdmap:
553
      utils_wrapper.SetNonblockFlag(fd, True)
554

    
555
    while fdmap:
556
      if poll_timeout:
557
        pt = poll_timeout() * 1000
558
        if pt < 0:
559
          if linger_timeout is None:
560
            logging.warning(msg_timeout)
561
            if child.poll() is None:
562
              timeout_action = _TIMEOUT_TERM
563
              utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid,
564
                                                  signal.SIGTERM)
565
            linger_timeout = \
566
              utils_algo.RunningTimeout(_linger_timeout, True).Remaining
567
          pt = linger_timeout() * 1000
568
          if pt < 0:
569
            break
570
      else:
571
        pt = None
572

    
573
      pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
574

    
575
      for fd, event in pollresult:
576
        if event & select.POLLIN or event & select.POLLPRI:
577
          data = fdmap[fd][1].read()
578
          # no data from read signifies EOF (the same as POLLHUP)
579
          if not data:
580
            poller.unregister(fd)
581
            del fdmap[fd]
582
            continue
583
          fdmap[fd][0].write(data)
584
        if (event & select.POLLNVAL or event & select.POLLHUP or
585
            event & select.POLLERR):
586
          poller.unregister(fd)
587
          del fdmap[fd]
588

    
589
  if timeout is not None:
590
    assert callable(poll_timeout)
591

    
592
    # We have no I/O left but it might still run
593
    if child.poll() is None:
594
      _WaitForProcess(child, poll_timeout())
595

    
596
    # Terminate if still alive after timeout
597
    if child.poll() is None:
598
      if linger_timeout is None:
599
        logging.warning(msg_timeout)
600
        timeout_action = _TIMEOUT_TERM
601
        utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
602
        lt = _linger_timeout
603
      else:
604
        lt = linger_timeout()
605
      _WaitForProcess(child, lt)
606

    
607
    # Okay, still alive after timeout and linger timeout? Kill it!
608
    if child.poll() is None:
609
      timeout_action = _TIMEOUT_KILL
610
      logging.warning(msg_linger)
611
      utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
612

    
613
  out = out.getvalue()
614
  err = err.getvalue()
615

    
616
  status = child.wait()
617
  return out, err, status, timeout_action
618

    
619

    
620
def _RunCmdFile(cmd, env, via_shell, output, cwd, noclose_fds):
621
  """Run a command and save its output to a file.
622

623
  @type  cmd: string or list
624
  @param cmd: Command to run
625
  @type env: dict
626
  @param env: The environment to use
627
  @type via_shell: bool
628
  @param via_shell: if we should run via the shell
629
  @type output: str
630
  @param output: the filename in which to save the output
631
  @type cwd: string
632
  @param cwd: the working directory for the program
633
  @type noclose_fds: list
634
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
635
                      open for the child process
636
  @rtype: int
637
  @return: the exit status
638

639
  """
640
  fh = open(output, "a")
641

    
642
  if noclose_fds:
643
    preexec_fn = lambda: CloseFDs(noclose_fds + [fh.fileno()])
644
    close_fds = False
645
  else:
646
    preexec_fn = None
647
    close_fds = True
648

    
649
  try:
650
    child = subprocess.Popen(cmd, shell=via_shell,
651
                             stderr=subprocess.STDOUT,
652
                             stdout=fh,
653
                             stdin=subprocess.PIPE,
654
                             close_fds=close_fds, env=env,
655
                             cwd=cwd,
656
                             preexec_fn=preexec_fn)
657

    
658
    child.stdin.close()
659
    status = child.wait()
660
  finally:
661
    fh.close()
662
  return status
663

    
664

    
665
def RunParts(dir_name, env=None, reset_env=False):
666
  """Run Scripts or programs in a directory
667

668
  @type dir_name: string
669
  @param dir_name: absolute path to a directory
670
  @type env: dict
671
  @param env: The environment to use
672
  @type reset_env: boolean
673
  @param reset_env: whether to reset or keep the default os environment
674
  @rtype: list of tuples
675
  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
676

677
  """
678
  rr = []
679

    
680
  try:
681
    dir_contents = utils_io.ListVisibleFiles(dir_name)
682
  except OSError, err:
683
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
684
    return rr
685

    
686
  for relname in sorted(dir_contents):
687
    fname = utils_io.PathJoin(dir_name, relname)
688
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
689
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
690
      rr.append((relname, constants.RUNPARTS_SKIP, None))
691
    else:
692
      try:
693
        result = RunCmd([fname], env=env, reset_env=reset_env)
694
      except Exception, err: # pylint: disable-msg=W0703
695
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
696
      else:
697
        rr.append((relname, constants.RUNPARTS_RUN, result))
698

    
699
  return rr
700

    
701

    
702
def _GetProcStatusPath(pid):
703
  """Returns the path for a PID's proc status file.
704

705
  @type pid: int
706
  @param pid: Process ID
707
  @rtype: string
708

709
  """
710
  return "/proc/%d/status" % pid
711

    
712

    
713
def IsProcessAlive(pid):
714
  """Check if a given pid exists on the system.
715

716
  @note: zombie status is not handled, so zombie processes
717
      will be returned as alive
718
  @type pid: int
719
  @param pid: the process ID to check
720
  @rtype: boolean
721
  @return: True if the process exists
722

723
  """
724
  def _TryStat(name):
725
    try:
726
      os.stat(name)
727
      return True
728
    except EnvironmentError, err:
729
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
730
        return False
731
      elif err.errno == errno.EINVAL:
732
        raise utils_retry.RetryAgain(err)
733
      raise
734

    
735
  assert isinstance(pid, int), "pid must be an integer"
736
  if pid <= 0:
737
    return False
738

    
739
  # /proc in a multiprocessor environment can have strange behaviors.
740
  # Retry the os.stat a few times until we get a good result.
741
  try:
742
    return utils_retry.Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
743
                             args=[_GetProcStatusPath(pid)])
744
  except utils_retry.RetryTimeout, err:
745
    err.RaiseInner()
746

    
747

    
748
def _ParseSigsetT(sigset):
749
  """Parse a rendered sigset_t value.
750

751
  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
752
  function.
753

754
  @type sigset: string
755
  @param sigset: Rendered signal set from /proc/$pid/status
756
  @rtype: set
757
  @return: Set of all enabled signal numbers
758

759
  """
760
  result = set()
761

    
762
  signum = 0
763
  for ch in reversed(sigset):
764
    chv = int(ch, 16)
765

    
766
    # The following could be done in a loop, but it's easier to read and
767
    # understand in the unrolled form
768
    if chv & 1:
769
      result.add(signum + 1)
770
    if chv & 2:
771
      result.add(signum + 2)
772
    if chv & 4:
773
      result.add(signum + 3)
774
    if chv & 8:
775
      result.add(signum + 4)
776

    
777
    signum += 4
778

    
779
  return result
780

    
781

    
782
def _GetProcStatusField(pstatus, field):
783
  """Retrieves a field from the contents of a proc status file.
784

785
  @type pstatus: string
786
  @param pstatus: Contents of /proc/$pid/status
787
  @type field: string
788
  @param field: Name of field whose value should be returned
789
  @rtype: string
790

791
  """
792
  for line in pstatus.splitlines():
793
    parts = line.split(":", 1)
794

    
795
    if len(parts) < 2 or parts[0] != field:
796
      continue
797

    
798
    return parts[1].strip()
799

    
800
  return None
801

    
802

    
803
def IsProcessHandlingSignal(pid, signum, status_path=None):
804
  """Checks whether a process is handling a signal.
805

806
  @type pid: int
807
  @param pid: Process ID
808
  @type signum: int
809
  @param signum: Signal number
810
  @rtype: bool
811

812
  """
813
  if status_path is None:
814
    status_path = _GetProcStatusPath(pid)
815

    
816
  try:
817
    proc_status = utils_io.ReadFile(status_path)
818
  except EnvironmentError, err:
819
    # In at least one case, reading /proc/$pid/status failed with ESRCH.
820
    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
821
      return False
822
    raise
823

    
824
  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
825
  if sigcgt is None:
826
    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
827

    
828
  # Now check whether signal is handled
829
  return signum in _ParseSigsetT(sigcgt)
830

    
831

    
832
def Daemonize(logfile):
833
  """Daemonize the current process.
834

835
  This detaches the current process from the controlling terminal and
836
  runs it in the background as a daemon.
837

838
  @type logfile: str
839
  @param logfile: the logfile to which we should redirect stdout/stderr
840
  @rtype: int
841
  @return: the value zero
842

843
  """
844
  # pylint: disable-msg=W0212
845
  # yes, we really want os._exit
846

    
847
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
848
  # least abstract the pipe functionality between them
849

    
850
  # Create pipe for sending error messages
851
  (rpipe, wpipe) = os.pipe()
852

    
853
  # this might fail
854
  pid = os.fork()
855
  if (pid == 0):  # The first child.
856
    SetupDaemonEnv()
857

    
858
    # this might fail
859
    pid = os.fork() # Fork a second child.
860
    if (pid == 0):  # The second child.
861
      utils_wrapper.CloseFdNoError(rpipe)
862
    else:
863
      # exit() or _exit()?  See below.
864
      os._exit(0) # Exit parent (the first child) of the second child.
865
  else:
866
    utils_wrapper.CloseFdNoError(wpipe)
867
    # Wait for daemon to be started (or an error message to
868
    # arrive) and read up to 100 KB as an error message
869
    errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024)
870
    if errormsg:
871
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
872
      rcode = 1
873
    else:
874
      rcode = 0
875
    os._exit(rcode) # Exit parent of the first child.
876

    
877
  SetupDaemonFDs(logfile, None)
878
  return wpipe
879

    
880

    
881
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
882
                waitpid=False):
883
  """Kill a process given by its pid.
884

885
  @type pid: int
886
  @param pid: The PID to terminate.
887
  @type signal_: int
888
  @param signal_: The signal to send, by default SIGTERM
889
  @type timeout: int
890
  @param timeout: The timeout after which, if the process is still alive,
891
                  a SIGKILL will be sent. If not positive, no such checking
892
                  will be done
893
  @type waitpid: boolean
894
  @param waitpid: If true, we should waitpid on this process after
895
      sending signals, since it's our own child and otherwise it
896
      would remain as zombie
897

898
  """
899
  def _helper(pid, signal_, wait):
900
    """Simple helper to encapsulate the kill/waitpid sequence"""
901
    if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
902
      try:
903
        os.waitpid(pid, os.WNOHANG)
904
      except OSError:
905
        pass
906

    
907
  if pid <= 0:
908
    # kill with pid=0 == suicide
909
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
910

    
911
  if not IsProcessAlive(pid):
912
    return
913

    
914
  _helper(pid, signal_, waitpid)
915

    
916
  if timeout <= 0:
917
    return
918

    
919
  def _CheckProcess():
920
    if not IsProcessAlive(pid):
921
      return
922

    
923
    try:
924
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
925
    except OSError:
926
      raise utils_retry.RetryAgain()
927

    
928
    if result_pid > 0:
929
      return
930

    
931
    raise utils_retry.RetryAgain()
932

    
933
  try:
934
    # Wait up to $timeout seconds
935
    utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
936
  except utils_retry.RetryTimeout:
937
    pass
938

    
939
  if IsProcessAlive(pid):
940
    # Kill process if it's still alive
941
    _helper(pid, signal.SIGKILL, waitpid)
942

    
943

    
944
def RunInSeparateProcess(fn, *args):
945
  """Runs a function in a separate process.
946

947
  Note: Only boolean return values are supported.
948

949
  @type fn: callable
950
  @param fn: Function to be called
951
  @rtype: bool
952
  @return: Function's result
953

954
  """
955
  pid = os.fork()
956
  if pid == 0:
957
    # Child process
958
    try:
959
      # In case the function uses temporary files
960
      utils_wrapper.ResetTempfileModule()
961

    
962
      # Call function
963
      result = int(bool(fn(*args)))
964
      assert result in (0, 1)
965
    except: # pylint: disable-msg=W0702
966
      logging.exception("Error while calling function in separate process")
967
      # 0 and 1 are reserved for the return value
968
      result = 33
969

    
970
    os._exit(result) # pylint: disable-msg=W0212
971

    
972
  # Parent process
973

    
974
  # Avoid zombies and check exit code
975
  (_, status) = os.waitpid(pid, 0)
976

    
977
  if os.WIFSIGNALED(status):
978
    exitcode = None
979
    signum = os.WTERMSIG(status)
980
  else:
981
    exitcode = os.WEXITSTATUS(status)
982
    signum = None
983

    
984
  if not (exitcode in (0, 1) and signum is None):
985
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
986
                              (exitcode, signum))
987

    
988
  return bool(exitcode)
989

    
990

    
991
def CloseFDs(noclose_fds=None):
992
  """Close file descriptors.
993

994
  This closes all file descriptors above 2 (i.e. except
995
  stdin/out/err).
996

997
  @type noclose_fds: list or None
998
  @param noclose_fds: if given, it denotes a list of file descriptor
999
      that should not be closed
1000

1001
  """
1002
  # Default maximum for the number of available file descriptors.
1003
  if 'SC_OPEN_MAX' in os.sysconf_names:
1004
    try:
1005
      MAXFD = os.sysconf('SC_OPEN_MAX')
1006
      if MAXFD < 0:
1007
        MAXFD = 1024
1008
    except OSError:
1009
      MAXFD = 1024
1010
  else:
1011
    MAXFD = 1024
1012

    
1013
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1014
  if (maxfd == resource.RLIM_INFINITY):
1015
    maxfd = MAXFD
1016

    
1017
  # Iterate through and close all file descriptors (except the standard ones)
1018
  for fd in range(3, maxfd):
1019
    if noclose_fds and fd in noclose_fds:
1020
      continue
1021
    utils_wrapper.CloseFdNoError(fd)