__init__.py 52.7 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
#
Iustin Pop's avatar
Iustin Pop committed
2
3
#

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


22
23
24
25
"""Ganeti utility module.

This module holds functions that can be used in both daemons (all) and
the command line scripts.
26

Iustin Pop's avatar
Iustin Pop committed
27
28
29
30
"""


import os
31
import sys
Iustin Pop's avatar
Iustin Pop committed
32
import time
33
import subprocess
Iustin Pop's avatar
Iustin Pop committed
34
35
36
37
import re
import socket
import tempfile
import shutil
38
import errno
39
import pwd
Guido Trotter's avatar
Guido Trotter committed
40
import itertools
41
42
import select
import fcntl
43
import resource
44
import logging
Michael Hanselmann's avatar
Michael Hanselmann committed
45
import signal
46
47
import datetime
import calendar
48
49

from cStringIO import StringIO
Iustin Pop's avatar
Iustin Pop committed
50
51

from ganeti import errors
Iustin Pop's avatar
Iustin Pop committed
52
from ganeti import constants
53
from ganeti import compat
Iustin Pop's avatar
Iustin Pop committed
54

55
from ganeti.utils.algo import * # pylint: disable-msg=W0401
56
from ganeti.utils.retry import * # pylint: disable-msg=W0401
57
from ganeti.utils.text import * # pylint: disable-msg=W0401
58
from ganeti.utils.mlock import * # pylint: disable-msg=W0401
59
from ganeti.utils.log import * # pylint: disable-msg=W0401
60
from ganeti.utils.hash import * # pylint: disable-msg=W0401
61
from ganeti.utils.wrapper import * # pylint: disable-msg=W0401
62
from ganeti.utils.filelock import * # pylint: disable-msg=W0401
63
from ganeti.utils.io import * # pylint: disable-msg=W0401
64
from ganeti.utils.x509 import * # pylint: disable-msg=W0401
65

66
67

#: when set to True, L{RunCmd} is disabled
68
_no_fork = False
69

70
71
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"

72
73
_VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")

74
75
76
UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
                     '[a-f0-9]{4}-[a-f0-9]{12}$')

René Nussbaumer's avatar
René Nussbaumer committed
77
78
79
80
(_TIMEOUT_NONE,
 _TIMEOUT_TERM,
 _TIMEOUT_KILL) = range(3)

81
82
83
#: Shell param checker regexp
_SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$")

84

85
86
87
88
89
90
91
92
93
def DisableFork():
  """Disables the use of fork(2).

  """
  global _no_fork # pylint: disable-msg=W0603

  _no_fork = True


Iustin Pop's avatar
Iustin Pop committed
94
class RunResult(object):
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
  """Holds the result of running external programs.

  @type exit_code: int
  @ivar exit_code: the exit code of the program, or None (if the program
      didn't exit())
  @type signal: int or None
  @ivar signal: the signal that caused the program to finish, or None
      (if the program wasn't terminated by a signal)
  @type stdout: str
  @ivar stdout: the standard output of the program
  @type stderr: str
  @ivar stderr: the standard error of the program
  @type failed: boolean
  @ivar failed: True in case the program was
      terminated by a signal or exited with a non-zero exit code
  @ivar fail_reason: a string detailing the termination reason
Iustin Pop's avatar
Iustin Pop committed
111
112
113
114
115
116

  """
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
               "failed", "fail_reason", "cmd"]


René Nussbaumer's avatar
René Nussbaumer committed
117
118
  def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
               timeout):
Iustin Pop's avatar
Iustin Pop committed
119
120
    self.cmd = cmd
    self.exit_code = exit_code
Iustin Pop's avatar
Iustin Pop committed
121
    self.signal = signal_
Iustin Pop's avatar
Iustin Pop committed
122
123
    self.stdout = stdout
    self.stderr = stderr
Iustin Pop's avatar
Iustin Pop committed
124
    self.failed = (signal_ is not None or exit_code != 0)
Iustin Pop's avatar
Iustin Pop committed
125

René Nussbaumer's avatar
René Nussbaumer committed
126
    fail_msgs = []
Iustin Pop's avatar
Iustin Pop committed
127
    if self.signal is not None:
René Nussbaumer's avatar
René Nussbaumer committed
128
      fail_msgs.append("terminated by signal %s" % self.signal)
Iustin Pop's avatar
Iustin Pop committed
129
    elif self.exit_code is not None:
René Nussbaumer's avatar
René Nussbaumer committed
130
      fail_msgs.append("exited with exit code %s" % self.exit_code)
Iustin Pop's avatar
Iustin Pop committed
131
    else:
René Nussbaumer's avatar
René Nussbaumer committed
132
133
134
135
136
137
138
139
140
141
142
      fail_msgs.append("unable to determine termination reason")

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

    if fail_msgs and self.failed:
      self.fail_reason = CommaJoin(fail_msgs)
Iustin Pop's avatar
Iustin Pop committed
143

144
145
146
    if self.failed:
      logging.debug("Command '%s' failed (%s); output: %s",
                    self.cmd, self.fail_reason, self.output)
147

Iustin Pop's avatar
Iustin Pop committed
148
149
150
151
152
153
154
155
156
  def _GetOutput(self):
    """Returns the combined stdout and stderr for easier usage.

    """
    return self.stdout + self.stderr

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


157
def _BuildCmdEnvironment(env, reset):
158
159
160
  """Builds the environment for an external program.

  """
161
162
163
164
165
166
  if reset:
    cmd_env = {}
  else:
    cmd_env = os.environ.copy()
    cmd_env["LC_ALL"] = "C"

167
168
  if env is not None:
    cmd_env.update(env)
169

170
171
172
  return cmd_env


173
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
René Nussbaumer's avatar
René Nussbaumer committed
174
           interactive=False, timeout=None):
Iustin Pop's avatar
Iustin Pop committed
175
176
177
178
179
  """Execute a (shell) command.

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

180
  @type cmd: string or list
181
  @param cmd: Command to run
182
  @type env: dict
183
  @param env: Additional environment variables
184
  @type output: str
185
  @param output: if desired, the output of the command can be
186
187
      saved in a file instead of the RunResult instance; this
      parameter denotes the file name (if not None)
188
189
190
  @type cwd: string
  @param cwd: if specified, will be used as the working
      directory for the command; the default will be /
Guido Trotter's avatar
Guido Trotter committed
191
192
  @type reset_env: boolean
  @param reset_env: whether to reset or keep the default os environment
193
194
195
  @type interactive: boolean
  @param interactive: weather we pipe stdin, stdout and stderr
                      (default behaviour) or run the command interactive
René Nussbaumer's avatar
René Nussbaumer committed
196
197
198
  @type timeout: int
  @param timeout: If not None, timeout in seconds until child process gets
                  killed
199
  @rtype: L{RunResult}
200
  @return: RunResult instance
Michael Hanselmann's avatar
Michael Hanselmann committed
201
  @raise errors.ProgrammerError: if we call this when forks are disabled
Iustin Pop's avatar
Iustin Pop committed
202
203

  """
204
  if _no_fork:
205
206
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")

207
208
209
210
  if output and interactive:
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
                                 " not be provided at the same time")

211
212
213
214
  if isinstance(cmd, basestring):
    strcmd = cmd
    shell = True
  else:
Iustin Pop's avatar
Iustin Pop committed
215
    cmd = [str(val) for val in cmd]
216
    strcmd = ShellQuoteArgs(cmd)
217
    shell = False
218
219
220

  if output:
    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
221
  else:
222
    logging.debug("RunCmd %s", strcmd)
223

224
  cmd_env = _BuildCmdEnvironment(env, reset_env)
225

226
227
  try:
    if output is None:
René Nussbaumer's avatar
René Nussbaumer committed
228
229
      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
                                                     interactive, timeout)
230
    else:
René Nussbaumer's avatar
René Nussbaumer committed
231
      timeout_action = _TIMEOUT_NONE
232
233
234
235
236
237
238
239
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
      out = err = ""
  except OSError, err:
    if err.errno == errno.ENOENT:
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
                               (strcmd, err))
    else:
      raise
240
241
242
243
244
245
246
247

  if status >= 0:
    exitcode = status
    signal_ = None
  else:
    exitcode = None
    signal_ = -status

René Nussbaumer's avatar
René Nussbaumer committed
248
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
249

250

251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def SetupDaemonEnv(cwd="/", umask=077):
  """Setup a daemon's environment.

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

  @param cwd: the directory to which to chdir
  @param umask: the umask to setup

  """
  os.chdir(cwd)
  os.umask(umask)
  os.setsid()


266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def SetupDaemonFDs(output_file, output_fd):
  """Setups up a daemon's file descriptors.

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

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

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

  if output_fd is not None:
    pass
  elif output_file is not None:
    # Open output file
    try:
      output_fd = os.open(output_file,
                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
    except EnvironmentError, err:
      raise Exception("Opening output file failed: %s" % err)
  else:
    output_fd = os.open(os.devnull, os.O_WRONLY)

  # Redirect standard I/O
  os.dup2(devnull_fd, 0)
  os.dup2(output_fd, 1)
  os.dup2(output_fd, 2)


298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
                pidfile=None):
  """Start a daemon process after forking twice.

  @type cmd: string or list
  @param cmd: Command to run
  @type env: dict
  @param env: Additional environment variables
  @type cwd: string
  @param cwd: Working directory for the program
  @type output: string
  @param output: Path to file in which to save the output
  @type output_fd: int
  @param output_fd: File descriptor for output
  @type pidfile: string
  @param pidfile: Process ID file
  @rtype: int
  @return: Daemon process ID
  @raise errors.ProgrammerError: if we call this when forks are disabled

  """
319
  if _no_fork:
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
                                 " disabled")

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

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

  strcmd = ShellQuoteArgs(cmd)

  if output:
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
  else:
    logging.debug("StartDaemon %s", strcmd)

337
  cmd_env = _BuildCmdEnvironment(env, False)
338
339
340
341
342
343
344
345
346
347
348
349
350
351

  # Create pipe for sending PID back
  (pidpipe_read, pidpipe_write) = os.pipe()
  try:
    try:
      # Create pipe for sending error messages
      (errpipe_read, errpipe_write) = os.pipe()
      try:
        try:
          # First fork
          pid = os.fork()
          if pid == 0:
            try:
              # Child process, won't return
352
353
354
355
              _StartDaemonChild(errpipe_read, errpipe_write,
                                pidpipe_read, pidpipe_write,
                                cmd, cmd_env, cwd,
                                output, output_fd, pidfile)
356
357
            finally:
              # Well, maybe child process failed
358
              os._exit(1) # pylint: disable-msg=W0212
359
        finally:
360
          CloseFdNoError(errpipe_write)
361

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

    # Read up to 128 bytes for PID
    pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
  finally:
373
    CloseFdNoError(pidpipe_read)
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391

  # Try to avoid zombies by waiting for child process
  try:
    os.waitpid(pid, 0)
  except OSError:
    pass

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

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


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

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

    # First child process
405
    SetupDaemonEnv()
406
407
408
409
410
411
412

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

413
414
    # Make sure pipe is closed on execv* (and thereby notifies
    # original process)
415
416
417
418
419
420
421
    SetCloseOnExecFlag(errpipe_write, True)

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

    # Open PID file
    if pidfile:
422
      fd_pidfile = WritePidFile(pidfile)
423
424
425
426
427
428
429
430

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

      SetCloseOnExecFlag(fd_pidfile, False)
    else:
      fd_pidfile = None

431
    SetupDaemonFDs(output, fd_output)
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448

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

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

    # Change working directory
    os.chdir(cwd)

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

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


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
def WriteErrorToFD(fd, err):
  """Possibly write an error message to a fd.

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

  """
  if fd is None:
    return

  if not err:
    err = "<unknown error>"

  RetryOnSignal(os.write, fd, err)


René Nussbaumer's avatar
René Nussbaumer committed
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
def _CheckIfAlive(child):
  """Raises L{RetryAgain} if child is still alive.

  @raises RetryAgain: If child is still alive

  """
  if child.poll() is None:
    raise RetryAgain()


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

  """
  try:
    Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout), args=[child])
  except RetryTimeout:
    pass


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

  @type  cmd: string or list
  @param cmd: Command to run
  @type env: dict
  @param env: The environment to use
  @type via_shell: bool
  @param via_shell: if we should run via the shell
504
505
  @type cwd: string
  @param cwd: the working directory for the program
506
507
  @type interactive: boolean
  @param interactive: Run command interactive (without piping)
René Nussbaumer's avatar
René Nussbaumer committed
508
509
  @type timeout: int
  @param timeout: Timeout after the programm gets terminated
510
511
512
513
  @rtype: tuple
  @return: (out, err, status)

  """
514
  poller = select.poll()
515
516
517
518
519
520
521
522

  stderr = subprocess.PIPE
  stdout = subprocess.PIPE
  stdin = subprocess.PIPE

  if interactive:
    stderr = stdout = stdin = None

523
  child = subprocess.Popen(cmd, shell=via_shell,
524
525
526
                           stderr=stderr,
                           stdout=stdout,
                           stdin=stdin,
527
528
                           close_fds=True, env=env,
                           cwd=cwd)
529

530
531
  out = StringIO()
  err = StringIO()
René Nussbaumer's avatar
René Nussbaumer committed
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546

  linger_timeout = None

  if timeout is None:
    poll_timeout = None
  else:
    poll_timeout = RunningTimeout(timeout, True).Remaining

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

  timeout_action = _TIMEOUT_NONE

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

    while fdmap:
René Nussbaumer's avatar
René Nussbaumer committed
559
      if poll_timeout:
560
561
        pt = poll_timeout() * 1000
        if pt < 0:
René Nussbaumer's avatar
René Nussbaumer committed
562
563
564
565
566
567
          if linger_timeout is None:
            logging.warning(msg_timeout)
            if child.poll() is None:
              timeout_action = _TIMEOUT_TERM
              IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
            linger_timeout = RunningTimeout(_linger_timeout, True).Remaining
568
569
          pt = linger_timeout() * 1000
          if pt < 0:
René Nussbaumer's avatar
René Nussbaumer committed
570
571
572
573
574
            break
      else:
        pt = None

      pollresult = RetryOnSignal(poller.poll, pt)
575
576
577
578
579
580
581
582
583
584
585
586

      for fd, event in pollresult:
        if event & select.POLLIN or event & select.POLLPRI:
          data = fdmap[fd][1].read()
          # no data from read signifies EOF (the same as POLLHUP)
          if not data:
            poller.unregister(fd)
            del fdmap[fd]
            continue
          fdmap[fd][0].write(data)
        if (event & select.POLLNVAL or event & select.POLLHUP or
            event & select.POLLERR):
587
588
589
          poller.unregister(fd)
          del fdmap[fd]

René Nussbaumer's avatar
René Nussbaumer committed
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
  if timeout is not None:
    assert callable(poll_timeout)

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

    # Terminate if still alive after timeout
    if child.poll() is None:
      if linger_timeout is None:
        logging.warning(msg_timeout)
        timeout_action = _TIMEOUT_TERM
        IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
        lt = _linger_timeout
      else:
        lt = linger_timeout()
      _WaitForProcess(child, lt)

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

614
615
  out = out.getvalue()
  err = err.getvalue()
Iustin Pop's avatar
Iustin Pop committed
616
617

  status = child.wait()
René Nussbaumer's avatar
René Nussbaumer committed
618
  return out, err, status, timeout_action
Iustin Pop's avatar
Iustin Pop committed
619

620

621
def _RunCmdFile(cmd, env, via_shell, output, cwd):
622
623
624
625
626
627
628
629
630
631
  """Run a command and save its output to a file.

  @type  cmd: string or list
  @param cmd: Command to run
  @type env: dict
  @param env: The environment to use
  @type via_shell: bool
  @param via_shell: if we should run via the shell
  @type output: str
  @param output: the filename in which to save the output
632
633
  @type cwd: string
  @param cwd: the working directory for the program
634
635
636
637
638
639
640
641
642
643
  @rtype: int
  @return: the exit status

  """
  fh = open(output, "a")
  try:
    child = subprocess.Popen(cmd, shell=via_shell,
                             stderr=subprocess.STDOUT,
                             stdout=fh,
                             stdin=subprocess.PIPE,
644
645
                             close_fds=True, env=env,
                             cwd=cwd)
646
647
648
649
650
651

    child.stdin.close()
    status = child.wait()
  finally:
    fh.close()
  return status
Iustin Pop's avatar
Iustin Pop committed
652
653


654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
def RunParts(dir_name, env=None, reset_env=False):
  """Run Scripts or programs in a directory

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

  """
  rr = []

  try:
    dir_contents = ListVisibleFiles(dir_name)
  except OSError, err:
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
    return rr

  for relname in sorted(dir_contents):
676
    fname = PathJoin(dir_name, relname)
677
678
679
680
681
682
683
684
685
686
687
688
689
690
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
      rr.append((relname, constants.RUNPARTS_SKIP, None))
    else:
      try:
        result = RunCmd([fname], env=env, reset_env=reset_env)
      except Exception, err: # pylint: disable-msg=W0703
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
      else:
        rr.append((relname, constants.RUNPARTS_RUN, result))

  return rr


691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
def ResetTempfileModule():
  """Resets the random name generator of the tempfile module.

  This function should be called after C{os.fork} in the child process to
  ensure it creates a newly seeded random generator. Otherwise it would
  generate the same random parts as the parent process. If several processes
  race for the creation of a temporary file, this could lead to one not getting
  a temporary name.

  """
  # pylint: disable-msg=W0212
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
    tempfile._once_lock.acquire()
    try:
      # Reset random name generator
      tempfile._name_sequence = None
    finally:
      tempfile._once_lock.release()
  else:
    logging.critical("The tempfile module misses at least one of the"
                     " '_once_lock' and '_name_sequence' attributes")


714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
def ForceDictType(target, key_types, allowed_values=None):
  """Force the values of a dict to have certain types.

  @type target: dict
  @param target: the dict to update
  @type key_types: dict
  @param key_types: dict mapping target dict keys to types
                    in constants.ENFORCEABLE_TYPES
  @type allowed_values: list
  @keyword allowed_values: list of specially allowed values

  """
  if allowed_values is None:
    allowed_values = []

729
730
731
732
  if not isinstance(target, dict):
    msg = "Expected dictionary, got '%s'" % target
    raise errors.TypeEnforcementError(msg)

733
734
735
736
737
738
739
740
  for key in target:
    if key not in key_types:
      msg = "Unknown key '%s'" % key
      raise errors.TypeEnforcementError(msg)

    if target[key] in allowed_values:
      continue

Iustin Pop's avatar
Iustin Pop committed
741
742
743
    ktype = key_types[key]
    if ktype not in constants.ENFORCEABLE_TYPES:
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
744
745
      raise errors.ProgrammerError(msg)

746
747
748
749
    if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
      if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
        pass
      elif not isinstance(target[key], basestring):
750
751
752
753
754
        if isinstance(target[key], bool) and not target[key]:
          target[key] = ''
        else:
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
          raise errors.TypeEnforcementError(msg)
Iustin Pop's avatar
Iustin Pop committed
755
    elif ktype == constants.VTYPE_BOOL:
756
757
758
759
760
761
762
763
764
765
766
767
      if isinstance(target[key], basestring) and target[key]:
        if target[key].lower() == constants.VALUE_FALSE:
          target[key] = False
        elif target[key].lower() == constants.VALUE_TRUE:
          target[key] = True
        else:
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
          raise errors.TypeEnforcementError(msg)
      elif target[key]:
        target[key] = True
      else:
        target[key] = False
Iustin Pop's avatar
Iustin Pop committed
768
    elif ktype == constants.VTYPE_SIZE:
769
770
771
772
773
774
      try:
        target[key] = ParseUnit(target[key])
      except errors.UnitParseError, err:
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
              (key, target[key], err)
        raise errors.TypeEnforcementError(msg)
Iustin Pop's avatar
Iustin Pop committed
775
    elif ktype == constants.VTYPE_INT:
776
777
778
779
780
781
782
      try:
        target[key] = int(target[key])
      except (ValueError, TypeError):
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
        raise errors.TypeEnforcementError(msg)


783
784
785
786
787
788
789
790
791
792
793
def _GetProcStatusPath(pid):
  """Returns the path for a PID's proc status file.

  @type pid: int
  @param pid: Process ID
  @rtype: string

  """
  return "/proc/%d/status" % pid


Iustin Pop's avatar
Iustin Pop committed
794
795
796
def IsProcessAlive(pid):
  """Check if a given pid exists on the system.

797
798
  @note: zombie status is not handled, so zombie processes
      will be returned as alive
799
800
801
802
  @type pid: int
  @param pid: the process ID to check
  @rtype: boolean
  @return: True if the process exists
Iustin Pop's avatar
Iustin Pop committed
803
804

  """
805
806
807
808
809
810
811
812
813
814
815
816
  def _TryStat(name):
    try:
      os.stat(name)
      return True
    except EnvironmentError, err:
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
        return False
      elif err.errno == errno.EINVAL:
        raise RetryAgain(err)
      raise

  assert isinstance(pid, int), "pid must be an integer"
817
818
819
  if pid <= 0:
    return False

820
821
  # /proc in a multiprocessor environment can have strange behaviors.
  # Retry the os.stat a few times until we get a good result.
Iustin Pop's avatar
Iustin Pop committed
822
  try:
823
824
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
                 args=[_GetProcStatusPath(pid)])
825
826
  except RetryTimeout, err:
    err.RaiseInner()
Iustin Pop's avatar
Iustin Pop committed
827
828


829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
def _ParseSigsetT(sigset):
  """Parse a rendered sigset_t value.

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

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

  """
  result = set()

  signum = 0
  for ch in reversed(sigset):
    chv = int(ch, 16)

    # The following could be done in a loop, but it's easier to read and
    # understand in the unrolled form
    if chv & 1:
      result.add(signum + 1)
    if chv & 2:
      result.add(signum + 2)
    if chv & 4:
      result.add(signum + 3)
    if chv & 8:
      result.add(signum + 4)

    signum += 4

  return result


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

  @type pstatus: string
  @param pstatus: Contents of /proc/$pid/status
  @type field: string
  @param field: Name of field whose value should be returned
  @rtype: string

  """
  for line in pstatus.splitlines():
    parts = line.split(":", 1)

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

    return parts[1].strip()

  return None


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

  @type pid: int
  @param pid: Process ID
  @type signum: int
  @param signum: Signal number
  @rtype: bool

  """
  if status_path is None:
    status_path = _GetProcStatusPath(pid)

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

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

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


913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
def ValidateServiceName(name):
  """Validate the given service name.

  @type name: number or string
  @param name: Service name or port specification

  """
  try:
    numport = int(name)
  except (ValueError, TypeError):
    # Non-numeric service name
    valid = _VALID_SERVICE_NAME_RE.match(name)
  else:
    # Numeric port (protocols other than TCP or UDP might need adjustments
    # here)
    valid = (numport >= 0 and numport < (1 << 16))

  if not valid:
    raise errors.OpPrereqError("Invalid service name '%s'" % name,
                               errors.ECODE_INVAL)

  return name


Iustin Pop's avatar
Iustin Pop committed
937
938
939
def ListVolumeGroups():
  """List volume groups and their size

940
941
942
943
  @rtype: dict
  @return:
       Dictionary with keys volume name and values
       the size of the volume
Iustin Pop's avatar
Iustin Pop committed
944
945
946
947
948
949
950
951
952
953
954
955
956

  """
  command = "vgs --noheadings --units m --nosuffix -o name,size"
  result = RunCmd(command)
  retval = {}
  if result.failed:
    return retval

  for line in result.stdout.splitlines():
    try:
      name, size = line.split()
      size = int(float(size))
    except (IndexError, ValueError), err:
957
      logging.error("Invalid output from vgs (%s): %s", err, line)
Iustin Pop's avatar
Iustin Pop committed
958
959
960
961
962
963
964
965
966
967
      continue

    retval[name] = size

  return retval


def BridgeExists(bridge):
  """Check whether the given bridge exists in the system

968
969
970
971
  @type bridge: str
  @param bridge: the bridge name to check
  @rtype: boolean
  @return: True if it does
Iustin Pop's avatar
Iustin Pop committed
972
973
974
975
976
977
978
979

  """
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)


def TryConvert(fn, val):
  """Try to convert a value ignoring errors.

980
981
982
983
984
985
986
987
988
989
  This function tries to apply function I{fn} to I{val}. If no
  C{ValueError} or C{TypeError} exceptions are raised, it will return
  the result, else it will return the original value. Any other
  exceptions are propagated to the caller.

  @type fn: callable
  @param fn: function to apply to the value
  @param val: the value to be converted
  @return: The converted value if the conversion was successful,
      otherwise the original value.
Iustin Pop's avatar
Iustin Pop committed
990
991
992
993

  """
  try:
    nv = fn(val)
Michael Hanselmann's avatar
Michael Hanselmann committed
994
  except (ValueError, TypeError):
Iustin Pop's avatar
Iustin Pop committed
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
    nv = val
  return nv


def IsValidShellParam(word):
  """Verifies is the given word is safe from the shell's p.o.v.

  This means that we can pass this to a command via the shell and be
  sure that it doesn't alter the command line and is passed as such to
  the actual command.

  Note that we are overly restrictive here, in order to be on the safe
  side.

1009
1010
1011
1012
1013
  @type word: str
  @param word: the word to check
  @rtype: boolean
  @return: True if the word is 'safe'

Iustin Pop's avatar
Iustin Pop committed
1014
  """
1015
  return bool(_SHELLPARAM_REGEX.match(word))
Iustin Pop's avatar
Iustin Pop committed
1016
1017
1018
1019
1020
1021
1022


def BuildShellCmd(template, *args):
  """Build a safe shell command line from the given arguments.

  This function will check all arguments in the args list so that they
  are valid shell parameters (i.e. they don't contain shell
Michael Hanselmann's avatar
Michael Hanselmann committed
1023
  metacharacters). If everything is ok, it will return the result of
Iustin Pop's avatar
Iustin Pop committed
1024
1025
  template % args.

1026
1027
1028
1029
1030
1031
  @type template: str
  @param template: the string holding the template for the
      string formatting
  @rtype: str
  @return: the expanded command line

Iustin Pop's avatar
Iustin Pop committed
1032
1033
1034
  """
  for word in args:
    if not IsValidShellParam(word):
1035
1036
      raise errors.ProgrammerError("Shell argument '%s' contains"
                                   " invalid characters" % word)
Iustin Pop's avatar
Iustin Pop committed
1037
1038
1039
  return template % args


1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
def ParseCpuMask(cpu_mask):
  """Parse a CPU mask definition and return the list of CPU IDs.

  CPU mask format: comma-separated list of CPU IDs
  or dash-separated ID ranges
  Example: "0-2,5" -> "0,1,2,5"

  @type cpu_mask: str
  @param cpu_mask: CPU mask definition
  @rtype: list of int
  @return: list of CPU IDs

  """
  if not cpu_mask:
    return []
  cpu_list = []
  for range_def in cpu_mask.split(","):
    boundaries = range_def.split("-")
    n_elements = len(boundaries)
    if n_elements > 2:
      raise errors.ParseError("Invalid CPU ID range definition"
                              " (only one hyphen allowed): %s" % range_def)
    try:
      lower = int(boundaries[0])
    except (ValueError, TypeError), err:
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
                              " CPU ID range: %s" % str(err))
    try:
      higher = int(boundaries[-1])
    except (ValueError, TypeError), err:
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
                              " CPU ID range: %s" % str(err))
    if lower > higher:
      raise errors.ParseError("Invalid CPU ID range definition"
                              " (%d > %d): %s" % (lower, higher, range_def))
    cpu_list.extend(range(lower, higher + 1))
  return cpu_list


1079
1080
def SetEtcHostsEntry(file_name, ip, hostname, aliases):
  """Sets the name of an IP address and hostname in /etc/hosts.
1081

1082
1083
1084
1085
1086
1087
1088
1089
1090
  @type file_name: str
  @param file_name: path to the file to modify (usually C{/etc/hosts})
  @type ip: str
  @param ip: the IP address
  @type hostname: str
  @param hostname: the hostname to be added
  @type aliases: list
  @param aliases: the list of aliases to add for the hostname

1091
  """
1092
1093
1094
  # Ensure aliases are unique
  aliases = UniqueSequence([hostname] + aliases)[1:]

1095
1096
1097
1098
  def _WriteEtcHosts(fd):
    # Duplicating file descriptor because os.fdopen's result will automatically
    # close the descriptor, but we would still like to have its functionality.
    out = os.fdopen(os.dup(fd), "w")
1099
    try:
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
      for line in ReadFile(file_name).splitlines(True):
        fields = line.split()
        if fields and not fields[0].startswith("#") and ip == fields[0]:
          continue
        out.write(line)

      out.write("%s\t%s" % (ip, hostname))
      if aliases:
        out.write(" %s" % " ".join(aliases))
      out.write("\n")
      out.flush()
1111
1112
    finally:
      out.close()
1113
1114

  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
1115
1116


1117
def AddHostToEtcHosts(hostname, ip):
1118
1119
  """Wrapper around SetEtcHostsEntry.

1120
1121
1122
  @type hostname: str
  @param hostname: a hostname that will be resolved and added to
      L{constants.ETC_HOSTS}
1123
1124
  @type ip: str
  @param ip: The ip address of the host
1125

1126
  """
1127
  SetEtcHostsEntry(constants.ETC_HOSTS, ip, hostname, [hostname.split(".")[0]])
1128
1129


1130
def RemoveEtcHostsEntry(file_name, hostname):
1131
  """Removes a hostname from /etc/hosts.
1132

1133
  IP addresses without names are removed from the file.
1134
1135
1136
1137
1138
1139

  @type file_name: str
  @param file_name: path to the file to modify (usually C{/etc/hosts})
  @type hostname: str
  @param hostname: the hostname to be removed

1140
  """
1141
1142
1143
1144
  def _WriteEtcHosts(fd):
    # Duplicating file descriptor because os.fdopen's result will automatically
    # close the descriptor, but we would still like to have its functionality.
    out = os.fdopen(os.dup(fd), "w")
1145
    try:
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
      for line in ReadFile(file_name).splitlines(True):
        fields = line.split()
        if len(fields) > 1 and not fields[0].startswith("#"):
          names = fields[1:]
          if hostname in names:
            while hostname in names:
              names.remove(hostname)
            if names:
              out.write("%s %s\n" % (fields[0], " ".join(names)))
            continue
1156

1157
1158
1159
        out.write(line)

      out.flush()
Iustin Pop's avatar
Iustin Pop committed
1160
    finally:
1161
      out.close()
1162
1163

  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
Iustin Pop's avatar
Iustin Pop committed
1164
1165


1166
1167
1168
def RemoveHostFromEtcHosts(hostname):
  """Wrapper around RemoveEtcHostsEntry.

1169
1170
1171
1172
1173
  @type hostname: str
  @param hostname: hostname that will be resolved and its
      full and shot name will be removed from
      L{constants.ETC_HOSTS}

1174
  """
1175
1176
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname)
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname.split(".")[0])
1177
1178


1179
1180
1181
1182
1183
1184
def GetHomeDir(user, default=None):
  """Try to get the homedir of the given user.

  The user can be passed either as a string (denoting the name) or as
  an integer (denoting the user id). If the user is not found, the
  'default' argument is returned, which defaults to None.
1185
1186
1187

  """
  try:
1188
1189
1190
1191
1192
1193
1194
    if isinstance(user, basestring):
      result = pwd.getpwnam(user)
    elif isinstance(user, (int, long)):
      result = pwd.getpwuid(user)
    else:
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
                                   type(user))
1195
1196
1197
  except KeyError:
    return default
  return result.pw_dir
1198
1199


1200
def NewUUID():
1201
1202
  """Returns a random UUID.

1203
1204
1205
1206
  @note: This is a Linux-specific method as it uses the /proc
      filesystem.
  @rtype: str

1207
  """
1208
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
Iustin Pop's avatar
Iustin Pop committed
1209
1210


1211
1212
1213
1214
1215
1216
1217
1218
def FirstFree(seq, base=0):
  """Returns the first non-existing integer from seq.

  The seq argument should be a sorted list of positive integers. The
  first time the index of an element is smaller than the element
  value, the index will be returned.

  The base argument is used to start at a different offset,
1219
1220
1221
  i.e. C{[3, 4, 6]} with I{offset=3} will return 5.

  Example: C{[0, 1, 3]} will return I{2}.
1222

1223
1224
1225
1226
1227
1228
  @type seq: sequence
  @param seq: the sequence to be analyzed.
  @type base: int
  @param base: use this value as the base index of the sequence
  @rtype: int
  @return: the first non-used index in the sequence
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238

  """
  for idx, elem in enumerate(seq):
    assert elem >= base, "Passed element is higher than base offset"
    if elem > idx + base:
      # idx is not used
      return idx + base
  return None


1239
def SingleWaitForFdCondition(fdobj, event, timeout):
1240
1241
  """Waits for a condition to occur on the socket.

1242
1243
1244
1245
1246
  Immediately returns at the first interruption.

  @type fdobj: integer or object supporting a fileno() method
  @param fdobj: entity to wait for events on
  @type event: integer
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
  @param event: ORed condition (see select module)
  @type timeout: float or None
  @param timeout: Timeout in seconds
  @rtype: int or None
  @return: None for timeout, otherwise occured conditions

  """
  check = (event | select.POLLPRI |
           select.POLLNVAL | select.POLLHUP | select.POLLERR)

  if timeout is not None:
    # Poller object expects milliseconds
    timeout *= 1000

  poller = select.poll()
1262
  poller.register(fdobj, event)
1263
  try:
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
    # TODO: If the main thread receives a signal and we have no timeout, we
    # could wait forever. This should check a global "quit" flag or something
    # every so often.
    io_events = poller.poll(timeout)
  except select.error, err:
    if err[0] != errno.EINTR:
      raise
    io_events = []
  if io_events and io_events[0][1] & check:
    return io_events[0][1]
  else:
    return None


class FdConditionWaiterHelper(object):
  """Retry helper for WaitForFdCondition.

  This class contains the retried and wait functions that make sure
  WaitForFdCondition can continue waiting until the timeout is actually
  expired.

  """

  def __init__(self, timeout):
    self.timeout = timeout

  def Poll(self, fdobj, event):
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
    if result is None:
      raise RetryAgain()
    else:
      return result

  def UpdateTimeout(self, timeout):
    self.timeout = timeout


def WaitForFdCondition(fdobj, event, timeout):
  """Waits for a condition to occur on the socket.

  Retries until the timeout is expired, even if interrupted.

  @type fdobj: integer or object supporting a fileno() method
  @param fdobj: entity to wait for events on
  @type event: integer
  @param event: ORed condition (see select module)
  @type timeout: float or None
  @param timeout: Timeout in seconds
  @rtype: int or None
  @return: None for timeout, otherwise occured conditions

  """
  if timeout is not None:
    retrywaiter = FdConditionWaiterHelper(timeout)
1318
1319
1320
1321
1322
    try:
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
    except RetryTimeout:
      result = None
1323
1324
1325
1326
1327
  else:
    result = None
    while result is None:
      result = SingleWaitForFdCondition(fdobj, event, timeout)
  return result
1328
1329


Iustin Pop's avatar
Iustin Pop committed
1330
1331
1332
1333
1334
def CloseFDs(noclose_fds=None):
  """Close file descriptors.

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

1336
1337
1338
1339
  @type noclose_fds: list or None
  @param noclose_fds: if given, it denotes a list of file descriptor
      that should not be closed

1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
  """
  # Default maximum for the number of available file descriptors.
  if 'SC_OPEN_MAX' in os.sysconf_names:
    try:
      MAXFD = os.sysconf('SC_OPEN_MAX')
      if MAXFD < 0:
        MAXFD = 1024
    except OSError:
      MAXFD = 1024
  else:
    MAXFD = 1024
Iustin Pop's avatar
Iustin Pop committed
1351
1352
1353
1354
1355
1356
1357
1358
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
  if (maxfd == resource.RLIM_INFINITY):
    maxfd = MAXFD

  # Iterate through and close all file descriptors (except the standard ones)
  for fd in range(3, maxfd):
    if noclose_fds and fd in noclose_fds:
      continue
1359
    CloseFdNoError(fd)
Iustin Pop's avatar
Iustin Pop committed
1360
1361


1362
def Daemonize(logfile):
Iustin Pop's avatar
Iustin Pop committed
1363
1364
1365
1366
1367
1368
1369
1370
  """Daemonize the current process.

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

  @type logfile: str
  @param logfile: the logfile to which we should redirect stdout/stderr
  @rtype: int
Iustin Pop's avatar
Iustin Pop committed
1371
  @return: the value zero
Iustin Pop's avatar
Iustin Pop committed
1372
1373

  """
Iustin Pop's avatar
Iustin Pop committed
1374
1375
  # pylint: disable-msg=W0212
  # yes, we really want os._exit
1376

1377
1378
1379
1380
1381
1382
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
  # least abstract the pipe functionality between them

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

1383
1384
1385
  # this might fail
  pid = os.fork()
  if (pid == 0):  # The first child.
1386
1387
    SetupDaemonEnv()

1388
1389
1390
    # this might fail
    pid = os.fork() # Fork a second child.
    if (pid == 0):  # The second child.
1391
      CloseFdNoError(rpipe)
1392
1393
1394
1395
    else:
      # exit() or _exit()?  See below.
      os._exit(0) # Exit parent (the first child) of the second child.
  else:
1396
    CloseFdNoError(wpipe)
1397
1398
1399
1400
1401
1402
1403
1404
1405
    # Wait for daemon to be started (or an error message to
    # arrive) and read up to 100 KB as an error message
    errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
    if errormsg:
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
      rcode = 1
    else:
      rcode = 0
    os._exit(rcode) # Exit parent of the first child.
1406

1407
  SetupDaemonFDs(logfile, None)
1408
  return wpipe
1409
1410


1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
def EnsureDaemon(name):
  """Check for and start daemon if not alive.

  """
  result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
  if result.failed:
    logging.error("Can't start daemon '%s', failure %s, output: %s",
                  name, result.fail_reason, result.output)
    return False

  return True
1422
1423


1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
def StopDaemon(name):
  """Stop daemon

  """
  result = RunCmd([constants.DAEMON_UTIL, "stop", name])
  if result.failed:
    logging.error("Can't stop daemon '%s', failure %s, output: %s",
                  name, result.fail_reason, result.output)
    return False

  return True


1437
1438
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
                waitpid=False):
Iustin Pop's avatar
Iustin Pop committed
1439
1440
1441
1442
  """Kill a process given by its pid.

  @type pid: int
  @param pid: The PID to terminate.
Iustin Pop's avatar
Iustin Pop committed
1443
1444
  @type signal_: int
  @param signal_: The signal to send, by default SIGTERM
Iustin Pop's avatar
Iustin Pop committed
1445
1446
1447
1448
  @type timeout: int
  @param timeout: The timeout after which, if the process is still alive,
                  a SIGKILL will be sent. If not positive, no such checking
                  will be done
1449
1450
1451
1452
  @type waitpid: boolean
  @param waitpid: If true, we should waitpid on this process after
      sending signals, since it's our own child and otherwise it
      would remain as zombie
Iustin Pop's avatar
Iustin Pop committed
1453
1454

  """
1455
1456
  def _helper(pid, signal_, wait):
    """Simple helper to encapsulate the kill/waitpid sequence"""
1457
    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
1458
1459
1460
1461
1462
      try:
        os.waitpid(pid, os.WNOHANG)
      except OSError:
        pass

Iustin Pop's avatar
Iustin Pop committed
1463
1464
1465
1466
1467
1468
  if pid <= 0:
    # kill with pid=0 == suicide
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)

  if not IsProcessAlive(pid):
    return
1469

1470
  _helper(pid, signal_, waitpid)
1471

Iustin Pop's avatar
Iustin Pop committed
1472
1473
  if timeout <= 0:
    return
1474

1475
1476
1477
1478
  def _CheckProcess():
    if not IsProcessAlive(pid):
      return

1479
1480
1481
    try:
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
    except OSError:
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
      raise RetryAgain()

    if result_pid > 0:
      return

    raise RetryAgain()

  try:
    # Wait up to $timeout seconds
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
  except RetryTimeout:
    pass
1494

Iustin Pop's avatar
Iustin Pop committed
1495
  if IsProcessAlive(pid):
1496
    # Kill process if it's still alive
Iustin Pop's avatar
Iustin Pop committed
1497
    _helper(pid, signal.SIGKILL, waitpid)
Iustin Pop's avatar
Iustin Pop committed
1498
1499


1500
1501
1502
def CheckVolumeGroupSize(vglist, vgname, minsize):
  """Checks if the volume group list is valid.

1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
  The function will check if a given volume group is in the list of
  volume groups and has a minimum size.

  @type vglist: dict
  @param vglist: dictionary of volume group names and their size
  @type vgname: str
  @param vgname: the volume group we should check
  @type minsize: int
  @param minsize: the minimum size we accept
  @rtype: None or str
  @return: None for success, otherwise the error message
1514
1515
1516
1517
1518
1519
1520
1521
1522

  """
  vgsize = vglist.get(vgname, None)
  if vgsize is None:
    return "volume group '%s' missing" % vgname
  elif vgsize < minsize:
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
            (vgname, minsize, vgsize))
  return None