Statistics
| Branch: | Tag: | Revision:

root / lib / utils / process.py @ e1a6abf9

History | View | Annotate | Download (29.8 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
    else:
114
      self.fail_reason = None
115

    
116
    if self.failed:
117
      logging.debug("Command '%s' failed (%s); output: %s",
118
                    self.cmd, self.fail_reason, self.output)
119

    
120
  def _GetOutput(self):
121
    """Returns the combined stdout and stderr for easier usage.
122

123
    """
124
    return self.stdout + self.stderr
125

    
126
  output = property(_GetOutput, None, None, "Return full output")
127

    
128

    
129
def _BuildCmdEnvironment(env, reset):
130
  """Builds the environment for an external program.
131

132
  """
133
  if reset:
134
    cmd_env = {}
135
  else:
136
    cmd_env = os.environ.copy()
137
    cmd_env["LC_ALL"] = "C"
138

    
139
  if env is not None:
140
    cmd_env.update(env)
141

    
142
  return cmd_env
143

    
144

    
145
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
146
           interactive=False, timeout=None, noclose_fds=None,
147
           input_fd=None, postfork_fn=None):
148
  """Execute a (shell) command.
149

150
  The command should not read from its standard input, as it will be
151
  closed.
152

153
  @type cmd: string or list
154
  @param cmd: Command to run
155
  @type env: dict
156
  @param env: Additional environment variables
157
  @type output: str
158
  @param output: if desired, the output of the command can be
159
      saved in a file instead of the RunResult instance; this
160
      parameter denotes the file name (if not None)
161
  @type cwd: string
162
  @param cwd: if specified, will be used as the working
163
      directory for the command; the default will be /
164
  @type reset_env: boolean
165
  @param reset_env: whether to reset or keep the default os environment
166
  @type interactive: boolean
167
  @param interactive: whether we pipe stdin, stdout and stderr
168
                      (default behaviour) or run the command interactive
169
  @type timeout: int
170
  @param timeout: If not None, timeout in seconds until child process gets
171
                  killed
172
  @type noclose_fds: list
173
  @param noclose_fds: list of additional (fd >=3) file descriptors to leave
174
                      open for the child process
175
  @type input_fd: C{file}-like object or numeric file descriptor
176
  @param input_fd: File descriptor for process' standard input
177
  @type postfork_fn: Callable receiving PID as parameter
178
  @param postfork_fn: Callback run after fork but before timeout
179
  @rtype: L{RunResult}
180
  @return: RunResult instance
181
  @raise errors.ProgrammerError: if we call this when forks are disabled
182

183
  """
184
  if _no_fork:
185
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
186

    
187
  if output and interactive:
188
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
189
                                 " not be provided at the same time")
190

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

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

    
205
  if output:
206
    logging.info("RunCmd %s, output file '%s'", strcmd, output)
207
  else:
208
    logging.info("RunCmd %s", strcmd)
209

    
210
  cmd_env = _BuildCmdEnvironment(env, reset_env)
211

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

    
233
  if status >= 0:
234
    exitcode = status
235
    signal_ = None
236
  else:
237
    exitcode = None
238
    signal_ = -status
239

    
240
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
241

    
242

    
243
def SetupDaemonEnv(cwd="/", umask=077):
244
  """Setup a daemon's environment.
245

246
  This should be called between the first and second fork, due to
247
  setsid usage.
248

249
  @param cwd: the directory to which to chdir
250
  @param umask: the umask to setup
251

252
  """
253
  os.chdir(cwd)
254
  os.umask(umask)
255
  os.setsid()
256

    
257

    
258
def SetupDaemonFDs(output_file, output_fd):
259
  """Setups up a daemon's file descriptors.
260

261
  @param output_file: if not None, the file to which to redirect
262
      stdout/stderr
263
  @param output_fd: if not None, the file descriptor for stdout/stderr
264

265
  """
266
  # check that at most one is defined
267
  assert [output_file, output_fd].count(None) >= 1
268

    
269
  # Open /dev/null (read-only, only for stdin)
270
  devnull_fd = os.open(os.devnull, os.O_RDONLY)
271

    
272
  output_close = True
273

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

    
286
  # Redirect standard I/O
287
  os.dup2(devnull_fd, 0)
288
  os.dup2(output_fd, 1)
289
  os.dup2(output_fd, 2)
290

    
291
  if devnull_fd > 2:
292
    utils_wrapper.CloseFdNoError(devnull_fd)
293

    
294
  if output_close and output_fd > 2:
295
    utils_wrapper.CloseFdNoError(output_fd)
296

    
297

    
298
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
299
                pidfile=None):
300
  """Start a daemon process after forking twice.
301

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

318
  """
319
  if _no_fork:
320
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
321
                                 " disabled")
322

    
323
  if output and not (bool(output) ^ (output_fd is not None)):
324
    raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
325
                                 " specified")
326

    
327
  if isinstance(cmd, basestring):
328
    cmd = ["/bin/sh", "-c", cmd]
329

    
330
  strcmd = utils_text.ShellQuoteArgs(cmd)
331

    
332
  if output:
333
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
334
  else:
335
    logging.debug("StartDaemon %s", strcmd)
336

    
337
  cmd_env = _BuildCmdEnvironment(env, False)
338

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

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

    
371
    # Read up to 128 bytes for PID
372
    pidtext = utils_wrapper.RetryOnSignal(os.read, pidpipe_read, 128)
373
  finally:
374
    utils_wrapper.CloseFdNoError(pidpipe_read)
375

    
376
  # Try to avoid zombies by waiting for child process
377
  try:
378
    os.waitpid(pid, 0)
379
  except OSError:
380
    pass
381

    
382
  if errormsg:
383
    raise errors.OpExecError("Error when starting daemon process: %r" %
384
                             errormsg)
385

    
386
  try:
387
    return int(pidtext)
388
  except (ValueError, TypeError), err:
389
    raise errors.OpExecError("Error while trying to parse PID %r: %s" %
390
                             (pidtext, err))
391

    
392

    
393
def _StartDaemonChild(errpipe_read, errpipe_write,
394
                      pidpipe_read, pidpipe_write,
395
                      args, env, cwd,
396
                      output, fd_output, pidfile):
397
  """Child process for starting daemon.
398

399
  """
400
  try:
401
    # Close parent's side
402
    utils_wrapper.CloseFdNoError(errpipe_read)
403
    utils_wrapper.CloseFdNoError(pidpipe_read)
404

    
405
    # First child process
406
    SetupDaemonEnv()
407

    
408
    # And fork for the second time
409
    pid = os.fork()
410
    if pid != 0:
411
      # Exit first child process
412
      os._exit(0) # pylint: disable=W0212
413

    
414
    # Make sure pipe is closed on execv* (and thereby notifies
415
    # original process)
416
    utils_wrapper.SetCloseOnExecFlag(errpipe_write, True)
417

    
418
    # List of file descriptors to be left open
419
    noclose_fds = [errpipe_write]
420

    
421
    # Open PID file
422
    if pidfile:
423
      fd_pidfile = utils_io.WritePidFile(pidfile)
424

    
425
      # Keeping the file open to hold the lock
426
      noclose_fds.append(fd_pidfile)
427

    
428
      utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
429
    else:
430
      fd_pidfile = None
431

    
432
    SetupDaemonFDs(output, fd_output)
433

    
434
    # Send daemon PID to parent
435
    utils_wrapper.RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
436

    
437
    # Close all file descriptors except stdio and error message pipe
438
    CloseFDs(noclose_fds=noclose_fds)
439

    
440
    # Change working directory
441
    os.chdir(cwd)
442

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

    
455
  os._exit(1) # pylint: disable=W0212
456

    
457

    
458
def WriteErrorToFD(fd, err):
459
  """Possibly write an error message to a fd.
460

461
  @type fd: None or int (file descriptor)
462
  @param fd: if not None, the error will be written to this fd
463
  @param err: string, the error message
464

465
  """
466
  if fd is None:
467
    return
468

    
469
  if not err:
470
    err = "<unknown error>"
471

    
472
  utils_wrapper.RetryOnSignal(os.write, fd, err)
473

    
474

    
475
def _CheckIfAlive(child):
476
  """Raises L{utils_retry.RetryAgain} if child is still alive.
477

478
  @raises utils_retry.RetryAgain: If child is still alive
479

480
  """
481
  if child.poll() is None:
482
    raise utils_retry.RetryAgain()
483

    
484

    
485
def _WaitForProcess(child, timeout):
486
  """Waits for the child to terminate or until we reach timeout.
487

488
  """
489
  try:
490
    utils_retry.Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout),
491
                      args=[child])
492
  except utils_retry.RetryTimeout:
493
    pass
494

    
495

    
496
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds,
497
                input_fd, postfork_fn=None,
498
                _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
499
  """Run a command and return its output.
500

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

523
  """
524
  poller = select.poll()
525

    
526
  if interactive:
527
    stderr = None
528
    stdout = None
529
  else:
530
    stderr = subprocess.PIPE
531
    stdout = subprocess.PIPE
532

    
533
  if input_fd:
534
    stdin = input_fd
535
  elif interactive:
536
    stdin = None
537
  else:
538
    stdin = subprocess.PIPE
539

    
540
  if noclose_fds:
541
    preexec_fn = lambda: CloseFDs(noclose_fds)
542
    close_fds = False
543
  else:
544
    preexec_fn = None
545
    close_fds = True
546

    
547
  child = subprocess.Popen(cmd, shell=via_shell,
548
                           stderr=stderr,
549
                           stdout=stdout,
550
                           stdin=stdin,
551
                           close_fds=close_fds, env=env,
552
                           cwd=cwd,
553
                           preexec_fn=preexec_fn)
554

    
555
  if postfork_fn:
556
    postfork_fn(child.pid)
557

    
558
  out = StringIO()
559
  err = StringIO()
560

    
561
  linger_timeout = None
562

    
563
  if timeout is None:
564
    poll_timeout = None
565
  else:
566
    poll_timeout = utils_algo.RunningTimeout(timeout, True).Remaining
567

    
568
  msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
569
                 (cmd, child.pid))
570
  msg_linger = ("Command %s (%d) run into linger timeout, killing" %
571
                (cmd, child.pid))
572

    
573
  timeout_action = _TIMEOUT_NONE
574

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

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

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

    
610
      pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
611

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

    
626
  if timeout is not None:
627
    assert callable(poll_timeout)
628

    
629
    # We have no I/O left but it might still run
630
    if child.poll() is None:
631
      _WaitForProcess(child, poll_timeout())
632

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

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

    
650
  out = out.getvalue()
651
  err = err.getvalue()
652

    
653
  status = child.wait()
654
  return out, err, status, timeout_action
655

    
656

    
657
def _RunCmdFile(cmd, env, via_shell, output, cwd, noclose_fds):
658
  """Run a command and save its output to a file.
659

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

676
  """
677
  fh = open(output, "a")
678

    
679
  if noclose_fds:
680
    preexec_fn = lambda: CloseFDs(noclose_fds + [fh.fileno()])
681
    close_fds = False
682
  else:
683
    preexec_fn = None
684
    close_fds = True
685

    
686
  try:
687
    child = subprocess.Popen(cmd, shell=via_shell,
688
                             stderr=subprocess.STDOUT,
689
                             stdout=fh,
690
                             stdin=subprocess.PIPE,
691
                             close_fds=close_fds, env=env,
692
                             cwd=cwd,
693
                             preexec_fn=preexec_fn)
694

    
695
    child.stdin.close()
696
    status = child.wait()
697
  finally:
698
    fh.close()
699
  return status
700

    
701

    
702
def RunParts(dir_name, env=None, reset_env=False):
703
  """Run Scripts or programs in a directory
704

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

714
  """
715
  rr = []
716

    
717
  try:
718
    dir_contents = utils_io.ListVisibleFiles(dir_name)
719
  except OSError, err:
720
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
721
    return rr
722

    
723
  for relname in sorted(dir_contents):
724
    fname = utils_io.PathJoin(dir_name, relname)
725
    if not (constants.EXT_PLUGIN_MASK.match(relname) is not None and
726
            utils_wrapper.IsExecutable(fname)):
727
      rr.append((relname, constants.RUNPARTS_SKIP, None))
728
    else:
729
      try:
730
        result = RunCmd([fname], env=env, reset_env=reset_env)
731
      except Exception, err: # pylint: disable=W0703
732
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
733
      else:
734
        rr.append((relname, constants.RUNPARTS_RUN, result))
735

    
736
  return rr
737

    
738

    
739
def _GetProcStatusPath(pid):
740
  """Returns the path for a PID's proc status file.
741

742
  @type pid: int
743
  @param pid: Process ID
744
  @rtype: string
745

746
  """
747
  return "/proc/%d/status" % pid
748

    
749

    
750
def IsProcessAlive(pid):
751
  """Check if a given pid exists on the system.
752

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

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

    
772
  assert isinstance(pid, int), "pid must be an integer"
773
  if pid <= 0:
774
    return False
775

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

    
784

    
785
def _ParseSigsetT(sigset):
786
  """Parse a rendered sigset_t value.
787

788
  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
789
  function.
790

791
  @type sigset: string
792
  @param sigset: Rendered signal set from /proc/$pid/status
793
  @rtype: set
794
  @return: Set of all enabled signal numbers
795

796
  """
797
  result = set()
798

    
799
  signum = 0
800
  for ch in reversed(sigset):
801
    chv = int(ch, 16)
802

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

    
814
    signum += 4
815

    
816
  return result
817

    
818

    
819
def _GetProcStatusField(pstatus, field):
820
  """Retrieves a field from the contents of a proc status file.
821

822
  @type pstatus: string
823
  @param pstatus: Contents of /proc/$pid/status
824
  @type field: string
825
  @param field: Name of field whose value should be returned
826
  @rtype: string
827

828
  """
829
  for line in pstatus.splitlines():
830
    parts = line.split(":", 1)
831

    
832
    if len(parts) < 2 or parts[0] != field:
833
      continue
834

    
835
    return parts[1].strip()
836

    
837
  return None
838

    
839

    
840
def IsProcessHandlingSignal(pid, signum, status_path=None):
841
  """Checks whether a process is handling a signal.
842

843
  @type pid: int
844
  @param pid: Process ID
845
  @type signum: int
846
  @param signum: Signal number
847
  @rtype: bool
848

849
  """
850
  if status_path is None:
851
    status_path = _GetProcStatusPath(pid)
852

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

    
861
  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
862
  if sigcgt is None:
863
    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
864

    
865
  # Now check whether signal is handled
866
  return signum in _ParseSigsetT(sigcgt)
867

    
868

    
869
def Daemonize(logfile):
870
  """Daemonize the current process.
871

872
  This detaches the current process from the controlling terminal and
873
  runs it in the background as a daemon.
874

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

881
  """
882
  # pylint: disable=W0212
883
  # yes, we really want os._exit
884

    
885
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
886
  # least abstract the pipe functionality between them
887

    
888
  # Create pipe for sending error messages
889
  (rpipe, wpipe) = os.pipe()
890

    
891
  # this might fail
892
  pid = os.fork()
893
  if (pid == 0):  # The first child.
894
    SetupDaemonEnv()
895

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

    
915
  reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
916

    
917
  # Open logs for the first time
918
  reopen_fn()
919

    
920
  return (wpipe, reopen_fn)
921

    
922

    
923
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
924
                waitpid=False):
925
  """Kill a process given by its pid.
926

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

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

    
949
  if pid <= 0:
950
    # kill with pid=0 == suicide
951
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
952

    
953
  if not IsProcessAlive(pid):
954
    return
955

    
956
  _helper(pid, signal_, waitpid)
957

    
958
  if timeout <= 0:
959
    return
960

    
961
  def _CheckProcess():
962
    if not IsProcessAlive(pid):
963
      return
964

    
965
    try:
966
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
967
    except OSError:
968
      raise utils_retry.RetryAgain()
969

    
970
    if result_pid > 0:
971
      return
972

    
973
    raise utils_retry.RetryAgain()
974

    
975
  try:
976
    # Wait up to $timeout seconds
977
    utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
978
  except utils_retry.RetryTimeout:
979
    pass
980

    
981
  if IsProcessAlive(pid):
982
    # Kill process if it's still alive
983
    _helper(pid, signal.SIGKILL, waitpid)
984

    
985

    
986
def RunInSeparateProcess(fn, *args):
987
  """Runs a function in a separate process.
988

989
  Note: Only boolean return values are supported.
990

991
  @type fn: callable
992
  @param fn: Function to be called
993
  @rtype: bool
994
  @return: Function's result
995

996
  """
997
  pid = os.fork()
998
  if pid == 0:
999
    # Child process
1000
    try:
1001
      # In case the function uses temporary files
1002
      utils_wrapper.ResetTempfileModule()
1003

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

    
1012
    os._exit(result) # pylint: disable=W0212
1013

    
1014
  # Parent process
1015

    
1016
  # Avoid zombies and check exit code
1017
  (_, status) = os.waitpid(pid, 0)
1018

    
1019
  if os.WIFSIGNALED(status):
1020
    exitcode = None
1021
    signum = os.WTERMSIG(status)
1022
  else:
1023
    exitcode = os.WEXITSTATUS(status)
1024
    signum = None
1025

    
1026
  if not (exitcode in (0, 1) and signum is None):
1027
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
1028
                              (exitcode, signum))
1029

    
1030
  return bool(exitcode)
1031

    
1032

    
1033
def CloseFDs(noclose_fds=None):
1034
  """Close file descriptors.
1035

1036
  This closes all file descriptors above 2 (i.e. except
1037
  stdin/out/err).
1038

1039
  @type noclose_fds: list or None
1040
  @param noclose_fds: if given, it denotes a list of file descriptor
1041
      that should not be closed
1042

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

    
1055
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1056
  if (maxfd == resource.RLIM_INFINITY):
1057
    maxfd = MAXFD
1058

    
1059
  # Iterate through and close all file descriptors (except the standard ones)
1060
  for fd in range(3, maxfd):
1061
    if noclose_fds and fd in noclose_fds:
1062
      continue
1063
    utils_wrapper.CloseFdNoError(fd)