Statistics
| Branch: | Tag: | Revision:

root / lib / utils / process.py @ adec726e

History | View | Annotate | Download (30.1 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 IsDaemonAlive(name):
786
  """Determines whether a daemon is alive
787

788
  @type name: string
789
  @param name: daemon name
790

791
  @rtype: boolean
792
  @return: True if daemon is running, False otherwise
793

794
  """
795
  return IsProcessAlive(utils_io.ReadPidFile(utils_io.DaemonPidFileName(name)))
796

    
797

    
798
def _ParseSigsetT(sigset):
799
  """Parse a rendered sigset_t value.
800

801
  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
802
  function.
803

804
  @type sigset: string
805
  @param sigset: Rendered signal set from /proc/$pid/status
806
  @rtype: set
807
  @return: Set of all enabled signal numbers
808

809
  """
810
  result = set()
811

    
812
  signum = 0
813
  for ch in reversed(sigset):
814
    chv = int(ch, 16)
815

    
816
    # The following could be done in a loop, but it's easier to read and
817
    # understand in the unrolled form
818
    if chv & 1:
819
      result.add(signum + 1)
820
    if chv & 2:
821
      result.add(signum + 2)
822
    if chv & 4:
823
      result.add(signum + 3)
824
    if chv & 8:
825
      result.add(signum + 4)
826

    
827
    signum += 4
828

    
829
  return result
830

    
831

    
832
def _GetProcStatusField(pstatus, field):
833
  """Retrieves a field from the contents of a proc status file.
834

835
  @type pstatus: string
836
  @param pstatus: Contents of /proc/$pid/status
837
  @type field: string
838
  @param field: Name of field whose value should be returned
839
  @rtype: string
840

841
  """
842
  for line in pstatus.splitlines():
843
    parts = line.split(":", 1)
844

    
845
    if len(parts) < 2 or parts[0] != field:
846
      continue
847

    
848
    return parts[1].strip()
849

    
850
  return None
851

    
852

    
853
def IsProcessHandlingSignal(pid, signum, status_path=None):
854
  """Checks whether a process is handling a signal.
855

856
  @type pid: int
857
  @param pid: Process ID
858
  @type signum: int
859
  @param signum: Signal number
860
  @rtype: bool
861

862
  """
863
  if status_path is None:
864
    status_path = _GetProcStatusPath(pid)
865

    
866
  try:
867
    proc_status = utils_io.ReadFile(status_path)
868
  except EnvironmentError, err:
869
    # In at least one case, reading /proc/$pid/status failed with ESRCH.
870
    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
871
      return False
872
    raise
873

    
874
  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
875
  if sigcgt is None:
876
    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
877

    
878
  # Now check whether signal is handled
879
  return signum in _ParseSigsetT(sigcgt)
880

    
881

    
882
def Daemonize(logfile):
883
  """Daemonize the current process.
884

885
  This detaches the current process from the controlling terminal and
886
  runs it in the background as a daemon.
887

888
  @type logfile: str
889
  @param logfile: the logfile to which we should redirect stdout/stderr
890
  @rtype: tuple; (int, callable)
891
  @return: File descriptor of pipe(2) which must be closed to notify parent
892
    process and a callable to reopen log files
893

894
  """
895
  # pylint: disable=W0212
896
  # yes, we really want os._exit
897

    
898
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
899
  # least abstract the pipe functionality between them
900

    
901
  # Create pipe for sending error messages
902
  (rpipe, wpipe) = os.pipe()
903

    
904
  # this might fail
905
  pid = os.fork()
906
  if (pid == 0):  # The first child.
907
    SetupDaemonEnv()
908

    
909
    # this might fail
910
    pid = os.fork() # Fork a second child.
911
    if (pid == 0):  # The second child.
912
      utils_wrapper.CloseFdNoError(rpipe)
913
    else:
914
      # exit() or _exit()?  See below.
915
      os._exit(0) # Exit parent (the first child) of the second child.
916
  else:
917
    utils_wrapper.CloseFdNoError(wpipe)
918
    # Wait for daemon to be started (or an error message to
919
    # arrive) and read up to 100 KB as an error message
920
    errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024)
921
    if errormsg:
922
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
923
      rcode = 1
924
    else:
925
      rcode = 0
926
    os._exit(rcode) # Exit parent of the first child.
927

    
928
  reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
929

    
930
  # Open logs for the first time
931
  reopen_fn()
932

    
933
  return (wpipe, reopen_fn)
934

    
935

    
936
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
937
                waitpid=False):
938
  """Kill a process given by its pid.
939

940
  @type pid: int
941
  @param pid: The PID to terminate.
942
  @type signal_: int
943
  @param signal_: The signal to send, by default SIGTERM
944
  @type timeout: int
945
  @param timeout: The timeout after which, if the process is still alive,
946
                  a SIGKILL will be sent. If not positive, no such checking
947
                  will be done
948
  @type waitpid: boolean
949
  @param waitpid: If true, we should waitpid on this process after
950
      sending signals, since it's our own child and otherwise it
951
      would remain as zombie
952

953
  """
954
  def _helper(pid, signal_, wait):
955
    """Simple helper to encapsulate the kill/waitpid sequence"""
956
    if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
957
      try:
958
        os.waitpid(pid, os.WNOHANG)
959
      except OSError:
960
        pass
961

    
962
  if pid <= 0:
963
    # kill with pid=0 == suicide
964
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
965

    
966
  if not IsProcessAlive(pid):
967
    return
968

    
969
  _helper(pid, signal_, waitpid)
970

    
971
  if timeout <= 0:
972
    return
973

    
974
  def _CheckProcess():
975
    if not IsProcessAlive(pid):
976
      return
977

    
978
    try:
979
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
980
    except OSError:
981
      raise utils_retry.RetryAgain()
982

    
983
    if result_pid > 0:
984
      return
985

    
986
    raise utils_retry.RetryAgain()
987

    
988
  try:
989
    # Wait up to $timeout seconds
990
    utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
991
  except utils_retry.RetryTimeout:
992
    pass
993

    
994
  if IsProcessAlive(pid):
995
    # Kill process if it's still alive
996
    _helper(pid, signal.SIGKILL, waitpid)
997

    
998

    
999
def RunInSeparateProcess(fn, *args):
1000
  """Runs a function in a separate process.
1001

1002
  Note: Only boolean return values are supported.
1003

1004
  @type fn: callable
1005
  @param fn: Function to be called
1006
  @rtype: bool
1007
  @return: Function's result
1008

1009
  """
1010
  pid = os.fork()
1011
  if pid == 0:
1012
    # Child process
1013
    try:
1014
      # In case the function uses temporary files
1015
      utils_wrapper.ResetTempfileModule()
1016

    
1017
      # Call function
1018
      result = int(bool(fn(*args)))
1019
      assert result in (0, 1)
1020
    except: # pylint: disable=W0702
1021
      logging.exception("Error while calling function in separate process")
1022
      # 0 and 1 are reserved for the return value
1023
      result = 33
1024

    
1025
    os._exit(result) # pylint: disable=W0212
1026

    
1027
  # Parent process
1028

    
1029
  # Avoid zombies and check exit code
1030
  (_, status) = os.waitpid(pid, 0)
1031

    
1032
  if os.WIFSIGNALED(status):
1033
    exitcode = None
1034
    signum = os.WTERMSIG(status)
1035
  else:
1036
    exitcode = os.WEXITSTATUS(status)
1037
    signum = None
1038

    
1039
  if not (exitcode in (0, 1) and signum is None):
1040
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
1041
                              (exitcode, signum))
1042

    
1043
  return bool(exitcode)
1044

    
1045

    
1046
def CloseFDs(noclose_fds=None):
1047
  """Close file descriptors.
1048

1049
  This closes all file descriptors above 2 (i.e. except
1050
  stdin/out/err).
1051

1052
  @type noclose_fds: list or None
1053
  @param noclose_fds: if given, it denotes a list of file descriptor
1054
      that should not be closed
1055

1056
  """
1057
  # Default maximum for the number of available file descriptors.
1058
  if 'SC_OPEN_MAX' in os.sysconf_names:
1059
    try:
1060
      MAXFD = os.sysconf('SC_OPEN_MAX')
1061
      if MAXFD < 0:
1062
        MAXFD = 1024
1063
    except OSError:
1064
      MAXFD = 1024
1065
  else:
1066
    MAXFD = 1024
1067

    
1068
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1069
  if (maxfd == resource.RLIM_INFINITY):
1070
    maxfd = MAXFD
1071

    
1072
  # Iterate through and close all file descriptors (except the standard ones)
1073
  for fd in range(3, maxfd):
1074
    if noclose_fds and fd in noclose_fds:
1075
      continue
1076
    utils_wrapper.CloseFdNoError(fd)