Statistics
| Branch: | Tag: | Revision:

root / lib / utils / process.py @ 09b72783

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

    
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
  @type postfork_fn: Callable receiving PID as parameter
176
  @param postfork_fn: Callback run after fork but before timeout
177
  @rtype: L{RunResult}
178
  @return: RunResult instance
179
  @raise errors.ProgrammerError: if we call this when forks are disabled
180

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

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

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

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

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

    
208
  cmd_env = _BuildCmdEnvironment(env, reset_env)
209

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

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

    
238
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
239

    
240

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

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

247
  @param cwd: the directory to which to chdir
248
  @param umask: the umask to setup
249

250
  """
251
  os.chdir(cwd)
252
  os.umask(umask)
253
  os.setsid()
254

    
255

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

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

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

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

    
270
  output_close = True
271

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

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

    
289
  if devnull_fd > 2:
290
    utils_wrapper.CloseFdNoError(devnull_fd)
291

    
292
  if output_close and output_fd > 2:
293
    utils_wrapper.CloseFdNoError(output_fd)
294

    
295

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

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

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

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

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

    
328
  strcmd = utils_text.ShellQuoteArgs(cmd)
329

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

    
335
  cmd_env = _BuildCmdEnvironment(env, False)
336

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

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

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

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

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

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

    
390

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

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

    
403
    # First child process
404
    SetupDaemonEnv()
405

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

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

    
416
    # List of file descriptors to be left open
417
    noclose_fds = [errpipe_write]
418

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

    
423
      # Keeping the file open to hold the lock
424
      noclose_fds.append(fd_pidfile)
425

    
426
      utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
427
    else:
428
      fd_pidfile = None
429

    
430
    SetupDaemonFDs(output, fd_output)
431

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

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

    
438
    # Change working directory
439
    os.chdir(cwd)
440

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

    
453
  os._exit(1) # pylint: disable=W0212
454

    
455

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

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

463
  """
464
  if fd is None:
465
    return
466

    
467
  if not err:
468
    err = "<unknown error>"
469

    
470
  utils_wrapper.RetryOnSignal(os.write, fd, err)
471

    
472

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

476
  @raises utils_retry.RetryAgain: If child is still alive
477

478
  """
479
  if child.poll() is None:
480
    raise utils_retry.RetryAgain()
481

    
482

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

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

    
493

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

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

521
  """
522
  poller = select.poll()
523

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

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

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

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

    
553
  if postfork_fn:
554
    postfork_fn(child.pid)
555

    
556
  out = StringIO()
557
  err = StringIO()
558

    
559
  linger_timeout = None
560

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

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

    
571
  timeout_action = _TIMEOUT_NONE
572

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

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

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

    
608
      pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
609

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

    
624
  if timeout is not None:
625
    assert callable(poll_timeout)
626

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

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

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

    
648
  out = out.getvalue()
649
  err = err.getvalue()
650

    
651
  status = child.wait()
652
  return out, err, status, timeout_action
653

    
654

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

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

674
  """
675
  fh = open(output, "a")
676

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

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

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

    
699

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

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

712
  """
713
  rr = []
714

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

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

    
734
  return rr
735

    
736

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

740
  @type pid: int
741
  @param pid: Process ID
742
  @rtype: string
743

744
  """
745
  return "/proc/%d/status" % pid
746

    
747

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

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

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

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

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

    
782

    
783
def _ParseSigsetT(sigset):
784
  """Parse a rendered sigset_t value.
785

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

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

794
  """
795
  result = set()
796

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

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

    
812
    signum += 4
813

    
814
  return result
815

    
816

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

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

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

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

    
833
    return parts[1].strip()
834

    
835
  return None
836

    
837

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

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

847
  """
848
  if status_path is None:
849
    status_path = _GetProcStatusPath(pid)
850

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

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

    
863
  # Now check whether signal is handled
864
  return signum in _ParseSigsetT(sigcgt)
865

    
866

    
867
def Daemonize(logfile):
868
  """Daemonize the current process.
869

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

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

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

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

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

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

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

    
913
  reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
914

    
915
  # Open logs for the first time
916
  reopen_fn()
917

    
918
  return (wpipe, reopen_fn)
919

    
920

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

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

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

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

    
951
  if not IsProcessAlive(pid):
952
    return
953

    
954
  _helper(pid, signal_, waitpid)
955

    
956
  if timeout <= 0:
957
    return
958

    
959
  def _CheckProcess():
960
    if not IsProcessAlive(pid):
961
      return
962

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

    
968
    if result_pid > 0:
969
      return
970

    
971
    raise utils_retry.RetryAgain()
972

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

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

    
983

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

987
  Note: Only boolean return values are supported.
988

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

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

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

    
1010
    os._exit(result) # pylint: disable=W0212
1011

    
1012
  # Parent process
1013

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

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

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

    
1028
  return bool(exitcode)
1029

    
1030

    
1031
def CloseFDs(noclose_fds=None):
1032
  """Close file descriptors.
1033

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

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

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

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

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