Statistics
| Branch: | Tag: | Revision:

root / lib / utils / process.py @ d5d76ab2

History | View | Annotate | Download (29.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011, 2012 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Utility functions for processes.
22

23
"""
24

    
25

    
26
import os
27
import sys
28
import subprocess
29
import errno
30
import select
31
import logging
32
import signal
33
import resource
34

    
35
from cStringIO import StringIO
36

    
37
from ganeti import errors
38
from ganeti import constants
39
from ganeti import compat
40

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

    
47

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

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

    
55

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

59
  """
60
  global _no_fork # pylint: disable=W0603
61

    
62
  _no_fork = True
63

    
64

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

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

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

    
87
  def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
88
               timeout):
89
    self.cmd = cmd
90
    self.exit_code = exit_code
91
    self.signal = signal_
92
    self.stdout = stdout
93
    self.stderr = stderr
94
    self.failed = (signal_ is not None or exit_code != 0)
95

    
96
    fail_msgs = []
97
    if self.signal is not None:
98
      fail_msgs.append("terminated by signal %s" % self.signal)
99
    elif self.exit_code is not None:
100
      fail_msgs.append("exited with exit code %s" % self.exit_code)
101
    else:
102
      fail_msgs.append("unable to determine termination reason")
103

    
104
    if timeout_action == _TIMEOUT_TERM:
105
      fail_msgs.append("terminated after timeout of %.2f seconds" % timeout)
106
    elif timeout_action == _TIMEOUT_KILL:
107
      fail_msgs.append(("force termination after timeout of %.2f seconds"
108
                        " and linger for another %.2f seconds") %
109
                       (timeout, constants.CHILD_LINGER_TIMEOUT))
110

    
111
    if fail_msgs and self.failed:
112
      self.fail_reason = utils_text.CommaJoin(fail_msgs)
113

    
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
           input_fd=None, _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
  @type input_fd: C{file}-like object or numeric file descriptor
174
  @param input_fd: File descriptor for process' standard input
175
  @param _postfork_fn: Callback run after fork but before timeout (unittest)
176
  @rtype: L{RunResult}
177
  @return: RunResult instance
178
  @raise errors.ProgrammerError: if we call this when forks are disabled
179

180
  """
181
  if _no_fork:
182
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
183

    
184
  if output and interactive:
185
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
186
                                 " not be provided at the same time")
187

    
188
  if not (output is None or input_fd is None):
189
    # The current logic in "_RunCmdFile", which is used when output is defined,
190
    # does not support input files (not hard to implement, though)
191
    raise errors.ProgrammerError("Parameters 'output' and 'input_fd' can"
192
                                 " not be used at the same time")
193

    
194
  if isinstance(cmd, basestring):
195
    strcmd = cmd
196
    shell = True
197
  else:
198
    cmd = [str(val) for val in cmd]
199
    strcmd = utils_text.ShellQuoteArgs(cmd)
200
    shell = False
201

    
202
  if output:
203
    logging.info("RunCmd %s, output file '%s'", strcmd, output)
204
  else:
205
    logging.info("RunCmd %s", strcmd)
206

    
207
  cmd_env = _BuildCmdEnvironment(env, reset_env)
208

    
209
  try:
210
    if output is None:
211
      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
212
                                                     interactive, timeout,
213
                                                     noclose_fds, input_fd,
214
                                                     _postfork_fn=_postfork_fn)
215
    else:
216
      assert _postfork_fn is None, \
217
          "_postfork_fn not supported if output provided"
218
      assert input_fd is None
219
      timeout_action = _TIMEOUT_NONE
220
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds)
221
      out = err = ""
222
  except OSError, err:
223
    if err.errno == errno.ENOENT:
224
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
225
                               (strcmd, err))
226
    else:
227
      raise
228

    
229
  if status >= 0:
230
    exitcode = status
231
    signal_ = None
232
  else:
233
    exitcode = None
234
    signal_ = -status
235

    
236
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
237

    
238

    
239
def SetupDaemonEnv(cwd="/", umask=077):
240
  """Setup a daemon's environment.
241

242
  This should be called between the first and second fork, due to
243
  setsid usage.
244

245
  @param cwd: the directory to which to chdir
246
  @param umask: the umask to setup
247

248
  """
249
  os.chdir(cwd)
250
  os.umask(umask)
251
  os.setsid()
252

    
253

    
254
def SetupDaemonFDs(output_file, output_fd):
255
  """Setups up a daemon's file descriptors.
256

257
  @param output_file: if not None, the file to which to redirect
258
      stdout/stderr
259
  @param output_fd: if not None, the file descriptor for stdout/stderr
260

261
  """
262
  # check that at most one is defined
263
  assert [output_file, output_fd].count(None) >= 1
264

    
265
  # Open /dev/null (read-only, only for stdin)
266
  devnull_fd = os.open(os.devnull, os.O_RDONLY)
267

    
268
  output_close = True
269

    
270
  if output_fd is not None:
271
    output_close = False
272
  elif output_file is not None:
273
    # Open output file
274
    try:
275
      output_fd = os.open(output_file,
276
                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
277
    except EnvironmentError, err:
278
      raise Exception("Opening output file failed: %s" % err)
279
  else:
280
    output_fd = os.open(os.devnull, os.O_WRONLY)
281

    
282
  # Redirect standard I/O
283
  os.dup2(devnull_fd, 0)
284
  os.dup2(output_fd, 1)
285
  os.dup2(output_fd, 2)
286

    
287
  if devnull_fd > 2:
288
    utils_wrapper.CloseFdNoError(devnull_fd)
289

    
290
  if output_close and output_fd > 2:
291
    utils_wrapper.CloseFdNoError(output_fd)
292

    
293

    
294
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
295
                pidfile=None):
296
  """Start a daemon process after forking twice.
297

298
  @type cmd: string or list
299
  @param cmd: Command to run
300
  @type env: dict
301
  @param env: Additional environment variables
302
  @type cwd: string
303
  @param cwd: Working directory for the program
304
  @type output: string
305
  @param output: Path to file in which to save the output
306
  @type output_fd: int
307
  @param output_fd: File descriptor for output
308
  @type pidfile: string
309
  @param pidfile: Process ID file
310
  @rtype: int
311
  @return: Daemon process ID
312
  @raise errors.ProgrammerError: if we call this when forks are disabled
313

314
  """
315
  if _no_fork:
316
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
317
                                 " disabled")
318

    
319
  if output and not (bool(output) ^ (output_fd is not None)):
320
    raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
321
                                 " specified")
322

    
323
  if isinstance(cmd, basestring):
324
    cmd = ["/bin/sh", "-c", cmd]
325

    
326
  strcmd = utils_text.ShellQuoteArgs(cmd)
327

    
328
  if output:
329
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
330
  else:
331
    logging.debug("StartDaemon %s", strcmd)
332

    
333
  cmd_env = _BuildCmdEnvironment(env, False)
334

    
335
  # Create pipe for sending PID back
336
  (pidpipe_read, pidpipe_write) = os.pipe()
337
  try:
338
    try:
339
      # Create pipe for sending error messages
340
      (errpipe_read, errpipe_write) = os.pipe()
341
      try:
342
        try:
343
          # First fork
344
          pid = os.fork()
345
          if pid == 0:
346
            try:
347
              # Child process, won't return
348
              _StartDaemonChild(errpipe_read, errpipe_write,
349
                                pidpipe_read, pidpipe_write,
350
                                cmd, cmd_env, cwd,
351
                                output, output_fd, pidfile)
352
            finally:
353
              # Well, maybe child process failed
354
              os._exit(1) # pylint: disable=W0212
355
        finally:
356
          utils_wrapper.CloseFdNoError(errpipe_write)
357

    
358
        # Wait for daemon to be started (or an error message to
359
        # arrive) and read up to 100 KB as an error message
360
        errormsg = utils_wrapper.RetryOnSignal(os.read, errpipe_read,
361
                                               100 * 1024)
362
      finally:
363
        utils_wrapper.CloseFdNoError(errpipe_read)
364
    finally:
365
      utils_wrapper.CloseFdNoError(pidpipe_write)
366

    
367
    # Read up to 128 bytes for PID
368
    pidtext = utils_wrapper.RetryOnSignal(os.read, pidpipe_read, 128)
369
  finally:
370
    utils_wrapper.CloseFdNoError(pidpipe_read)
371

    
372
  # Try to avoid zombies by waiting for child process
373
  try:
374
    os.waitpid(pid, 0)
375
  except OSError:
376
    pass
377

    
378
  if errormsg:
379
    raise errors.OpExecError("Error when starting daemon process: %r" %
380
                             errormsg)
381

    
382
  try:
383
    return int(pidtext)
384
  except (ValueError, TypeError), err:
385
    raise errors.OpExecError("Error while trying to parse PID %r: %s" %
386
                             (pidtext, err))
387

    
388

    
389
def _StartDaemonChild(errpipe_read, errpipe_write,
390
                      pidpipe_read, pidpipe_write,
391
                      args, env, cwd,
392
                      output, fd_output, pidfile):
393
  """Child process for starting daemon.
394

395
  """
396
  try:
397
    # Close parent's side
398
    utils_wrapper.CloseFdNoError(errpipe_read)
399
    utils_wrapper.CloseFdNoError(pidpipe_read)
400

    
401
    # First child process
402
    SetupDaemonEnv()
403

    
404
    # And fork for the second time
405
    pid = os.fork()
406
    if pid != 0:
407
      # Exit first child process
408
      os._exit(0) # pylint: disable=W0212
409

    
410
    # Make sure pipe is closed on execv* (and thereby notifies
411
    # original process)
412
    utils_wrapper.SetCloseOnExecFlag(errpipe_write, True)
413

    
414
    # List of file descriptors to be left open
415
    noclose_fds = [errpipe_write]
416

    
417
    # Open PID file
418
    if pidfile:
419
      fd_pidfile = utils_io.WritePidFile(pidfile)
420

    
421
      # Keeping the file open to hold the lock
422
      noclose_fds.append(fd_pidfile)
423

    
424
      utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
425
    else:
426
      fd_pidfile = None
427

    
428
    SetupDaemonFDs(output, fd_output)
429

    
430
    # Send daemon PID to parent
431
    utils_wrapper.RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
432

    
433
    # Close all file descriptors except stdio and error message pipe
434
    CloseFDs(noclose_fds=noclose_fds)
435

    
436
    # Change working directory
437
    os.chdir(cwd)
438

    
439
    if env is None:
440
      os.execvp(args[0], args)
441
    else:
442
      os.execvpe(args[0], args, env)
443
  except: # pylint: disable=W0702
444
    try:
445
      # Report errors to original process
446
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
447
    except: # pylint: disable=W0702
448
      # Ignore errors in error handling
449
      pass
450

    
451
  os._exit(1) # pylint: disable=W0212
452

    
453

    
454
def WriteErrorToFD(fd, err):
455
  """Possibly write an error message to a fd.
456

457
  @type fd: None or int (file descriptor)
458
  @param fd: if not None, the error will be written to this fd
459
  @param err: string, the error message
460

461
  """
462
  if fd is None:
463
    return
464

    
465
  if not err:
466
    err = "<unknown error>"
467

    
468
  utils_wrapper.RetryOnSignal(os.write, fd, err)
469

    
470

    
471
def _CheckIfAlive(child):
472
  """Raises L{utils_retry.RetryAgain} if child is still alive.
473

474
  @raises utils_retry.RetryAgain: If child is still alive
475

476
  """
477
  if child.poll() is None:
478
    raise utils_retry.RetryAgain()
479

    
480

    
481
def _WaitForProcess(child, timeout):
482
  """Waits for the child to terminate or until we reach timeout.
483

484
  """
485
  try:
486
    utils_retry.Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout),
487
                      args=[child])
488
  except utils_retry.RetryTimeout:
489
    pass
490

    
491

    
492
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
493
                input_fd,
494
                _linger_timeout=constants.CHILD_LINGER_TIMEOUT,
495
                _postfork_fn=None):
496
  """Run a command and return its output.
497

498
  @type  cmd: string or list
499
  @param cmd: Command to run
500
  @type env: dict
501
  @param env: The environment to use
502
  @type via_shell: bool
503
  @param via_shell: if we should run via the shell
504
  @type cwd: string
505
  @param cwd: the working directory for the program
506
  @type interactive: boolean
507
  @param interactive: Run command interactive (without piping)
508
  @type timeout: int
509
  @param timeout: Timeout after the programm gets terminated
510
  @type noclose_fds: list
511
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
512
                      open for the child process
513
  @type input_fd: C{file}-like object or numeric file descriptor
514
  @param input_fd: File descriptor for process' standard input
515
  @param _postfork_fn: Function run after fork but before timeout (unittest)
516
  @rtype: tuple
517
  @return: (out, err, status)
518

519
  """
520
  poller = select.poll()
521

    
522
  if interactive:
523
    stderr = None
524
    stdout = None
525
  else:
526
    stderr = subprocess.PIPE
527
    stdout = subprocess.PIPE
528

    
529
  if input_fd:
530
    stdin = input_fd
531
  elif interactive:
532
    stdin = None
533
  else:
534
    stdin = subprocess.PIPE
535

    
536
  if noclose_fds:
537
    preexec_fn = lambda: CloseFDs(noclose_fds)
538
    close_fds = False
539
  else:
540
    preexec_fn = None
541
    close_fds = True
542

    
543
  child = subprocess.Popen(cmd, shell=via_shell,
544
                           stderr=stderr,
545
                           stdout=stdout,
546
                           stdin=stdin,
547
                           close_fds=close_fds, env=env,
548
                           cwd=cwd,
549
                           preexec_fn=preexec_fn)
550

    
551
  if _postfork_fn:
552
    _postfork_fn(child.pid)
553

    
554
  out = StringIO()
555
  err = StringIO()
556

    
557
  linger_timeout = None
558

    
559
  if timeout is None:
560
    poll_timeout = None
561
  else:
562
    poll_timeout = utils_algo.RunningTimeout(timeout, True).Remaining
563

    
564
  msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
565
                 (cmd, child.pid))
566
  msg_linger = ("Command %s (%d) run into linger timeout, killing" %
567
                (cmd, child.pid))
568

    
569
  timeout_action = _TIMEOUT_NONE
570

    
571
  # subprocess: "If the stdin argument is PIPE, this attribute is a file object
572
  # that provides input to the child process. Otherwise, it is None."
573
  assert (stdin == subprocess.PIPE) ^ (child.stdin is None), \
574
    "subprocess' stdin did not behave as documented"
575

    
576
  if not interactive:
577
    if child.stdin is not None:
578
      child.stdin.close()
579
    poller.register(child.stdout, select.POLLIN)
580
    poller.register(child.stderr, select.POLLIN)
581
    fdmap = {
582
      child.stdout.fileno(): (out, child.stdout),
583
      child.stderr.fileno(): (err, child.stderr),
584
      }
585
    for fd in fdmap:
586
      utils_wrapper.SetNonblockFlag(fd, True)
587

    
588
    while fdmap:
589
      if poll_timeout:
590
        pt = poll_timeout() * 1000
591
        if pt < 0:
592
          if linger_timeout is None:
593
            logging.warning(msg_timeout)
594
            if child.poll() is None:
595
              timeout_action = _TIMEOUT_TERM
596
              utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid,
597
                                                  signal.SIGTERM)
598
            linger_timeout = \
599
              utils_algo.RunningTimeout(_linger_timeout, True).Remaining
600
          pt = linger_timeout() * 1000
601
          if pt < 0:
602
            break
603
      else:
604
        pt = None
605

    
606
      pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
607

    
608
      for fd, event in pollresult:
609
        if event & select.POLLIN or event & select.POLLPRI:
610
          data = fdmap[fd][1].read()
611
          # no data from read signifies EOF (the same as POLLHUP)
612
          if not data:
613
            poller.unregister(fd)
614
            del fdmap[fd]
615
            continue
616
          fdmap[fd][0].write(data)
617
        if (event & select.POLLNVAL or event & select.POLLHUP or
618
            event & select.POLLERR):
619
          poller.unregister(fd)
620
          del fdmap[fd]
621

    
622
  if timeout is not None:
623
    assert callable(poll_timeout)
624

    
625
    # We have no I/O left but it might still run
626
    if child.poll() is None:
627
      _WaitForProcess(child, poll_timeout())
628

    
629
    # Terminate if still alive after timeout
630
    if child.poll() is None:
631
      if linger_timeout is None:
632
        logging.warning(msg_timeout)
633
        timeout_action = _TIMEOUT_TERM
634
        utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
635
        lt = _linger_timeout
636
      else:
637
        lt = linger_timeout()
638
      _WaitForProcess(child, lt)
639

    
640
    # Okay, still alive after timeout and linger timeout? Kill it!
641
    if child.poll() is None:
642
      timeout_action = _TIMEOUT_KILL
643
      logging.warning(msg_linger)
644
      utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
645

    
646
  out = out.getvalue()
647
  err = err.getvalue()
648

    
649
  status = child.wait()
650
  return out, err, status, timeout_action
651

    
652

    
653
def _RunCmdFile(cmd, env, via_shell, output, cwd, noclose_fds):
654
  """Run a command and save its output to a file.
655

656
  @type  cmd: string or list
657
  @param cmd: Command to run
658
  @type env: dict
659
  @param env: The environment to use
660
  @type via_shell: bool
661
  @param via_shell: if we should run via the shell
662
  @type output: str
663
  @param output: the filename in which to save the output
664
  @type cwd: string
665
  @param cwd: the working directory for the program
666
  @type noclose_fds: list
667
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
668
                      open for the child process
669
  @rtype: int
670
  @return: the exit status
671

672
  """
673
  fh = open(output, "a")
674

    
675
  if noclose_fds:
676
    preexec_fn = lambda: CloseFDs(noclose_fds + [fh.fileno()])
677
    close_fds = False
678
  else:
679
    preexec_fn = None
680
    close_fds = True
681

    
682
  try:
683
    child = subprocess.Popen(cmd, shell=via_shell,
684
                             stderr=subprocess.STDOUT,
685
                             stdout=fh,
686
                             stdin=subprocess.PIPE,
687
                             close_fds=close_fds, env=env,
688
                             cwd=cwd,
689
                             preexec_fn=preexec_fn)
690

    
691
    child.stdin.close()
692
    status = child.wait()
693
  finally:
694
    fh.close()
695
  return status
696

    
697

    
698
def RunParts(dir_name, env=None, reset_env=False):
699
  """Run Scripts or programs in a directory
700

701
  @type dir_name: string
702
  @param dir_name: absolute path to a directory
703
  @type env: dict
704
  @param env: The environment to use
705
  @type reset_env: boolean
706
  @param reset_env: whether to reset or keep the default os environment
707
  @rtype: list of tuples
708
  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
709

710
  """
711
  rr = []
712

    
713
  try:
714
    dir_contents = utils_io.ListVisibleFiles(dir_name)
715
  except OSError, err:
716
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
717
    return rr
718

    
719
  for relname in sorted(dir_contents):
720
    fname = utils_io.PathJoin(dir_name, relname)
721
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
722
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
723
      rr.append((relname, constants.RUNPARTS_SKIP, None))
724
    else:
725
      try:
726
        result = RunCmd([fname], env=env, reset_env=reset_env)
727
      except Exception, err: # pylint: disable=W0703
728
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
729
      else:
730
        rr.append((relname, constants.RUNPARTS_RUN, result))
731

    
732
  return rr
733

    
734

    
735
def _GetProcStatusPath(pid):
736
  """Returns the path for a PID's proc status file.
737

738
  @type pid: int
739
  @param pid: Process ID
740
  @rtype: string
741

742
  """
743
  return "/proc/%d/status" % pid
744

    
745

    
746
def IsProcessAlive(pid):
747
  """Check if a given pid exists on the system.
748

749
  @note: zombie status is not handled, so zombie processes
750
      will be returned as alive
751
  @type pid: int
752
  @param pid: the process ID to check
753
  @rtype: boolean
754
  @return: True if the process exists
755

756
  """
757
  def _TryStat(name):
758
    try:
759
      os.stat(name)
760
      return True
761
    except EnvironmentError, err:
762
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
763
        return False
764
      elif err.errno == errno.EINVAL:
765
        raise utils_retry.RetryAgain(err)
766
      raise
767

    
768
  assert isinstance(pid, int), "pid must be an integer"
769
  if pid <= 0:
770
    return False
771

    
772
  # /proc in a multiprocessor environment can have strange behaviors.
773
  # Retry the os.stat a few times until we get a good result.
774
  try:
775
    return utils_retry.Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
776
                             args=[_GetProcStatusPath(pid)])
777
  except utils_retry.RetryTimeout, err:
778
    err.RaiseInner()
779

    
780

    
781
def _ParseSigsetT(sigset):
782
  """Parse a rendered sigset_t value.
783

784
  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
785
  function.
786

787
  @type sigset: string
788
  @param sigset: Rendered signal set from /proc/$pid/status
789
  @rtype: set
790
  @return: Set of all enabled signal numbers
791

792
  """
793
  result = set()
794

    
795
  signum = 0
796
  for ch in reversed(sigset):
797
    chv = int(ch, 16)
798

    
799
    # The following could be done in a loop, but it's easier to read and
800
    # understand in the unrolled form
801
    if chv & 1:
802
      result.add(signum + 1)
803
    if chv & 2:
804
      result.add(signum + 2)
805
    if chv & 4:
806
      result.add(signum + 3)
807
    if chv & 8:
808
      result.add(signum + 4)
809

    
810
    signum += 4
811

    
812
  return result
813

    
814

    
815
def _GetProcStatusField(pstatus, field):
816
  """Retrieves a field from the contents of a proc status file.
817

818
  @type pstatus: string
819
  @param pstatus: Contents of /proc/$pid/status
820
  @type field: string
821
  @param field: Name of field whose value should be returned
822
  @rtype: string
823

824
  """
825
  for line in pstatus.splitlines():
826
    parts = line.split(":", 1)
827

    
828
    if len(parts) < 2 or parts[0] != field:
829
      continue
830

    
831
    return parts[1].strip()
832

    
833
  return None
834

    
835

    
836
def IsProcessHandlingSignal(pid, signum, status_path=None):
837
  """Checks whether a process is handling a signal.
838

839
  @type pid: int
840
  @param pid: Process ID
841
  @type signum: int
842
  @param signum: Signal number
843
  @rtype: bool
844

845
  """
846
  if status_path is None:
847
    status_path = _GetProcStatusPath(pid)
848

    
849
  try:
850
    proc_status = utils_io.ReadFile(status_path)
851
  except EnvironmentError, err:
852
    # In at least one case, reading /proc/$pid/status failed with ESRCH.
853
    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
854
      return False
855
    raise
856

    
857
  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
858
  if sigcgt is None:
859
    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
860

    
861
  # Now check whether signal is handled
862
  return signum in _ParseSigsetT(sigcgt)
863

    
864

    
865
def Daemonize(logfile):
866
  """Daemonize the current process.
867

868
  This detaches the current process from the controlling terminal and
869
  runs it in the background as a daemon.
870

871
  @type logfile: str
872
  @param logfile: the logfile to which we should redirect stdout/stderr
873
  @rtype: tuple; (int, callable)
874
  @return: File descriptor of pipe(2) which must be closed to notify parent
875
    process and a callable to reopen log files
876

877
  """
878
  # pylint: disable=W0212
879
  # yes, we really want os._exit
880

    
881
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
882
  # least abstract the pipe functionality between them
883

    
884
  # Create pipe for sending error messages
885
  (rpipe, wpipe) = os.pipe()
886

    
887
  # this might fail
888
  pid = os.fork()
889
  if (pid == 0):  # The first child.
890
    SetupDaemonEnv()
891

    
892
    # this might fail
893
    pid = os.fork() # Fork a second child.
894
    if (pid == 0):  # The second child.
895
      utils_wrapper.CloseFdNoError(rpipe)
896
    else:
897
      # exit() or _exit()?  See below.
898
      os._exit(0) # Exit parent (the first child) of the second child.
899
  else:
900
    utils_wrapper.CloseFdNoError(wpipe)
901
    # Wait for daemon to be started (or an error message to
902
    # arrive) and read up to 100 KB as an error message
903
    errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024)
904
    if errormsg:
905
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
906
      rcode = 1
907
    else:
908
      rcode = 0
909
    os._exit(rcode) # Exit parent of the first child.
910

    
911
  reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
912

    
913
  # Open logs for the first time
914
  reopen_fn()
915

    
916
  return (wpipe, reopen_fn)
917

    
918

    
919
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
920
                waitpid=False):
921
  """Kill a process given by its pid.
922

923
  @type pid: int
924
  @param pid: The PID to terminate.
925
  @type signal_: int
926
  @param signal_: The signal to send, by default SIGTERM
927
  @type timeout: int
928
  @param timeout: The timeout after which, if the process is still alive,
929
                  a SIGKILL will be sent. If not positive, no such checking
930
                  will be done
931
  @type waitpid: boolean
932
  @param waitpid: If true, we should waitpid on this process after
933
      sending signals, since it's our own child and otherwise it
934
      would remain as zombie
935

936
  """
937
  def _helper(pid, signal_, wait):
938
    """Simple helper to encapsulate the kill/waitpid sequence"""
939
    if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
940
      try:
941
        os.waitpid(pid, os.WNOHANG)
942
      except OSError:
943
        pass
944

    
945
  if pid <= 0:
946
    # kill with pid=0 == suicide
947
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
948

    
949
  if not IsProcessAlive(pid):
950
    return
951

    
952
  _helper(pid, signal_, waitpid)
953

    
954
  if timeout <= 0:
955
    return
956

    
957
  def _CheckProcess():
958
    if not IsProcessAlive(pid):
959
      return
960

    
961
    try:
962
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
963
    except OSError:
964
      raise utils_retry.RetryAgain()
965

    
966
    if result_pid > 0:
967
      return
968

    
969
    raise utils_retry.RetryAgain()
970

    
971
  try:
972
    # Wait up to $timeout seconds
973
    utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
974
  except utils_retry.RetryTimeout:
975
    pass
976

    
977
  if IsProcessAlive(pid):
978
    # Kill process if it's still alive
979
    _helper(pid, signal.SIGKILL, waitpid)
980

    
981

    
982
def RunInSeparateProcess(fn, *args):
983
  """Runs a function in a separate process.
984

985
  Note: Only boolean return values are supported.
986

987
  @type fn: callable
988
  @param fn: Function to be called
989
  @rtype: bool
990
  @return: Function's result
991

992
  """
993
  pid = os.fork()
994
  if pid == 0:
995
    # Child process
996
    try:
997
      # In case the function uses temporary files
998
      utils_wrapper.ResetTempfileModule()
999

    
1000
      # Call function
1001
      result = int(bool(fn(*args)))
1002
      assert result in (0, 1)
1003
    except: # pylint: disable=W0702
1004
      logging.exception("Error while calling function in separate process")
1005
      # 0 and 1 are reserved for the return value
1006
      result = 33
1007

    
1008
    os._exit(result) # pylint: disable=W0212
1009

    
1010
  # Parent process
1011

    
1012
  # Avoid zombies and check exit code
1013
  (_, status) = os.waitpid(pid, 0)
1014

    
1015
  if os.WIFSIGNALED(status):
1016
    exitcode = None
1017
    signum = os.WTERMSIG(status)
1018
  else:
1019
    exitcode = os.WEXITSTATUS(status)
1020
    signum = None
1021

    
1022
  if not (exitcode in (0, 1) and signum is None):
1023
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
1024
                              (exitcode, signum))
1025

    
1026
  return bool(exitcode)
1027

    
1028

    
1029
def CloseFDs(noclose_fds=None):
1030
  """Close file descriptors.
1031

1032
  This closes all file descriptors above 2 (i.e. except
1033
  stdin/out/err).
1034

1035
  @type noclose_fds: list or None
1036
  @param noclose_fds: if given, it denotes a list of file descriptor
1037
      that should not be closed
1038

1039
  """
1040
  # Default maximum for the number of available file descriptors.
1041
  if 'SC_OPEN_MAX' in os.sysconf_names:
1042
    try:
1043
      MAXFD = os.sysconf('SC_OPEN_MAX')
1044
      if MAXFD < 0:
1045
        MAXFD = 1024
1046
    except OSError:
1047
      MAXFD = 1024
1048
  else:
1049
    MAXFD = 1024
1050

    
1051
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1052
  if (maxfd == resource.RLIM_INFINITY):
1053
    maxfd = MAXFD
1054

    
1055
  # Iterate through and close all file descriptors (except the standard ones)
1056
  for fd in range(3, maxfd):
1057
    if noclose_fds and fd in noclose_fds:
1058
      continue
1059
    utils_wrapper.CloseFdNoError(fd)