Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 44c9b4fe

History | View | Annotate | Download (47.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 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

    
22
"""Ganeti utility module.
23

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

27
"""
28

    
29

    
30
import os
31
import sys
32
import time
33
import subprocess
34
import re
35
import socket
36
import tempfile
37
import shutil
38
import errno
39
import pwd
40
import itertools
41
import select
42
import fcntl
43
import resource
44
import logging
45
import signal
46
import datetime
47
import calendar
48

    
49
from cStringIO import StringIO
50

    
51
from ganeti import errors
52
from ganeti import constants
53
from ganeti import compat
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
from ganeti.utils.nodesetup import * # pylint: disable-msg=W0401
66

    
67

    
68
#: when set to True, L{RunCmd} is disabled
69
_no_fork = False
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
UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
76
                     '[a-f0-9]{4}-[a-f0-9]{12}$')
77

    
78
(_TIMEOUT_NONE,
79
 _TIMEOUT_TERM,
80
 _TIMEOUT_KILL) = range(3)
81

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

    
85

    
86
def DisableFork():
87
  """Disables the use of fork(2).
88

89
  """
90
  global _no_fork # pylint: disable-msg=W0603
91

    
92
  _no_fork = True
93

    
94

    
95
class RunResult(object):
96
  """Holds the result of running external programs.
97

98
  @type exit_code: int
99
  @ivar exit_code: the exit code of the program, or None (if the program
100
      didn't exit())
101
  @type signal: int or None
102
  @ivar signal: the signal that caused the program to finish, or None
103
      (if the program wasn't terminated by a signal)
104
  @type stdout: str
105
  @ivar stdout: the standard output of the program
106
  @type stderr: str
107
  @ivar stderr: the standard error of the program
108
  @type failed: boolean
109
  @ivar failed: True in case the program was
110
      terminated by a signal or exited with a non-zero exit code
111
  @ivar fail_reason: a string detailing the termination reason
112

113
  """
114
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
115
               "failed", "fail_reason", "cmd"]
116

    
117

    
118
  def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
119
               timeout):
120
    self.cmd = cmd
121
    self.exit_code = exit_code
122
    self.signal = signal_
123
    self.stdout = stdout
124
    self.stderr = stderr
125
    self.failed = (signal_ is not None or exit_code != 0)
126

    
127
    fail_msgs = []
128
    if self.signal is not None:
129
      fail_msgs.append("terminated by signal %s" % self.signal)
130
    elif self.exit_code is not None:
131
      fail_msgs.append("exited with exit code %s" % self.exit_code)
132
    else:
133
      fail_msgs.append("unable to determine termination reason")
134

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

    
142
    if fail_msgs and self.failed:
143
      self.fail_reason = CommaJoin(fail_msgs)
144

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

    
149
  def _GetOutput(self):
150
    """Returns the combined stdout and stderr for easier usage.
151

152
    """
153
    return self.stdout + self.stderr
154

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

    
157

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

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

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

    
171
  return cmd_env
172

    
173

    
174
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
175
           interactive=False, timeout=None):
176
  """Execute a (shell) command.
177

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

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

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

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

    
212
  if isinstance(cmd, basestring):
213
    strcmd = cmd
214
    shell = True
215
  else:
216
    cmd = [str(val) for val in cmd]
217
    strcmd = ShellQuoteArgs(cmd)
218
    shell = False
219

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

    
225
  cmd_env = _BuildCmdEnvironment(env, reset_env)
226

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

    
242
  if status >= 0:
243
    exitcode = status
244
    signal_ = None
245
  else:
246
    exitcode = None
247
    signal_ = -status
248

    
249
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
250

    
251

    
252
def SetupDaemonEnv(cwd="/", umask=077):
253
  """Setup a daemon's environment.
254

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

258
  @param cwd: the directory to which to chdir
259
  @param umask: the umask to setup
260

261
  """
262
  os.chdir(cwd)
263
  os.umask(umask)
264
  os.setsid()
265

    
266

    
267
def SetupDaemonFDs(output_file, output_fd):
268
  """Setups up a daemon's file descriptors.
269

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

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

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

    
281
  if output_fd is not None:
282
    pass
283
  elif output_file is not None:
284
    # Open output file
285
    try:
286
      output_fd = os.open(output_file,
287
                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
288
    except EnvironmentError, err:
289
      raise Exception("Opening output file failed: %s" % err)
290
  else:
291
    output_fd = os.open(os.devnull, os.O_WRONLY)
292

    
293
  # Redirect standard I/O
294
  os.dup2(devnull_fd, 0)
295
  os.dup2(output_fd, 1)
296
  os.dup2(output_fd, 2)
297

    
298

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

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

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

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

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

    
331
  strcmd = ShellQuoteArgs(cmd)
332

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

    
338
  cmd_env = _BuildCmdEnvironment(env, False)
339

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

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

    
371
    # Read up to 128 bytes for PID
372
    pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
373
  finally:
374
    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
    CloseFdNoError(errpipe_read)
403
    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-msg=W0212
413

    
414
    # Make sure pipe is closed on execv* (and thereby notifies
415
    # original process)
416
    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 = WritePidFile(pidfile)
424

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

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

    
432
    SetupDaemonFDs(output, fd_output)
433

    
434
    # Send daemon PID to parent
435
    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-msg=W0702
448
    try:
449
      # Report errors to original process
450
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
451
    except: # pylint: disable-msg=W0702
452
      # Ignore errors in error handling
453
      pass
454

    
455
  os._exit(1) # pylint: disable-msg=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
  RetryOnSignal(os.write, fd, err)
473

    
474

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

478
  @raises RetryAgain: If child is still alive
479

480
  """
481
  if child.poll() is None:
482
    raise 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
    Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout), args=[child])
491
  except RetryTimeout:
492
    pass
493

    
494

    
495
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
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
  @rtype: tuple
512
  @return: (out, err, status)
513

514
  """
515
  poller = select.poll()
516

    
517
  stderr = subprocess.PIPE
518
  stdout = subprocess.PIPE
519
  stdin = subprocess.PIPE
520

    
521
  if interactive:
522
    stderr = stdout = stdin = None
523

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

    
531
  out = StringIO()
532
  err = StringIO()
533

    
534
  linger_timeout = None
535

    
536
  if timeout is None:
537
    poll_timeout = None
538
  else:
539
    poll_timeout = RunningTimeout(timeout, True).Remaining
540

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

    
546
  timeout_action = _TIMEOUT_NONE
547

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

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

    
575
      pollresult = RetryOnSignal(poller.poll, pt)
576

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

    
591
  if timeout is not None:
592
    assert callable(poll_timeout)
593

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

    
598
    # Terminate if still alive after timeout
599
    if child.poll() is None:
600
      if linger_timeout is None:
601
        logging.warning(msg_timeout)
602
        timeout_action = _TIMEOUT_TERM
603
        IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
604
        lt = _linger_timeout
605
      else:
606
        lt = linger_timeout()
607
      _WaitForProcess(child, lt)
608

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

    
615
  out = out.getvalue()
616
  err = err.getvalue()
617

    
618
  status = child.wait()
619
  return out, err, status, timeout_action
620

    
621

    
622
def _RunCmdFile(cmd, env, via_shell, output, cwd):
623
  """Run a command and save its output to a file.
624

625
  @type  cmd: string or list
626
  @param cmd: Command to run
627
  @type env: dict
628
  @param env: The environment to use
629
  @type via_shell: bool
630
  @param via_shell: if we should run via the shell
631
  @type output: str
632
  @param output: the filename in which to save the output
633
  @type cwd: string
634
  @param cwd: the working directory for the program
635
  @rtype: int
636
  @return: the exit status
637

638
  """
639
  fh = open(output, "a")
640
  try:
641
    child = subprocess.Popen(cmd, shell=via_shell,
642
                             stderr=subprocess.STDOUT,
643
                             stdout=fh,
644
                             stdin=subprocess.PIPE,
645
                             close_fds=True, env=env,
646
                             cwd=cwd)
647

    
648
    child.stdin.close()
649
    status = child.wait()
650
  finally:
651
    fh.close()
652
  return status
653

    
654

    
655
def RunParts(dir_name, env=None, reset_env=False):
656
  """Run Scripts or programs in a directory
657

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

667
  """
668
  rr = []
669

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

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

    
689
  return rr
690

    
691

    
692
def ForceDictType(target, key_types, allowed_values=None):
693
  """Force the values of a dict to have certain types.
694

695
  @type target: dict
696
  @param target: the dict to update
697
  @type key_types: dict
698
  @param key_types: dict mapping target dict keys to types
699
                    in constants.ENFORCEABLE_TYPES
700
  @type allowed_values: list
701
  @keyword allowed_values: list of specially allowed values
702

703
  """
704
  if allowed_values is None:
705
    allowed_values = []
706

    
707
  if not isinstance(target, dict):
708
    msg = "Expected dictionary, got '%s'" % target
709
    raise errors.TypeEnforcementError(msg)
710

    
711
  for key in target:
712
    if key not in key_types:
713
      msg = "Unknown key '%s'" % key
714
      raise errors.TypeEnforcementError(msg)
715

    
716
    if target[key] in allowed_values:
717
      continue
718

    
719
    ktype = key_types[key]
720
    if ktype not in constants.ENFORCEABLE_TYPES:
721
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
722
      raise errors.ProgrammerError(msg)
723

    
724
    if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
725
      if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
726
        pass
727
      elif not isinstance(target[key], basestring):
728
        if isinstance(target[key], bool) and not target[key]:
729
          target[key] = ''
730
        else:
731
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
732
          raise errors.TypeEnforcementError(msg)
733
    elif ktype == constants.VTYPE_BOOL:
734
      if isinstance(target[key], basestring) and target[key]:
735
        if target[key].lower() == constants.VALUE_FALSE:
736
          target[key] = False
737
        elif target[key].lower() == constants.VALUE_TRUE:
738
          target[key] = True
739
        else:
740
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
741
          raise errors.TypeEnforcementError(msg)
742
      elif target[key]:
743
        target[key] = True
744
      else:
745
        target[key] = False
746
    elif ktype == constants.VTYPE_SIZE:
747
      try:
748
        target[key] = ParseUnit(target[key])
749
      except errors.UnitParseError, err:
750
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
751
              (key, target[key], err)
752
        raise errors.TypeEnforcementError(msg)
753
    elif ktype == constants.VTYPE_INT:
754
      try:
755
        target[key] = int(target[key])
756
      except (ValueError, TypeError):
757
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
758
        raise errors.TypeEnforcementError(msg)
759

    
760

    
761
def _GetProcStatusPath(pid):
762
  """Returns the path for a PID's proc status file.
763

764
  @type pid: int
765
  @param pid: Process ID
766
  @rtype: string
767

768
  """
769
  return "/proc/%d/status" % pid
770

    
771

    
772
def IsProcessAlive(pid):
773
  """Check if a given pid exists on the system.
774

775
  @note: zombie status is not handled, so zombie processes
776
      will be returned as alive
777
  @type pid: int
778
  @param pid: the process ID to check
779
  @rtype: boolean
780
  @return: True if the process exists
781

782
  """
783
  def _TryStat(name):
784
    try:
785
      os.stat(name)
786
      return True
787
    except EnvironmentError, err:
788
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
789
        return False
790
      elif err.errno == errno.EINVAL:
791
        raise RetryAgain(err)
792
      raise
793

    
794
  assert isinstance(pid, int), "pid must be an integer"
795
  if pid <= 0:
796
    return False
797

    
798
  # /proc in a multiprocessor environment can have strange behaviors.
799
  # Retry the os.stat a few times until we get a good result.
800
  try:
801
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
802
                 args=[_GetProcStatusPath(pid)])
803
  except RetryTimeout, err:
804
    err.RaiseInner()
805

    
806

    
807
def _ParseSigsetT(sigset):
808
  """Parse a rendered sigset_t value.
809

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

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

818
  """
819
  result = set()
820

    
821
  signum = 0
822
  for ch in reversed(sigset):
823
    chv = int(ch, 16)
824

    
825
    # The following could be done in a loop, but it's easier to read and
826
    # understand in the unrolled form
827
    if chv & 1:
828
      result.add(signum + 1)
829
    if chv & 2:
830
      result.add(signum + 2)
831
    if chv & 4:
832
      result.add(signum + 3)
833
    if chv & 8:
834
      result.add(signum + 4)
835

    
836
    signum += 4
837

    
838
  return result
839

    
840

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

844
  @type pstatus: string
845
  @param pstatus: Contents of /proc/$pid/status
846
  @type field: string
847
  @param field: Name of field whose value should be returned
848
  @rtype: string
849

850
  """
851
  for line in pstatus.splitlines():
852
    parts = line.split(":", 1)
853

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

    
857
    return parts[1].strip()
858

    
859
  return None
860

    
861

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

865
  @type pid: int
866
  @param pid: Process ID
867
  @type signum: int
868
  @param signum: Signal number
869
  @rtype: bool
870

871
  """
872
  if status_path is None:
873
    status_path = _GetProcStatusPath(pid)
874

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

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

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

    
890

    
891
def ValidateServiceName(name):
892
  """Validate the given service name.
893

894
  @type name: number or string
895
  @param name: Service name or port specification
896

897
  """
898
  try:
899
    numport = int(name)
900
  except (ValueError, TypeError):
901
    # Non-numeric service name
902
    valid = _VALID_SERVICE_NAME_RE.match(name)
903
  else:
904
    # Numeric port (protocols other than TCP or UDP might need adjustments
905
    # here)
906
    valid = (numport >= 0 and numport < (1 << 16))
907

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

    
912
  return name
913

    
914

    
915
def ListVolumeGroups():
916
  """List volume groups and their size
917

918
  @rtype: dict
919
  @return:
920
       Dictionary with keys volume name and values
921
       the size of the volume
922

923
  """
924
  command = "vgs --noheadings --units m --nosuffix -o name,size"
925
  result = RunCmd(command)
926
  retval = {}
927
  if result.failed:
928
    return retval
929

    
930
  for line in result.stdout.splitlines():
931
    try:
932
      name, size = line.split()
933
      size = int(float(size))
934
    except (IndexError, ValueError), err:
935
      logging.error("Invalid output from vgs (%s): %s", err, line)
936
      continue
937

    
938
    retval[name] = size
939

    
940
  return retval
941

    
942

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

946
  @type bridge: str
947
  @param bridge: the bridge name to check
948
  @rtype: boolean
949
  @return: True if it does
950

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

    
954

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

958
  This function tries to apply function I{fn} to I{val}. If no
959
  C{ValueError} or C{TypeError} exceptions are raised, it will return
960
  the result, else it will return the original value. Any other
961
  exceptions are propagated to the caller.
962

963
  @type fn: callable
964
  @param fn: function to apply to the value
965
  @param val: the value to be converted
966
  @return: The converted value if the conversion was successful,
967
      otherwise the original value.
968

969
  """
970
  try:
971
    nv = fn(val)
972
  except (ValueError, TypeError):
973
    nv = val
974
  return nv
975

    
976

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

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

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

987
  @type word: str
988
  @param word: the word to check
989
  @rtype: boolean
990
  @return: True if the word is 'safe'
991

992
  """
993
  return bool(_SHELLPARAM_REGEX.match(word))
994

    
995

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

999
  This function will check all arguments in the args list so that they
1000
  are valid shell parameters (i.e. they don't contain shell
1001
  metacharacters). If everything is ok, it will return the result of
1002
  template % args.
1003

1004
  @type template: str
1005
  @param template: the string holding the template for the
1006
      string formatting
1007
  @rtype: str
1008
  @return: the expanded command line
1009

1010
  """
1011
  for word in args:
1012
    if not IsValidShellParam(word):
1013
      raise errors.ProgrammerError("Shell argument '%s' contains"
1014
                                   " invalid characters" % word)
1015
  return template % args
1016

    
1017

    
1018
def ParseCpuMask(cpu_mask):
1019
  """Parse a CPU mask definition and return the list of CPU IDs.
1020

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

1025
  @type cpu_mask: str
1026
  @param cpu_mask: CPU mask definition
1027
  @rtype: list of int
1028
  @return: list of CPU IDs
1029

1030
  """
1031
  if not cpu_mask:
1032
    return []
1033
  cpu_list = []
1034
  for range_def in cpu_mask.split(","):
1035
    boundaries = range_def.split("-")
1036
    n_elements = len(boundaries)
1037
    if n_elements > 2:
1038
      raise errors.ParseError("Invalid CPU ID range definition"
1039
                              " (only one hyphen allowed): %s" % range_def)
1040
    try:
1041
      lower = int(boundaries[0])
1042
    except (ValueError, TypeError), err:
1043
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
1044
                              " CPU ID range: %s" % str(err))
1045
    try:
1046
      higher = int(boundaries[-1])
1047
    except (ValueError, TypeError), err:
1048
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
1049
                              " CPU ID range: %s" % str(err))
1050
    if lower > higher:
1051
      raise errors.ParseError("Invalid CPU ID range definition"
1052
                              " (%d > %d): %s" % (lower, higher, range_def))
1053
    cpu_list.extend(range(lower, higher + 1))
1054
  return cpu_list
1055

    
1056

    
1057
def GetHomeDir(user, default=None):
1058
  """Try to get the homedir of the given user.
1059

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

1064
  """
1065
  try:
1066
    if isinstance(user, basestring):
1067
      result = pwd.getpwnam(user)
1068
    elif isinstance(user, (int, long)):
1069
      result = pwd.getpwuid(user)
1070
    else:
1071
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1072
                                   type(user))
1073
  except KeyError:
1074
    return default
1075
  return result.pw_dir
1076

    
1077

    
1078
def NewUUID():
1079
  """Returns a random UUID.
1080

1081
  @note: This is a Linux-specific method as it uses the /proc
1082
      filesystem.
1083
  @rtype: str
1084

1085
  """
1086
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1087

    
1088

    
1089
def FirstFree(seq, base=0):
1090
  """Returns the first non-existing integer from seq.
1091

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

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

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

1101
  @type seq: sequence
1102
  @param seq: the sequence to be analyzed.
1103
  @type base: int
1104
  @param base: use this value as the base index of the sequence
1105
  @rtype: int
1106
  @return: the first non-used index in the sequence
1107

1108
  """
1109
  for idx, elem in enumerate(seq):
1110
    assert elem >= base, "Passed element is higher than base offset"
1111
    if elem > idx + base:
1112
      # idx is not used
1113
      return idx + base
1114
  return None
1115

    
1116

    
1117
def SingleWaitForFdCondition(fdobj, event, timeout):
1118
  """Waits for a condition to occur on the socket.
1119

1120
  Immediately returns at the first interruption.
1121

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

1131
  """
1132
  check = (event | select.POLLPRI |
1133
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
1134

    
1135
  if timeout is not None:
1136
    # Poller object expects milliseconds
1137
    timeout *= 1000
1138

    
1139
  poller = select.poll()
1140
  poller.register(fdobj, event)
1141
  try:
1142
    # TODO: If the main thread receives a signal and we have no timeout, we
1143
    # could wait forever. This should check a global "quit" flag or something
1144
    # every so often.
1145
    io_events = poller.poll(timeout)
1146
  except select.error, err:
1147
    if err[0] != errno.EINTR:
1148
      raise
1149
    io_events = []
1150
  if io_events and io_events[0][1] & check:
1151
    return io_events[0][1]
1152
  else:
1153
    return None
1154

    
1155

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

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

1163
  """
1164

    
1165
  def __init__(self, timeout):
1166
    self.timeout = timeout
1167

    
1168
  def Poll(self, fdobj, event):
1169
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1170
    if result is None:
1171
      raise RetryAgain()
1172
    else:
1173
      return result
1174

    
1175
  def UpdateTimeout(self, timeout):
1176
    self.timeout = timeout
1177

    
1178

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

1182
  Retries until the timeout is expired, even if interrupted.
1183

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

1193
  """
1194
  if timeout is not None:
1195
    retrywaiter = FdConditionWaiterHelper(timeout)
1196
    try:
1197
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1198
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1199
    except RetryTimeout:
1200
      result = None
1201
  else:
1202
    result = None
1203
    while result is None:
1204
      result = SingleWaitForFdCondition(fdobj, event, timeout)
1205
  return result
1206

    
1207

    
1208
def CloseFDs(noclose_fds=None):
1209
  """Close file descriptors.
1210

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

1214
  @type noclose_fds: list or None
1215
  @param noclose_fds: if given, it denotes a list of file descriptor
1216
      that should not be closed
1217

1218
  """
1219
  # Default maximum for the number of available file descriptors.
1220
  if 'SC_OPEN_MAX' in os.sysconf_names:
1221
    try:
1222
      MAXFD = os.sysconf('SC_OPEN_MAX')
1223
      if MAXFD < 0:
1224
        MAXFD = 1024
1225
    except OSError:
1226
      MAXFD = 1024
1227
  else:
1228
    MAXFD = 1024
1229
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1230
  if (maxfd == resource.RLIM_INFINITY):
1231
    maxfd = MAXFD
1232

    
1233
  # Iterate through and close all file descriptors (except the standard ones)
1234
  for fd in range(3, maxfd):
1235
    if noclose_fds and fd in noclose_fds:
1236
      continue
1237
    CloseFdNoError(fd)
1238

    
1239

    
1240
def Daemonize(logfile):
1241
  """Daemonize the current process.
1242

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

1246
  @type logfile: str
1247
  @param logfile: the logfile to which we should redirect stdout/stderr
1248
  @rtype: int
1249
  @return: the value zero
1250

1251
  """
1252
  # pylint: disable-msg=W0212
1253
  # yes, we really want os._exit
1254

    
1255
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
1256
  # least abstract the pipe functionality between them
1257

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

    
1261
  # this might fail
1262
  pid = os.fork()
1263
  if (pid == 0):  # The first child.
1264
    SetupDaemonEnv()
1265

    
1266
    # this might fail
1267
    pid = os.fork() # Fork a second child.
1268
    if (pid == 0):  # The second child.
1269
      CloseFdNoError(rpipe)
1270
    else:
1271
      # exit() or _exit()?  See below.
1272
      os._exit(0) # Exit parent (the first child) of the second child.
1273
  else:
1274
    CloseFdNoError(wpipe)
1275
    # Wait for daemon to be started (or an error message to
1276
    # arrive) and read up to 100 KB as an error message
1277
    errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
1278
    if errormsg:
1279
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
1280
      rcode = 1
1281
    else:
1282
      rcode = 0
1283
    os._exit(rcode) # Exit parent of the first child.
1284

    
1285
  SetupDaemonFDs(logfile, None)
1286
  return wpipe
1287

    
1288

    
1289
def EnsureDaemon(name):
1290
  """Check for and start daemon if not alive.
1291

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

    
1299
  return True
1300

    
1301

    
1302
def StopDaemon(name):
1303
  """Stop daemon
1304

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

    
1312
  return True
1313

    
1314

    
1315
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1316
                waitpid=False):
1317
  """Kill a process given by its pid.
1318

1319
  @type pid: int
1320
  @param pid: The PID to terminate.
1321
  @type signal_: int
1322
  @param signal_: The signal to send, by default SIGTERM
1323
  @type timeout: int
1324
  @param timeout: The timeout after which, if the process is still alive,
1325
                  a SIGKILL will be sent. If not positive, no such checking
1326
                  will be done
1327
  @type waitpid: boolean
1328
  @param waitpid: If true, we should waitpid on this process after
1329
      sending signals, since it's our own child and otherwise it
1330
      would remain as zombie
1331

1332
  """
1333
  def _helper(pid, signal_, wait):
1334
    """Simple helper to encapsulate the kill/waitpid sequence"""
1335
    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
1336
      try:
1337
        os.waitpid(pid, os.WNOHANG)
1338
      except OSError:
1339
        pass
1340

    
1341
  if pid <= 0:
1342
    # kill with pid=0 == suicide
1343
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1344

    
1345
  if not IsProcessAlive(pid):
1346
    return
1347

    
1348
  _helper(pid, signal_, waitpid)
1349

    
1350
  if timeout <= 0:
1351
    return
1352

    
1353
  def _CheckProcess():
1354
    if not IsProcessAlive(pid):
1355
      return
1356

    
1357
    try:
1358
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1359
    except OSError:
1360
      raise RetryAgain()
1361

    
1362
    if result_pid > 0:
1363
      return
1364

    
1365
    raise RetryAgain()
1366

    
1367
  try:
1368
    # Wait up to $timeout seconds
1369
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1370
  except RetryTimeout:
1371
    pass
1372

    
1373
  if IsProcessAlive(pid):
1374
    # Kill process if it's still alive
1375
    _helper(pid, signal.SIGKILL, waitpid)
1376

    
1377

    
1378
def CheckVolumeGroupSize(vglist, vgname, minsize):
1379
  """Checks if the volume group list is valid.
1380

1381
  The function will check if a given volume group is in the list of
1382
  volume groups and has a minimum size.
1383

1384
  @type vglist: dict
1385
  @param vglist: dictionary of volume group names and their size
1386
  @type vgname: str
1387
  @param vgname: the volume group we should check
1388
  @type minsize: int
1389
  @param minsize: the minimum size we accept
1390
  @rtype: None or str
1391
  @return: None for success, otherwise the error message
1392

1393
  """
1394
  vgsize = vglist.get(vgname, None)
1395
  if vgsize is None:
1396
    return "volume group '%s' missing" % vgname
1397
  elif vgsize < minsize:
1398
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1399
            (vgname, minsize, vgsize))
1400
  return None
1401

    
1402

    
1403
def SplitTime(value):
1404
  """Splits time as floating point number into a tuple.
1405

1406
  @param value: Time in seconds
1407
  @type value: int or float
1408
  @return: Tuple containing (seconds, microseconds)
1409

1410
  """
1411
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1412

    
1413
  assert 0 <= seconds, \
1414
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1415
  assert 0 <= microseconds <= 999999, \
1416
    "Microseconds must be 0-999999, but are %s" % microseconds
1417

    
1418
  return (int(seconds), int(microseconds))
1419

    
1420

    
1421
def MergeTime(timetuple):
1422
  """Merges a tuple into time as a floating point number.
1423

1424
  @param timetuple: Time as tuple, (seconds, microseconds)
1425
  @type timetuple: tuple
1426
  @return: Time as a floating point number expressed in seconds
1427

1428
  """
1429
  (seconds, microseconds) = timetuple
1430

    
1431
  assert 0 <= seconds, \
1432
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1433
  assert 0 <= microseconds <= 999999, \
1434
    "Microseconds must be 0-999999, but are %s" % microseconds
1435

    
1436
  return float(seconds) + (float(microseconds) * 0.000001)
1437

    
1438

    
1439
def FindMatch(data, name):
1440
  """Tries to find an item in a dictionary matching a name.
1441

1442
  Callers have to ensure the data names aren't contradictory (e.g. a regexp
1443
  that matches a string). If the name isn't a direct key, all regular
1444
  expression objects in the dictionary are matched against it.
1445

1446
  @type data: dict
1447
  @param data: Dictionary containing data
1448
  @type name: string
1449
  @param name: Name to look for
1450
  @rtype: tuple; (value in dictionary, matched groups as list)
1451

1452
  """
1453
  if name in data:
1454
    return (data[name], [])
1455

    
1456
  for key, value in data.items():
1457
    # Regex objects
1458
    if hasattr(key, "match"):
1459
      m = key.match(name)
1460
      if m:
1461
        return (value, list(m.groups()))
1462

    
1463
  return None
1464

    
1465

    
1466
def GetMounts(filename=constants.PROC_MOUNTS):
1467
  """Returns the list of mounted filesystems.
1468

1469
  This function is Linux-specific.
1470

1471
  @param filename: path of mounts file (/proc/mounts by default)
1472
  @rtype: list of tuples
1473
  @return: list of mount entries (device, mountpoint, fstype, options)
1474

1475
  """
1476
  # TODO(iustin): investigate non-Linux options (e.g. via mount output)
1477
  data = []
1478
  mountlines = ReadFile(filename).splitlines()
1479
  for line in mountlines:
1480
    device, mountpoint, fstype, options, _ = line.split(None, 4)
1481
    data.append((device, mountpoint, fstype, options))
1482

    
1483
  return data
1484

    
1485

    
1486
def RunInSeparateProcess(fn, *args):
1487
  """Runs a function in a separate process.
1488

1489
  Note: Only boolean return values are supported.
1490

1491
  @type fn: callable
1492
  @param fn: Function to be called
1493
  @rtype: bool
1494
  @return: Function's result
1495

1496
  """
1497
  pid = os.fork()
1498
  if pid == 0:
1499
    # Child process
1500
    try:
1501
      # In case the function uses temporary files
1502
      ResetTempfileModule()
1503

    
1504
      # Call function
1505
      result = int(bool(fn(*args)))
1506
      assert result in (0, 1)
1507
    except: # pylint: disable-msg=W0702
1508
      logging.exception("Error while calling function in separate process")
1509
      # 0 and 1 are reserved for the return value
1510
      result = 33
1511

    
1512
    os._exit(result) # pylint: disable-msg=W0212
1513

    
1514
  # Parent process
1515

    
1516
  # Avoid zombies and check exit code
1517
  (_, status) = os.waitpid(pid, 0)
1518

    
1519
  if os.WIFSIGNALED(status):
1520
    exitcode = None
1521
    signum = os.WTERMSIG(status)
1522
  else:
1523
    exitcode = os.WEXITSTATUS(status)
1524
    signum = None
1525

    
1526
  if not (exitcode in (0, 1) and signum is None):
1527
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
1528
                              (exitcode, signum))
1529

    
1530
  return bool(exitcode)
1531

    
1532

    
1533
def SignalHandled(signums):
1534
  """Signal Handled decoration.
1535

1536
  This special decorator installs a signal handler and then calls the target
1537
  function. The function must accept a 'signal_handlers' keyword argument,
1538
  which will contain a dict indexed by signal number, with SignalHandler
1539
  objects as values.
1540

1541
  The decorator can be safely stacked with iself, to handle multiple signals
1542
  with different handlers.
1543

1544
  @type signums: list
1545
  @param signums: signals to intercept
1546

1547
  """
1548
  def wrap(fn):
1549
    def sig_function(*args, **kwargs):
1550
      assert 'signal_handlers' not in kwargs or \
1551
             kwargs['signal_handlers'] is None or \
1552
             isinstance(kwargs['signal_handlers'], dict), \
1553
             "Wrong signal_handlers parameter in original function call"
1554
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
1555
        signal_handlers = kwargs['signal_handlers']
1556
      else:
1557
        signal_handlers = {}
1558
        kwargs['signal_handlers'] = signal_handlers
1559
      sighandler = SignalHandler(signums)
1560
      try:
1561
        for sig in signums:
1562
          signal_handlers[sig] = sighandler
1563
        return fn(*args, **kwargs)
1564
      finally:
1565
        sighandler.Reset()
1566
    return sig_function
1567
  return wrap
1568

    
1569

    
1570
class SignalWakeupFd(object):
1571
  try:
1572
    # This is only supported in Python 2.5 and above (some distributions
1573
    # backported it to Python 2.4)
1574
    _set_wakeup_fd_fn = signal.set_wakeup_fd
1575
  except AttributeError:
1576
    # Not supported
1577
    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
1578
      return -1
1579
  else:
1580
    def _SetWakeupFd(self, fd):
1581
      return self._set_wakeup_fd_fn(fd)
1582

    
1583
  def __init__(self):
1584
    """Initializes this class.
1585

1586
    """
1587
    (read_fd, write_fd) = os.pipe()
1588

    
1589
    # Once these succeeded, the file descriptors will be closed automatically.
1590
    # Buffer size 0 is important, otherwise .read() with a specified length
1591
    # might buffer data and the file descriptors won't be marked readable.
1592
    self._read_fh = os.fdopen(read_fd, "r", 0)
1593
    self._write_fh = os.fdopen(write_fd, "w", 0)
1594

    
1595
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
1596

    
1597
    # Utility functions
1598
    self.fileno = self._read_fh.fileno
1599
    self.read = self._read_fh.read
1600

    
1601
  def Reset(self):
1602
    """Restores the previous wakeup file descriptor.
1603

1604
    """
1605
    if hasattr(self, "_previous") and self._previous is not None:
1606
      self._SetWakeupFd(self._previous)
1607
      self._previous = None
1608

    
1609
  def Notify(self):
1610
    """Notifies the wakeup file descriptor.
1611

1612
    """
1613
    self._write_fh.write("\0")
1614

    
1615
  def __del__(self):
1616
    """Called before object deletion.
1617

1618
    """
1619
    self.Reset()
1620

    
1621

    
1622
class SignalHandler(object):
1623
  """Generic signal handler class.
1624

1625
  It automatically restores the original handler when deconstructed or
1626
  when L{Reset} is called. You can either pass your own handler
1627
  function in or query the L{called} attribute to detect whether the
1628
  signal was sent.
1629

1630
  @type signum: list
1631
  @ivar signum: the signals we handle
1632
  @type called: boolean
1633
  @ivar called: tracks whether any of the signals have been raised
1634

1635
  """
1636
  def __init__(self, signum, handler_fn=None, wakeup=None):
1637
    """Constructs a new SignalHandler instance.
1638

1639
    @type signum: int or list of ints
1640
    @param signum: Single signal number or set of signal numbers
1641
    @type handler_fn: callable
1642
    @param handler_fn: Signal handling function
1643

1644
    """
1645
    assert handler_fn is None or callable(handler_fn)
1646

    
1647
    self.signum = set(signum)
1648
    self.called = False
1649

    
1650
    self._handler_fn = handler_fn
1651
    self._wakeup = wakeup
1652

    
1653
    self._previous = {}
1654
    try:
1655
      for signum in self.signum:
1656
        # Setup handler
1657
        prev_handler = signal.signal(signum, self._HandleSignal)
1658
        try:
1659
          self._previous[signum] = prev_handler
1660
        except:
1661
          # Restore previous handler
1662
          signal.signal(signum, prev_handler)
1663
          raise
1664
    except:
1665
      # Reset all handlers
1666
      self.Reset()
1667
      # Here we have a race condition: a handler may have already been called,
1668
      # but there's not much we can do about it at this point.
1669
      raise
1670

    
1671
  def __del__(self):
1672
    self.Reset()
1673

    
1674
  def Reset(self):
1675
    """Restore previous handler.
1676

1677
    This will reset all the signals to their previous handlers.
1678

1679
    """
1680
    for signum, prev_handler in self._previous.items():
1681
      signal.signal(signum, prev_handler)
1682
      # If successful, remove from dict
1683
      del self._previous[signum]
1684

    
1685
  def Clear(self):
1686
    """Unsets the L{called} flag.
1687

1688
    This function can be used in case a signal may arrive several times.
1689

1690
    """
1691
    self.called = False
1692

    
1693
  def _HandleSignal(self, signum, frame):
1694
    """Actual signal handling function.
1695

1696
    """
1697
    # This is not nice and not absolutely atomic, but it appears to be the only
1698
    # solution in Python -- there are no atomic types.
1699
    self.called = True
1700

    
1701
    if self._wakeup:
1702
      # Notify whoever is interested in signals
1703
      self._wakeup.Notify()
1704

    
1705
    if self._handler_fn:
1706
      self._handler_fn(signum, frame)
1707

    
1708

    
1709
class FieldSet(object):
1710
  """A simple field set.
1711

1712
  Among the features are:
1713
    - checking if a string is among a list of static string or regex objects
1714
    - checking if a whole list of string matches
1715
    - returning the matching groups from a regex match
1716

1717
  Internally, all fields are held as regular expression objects.
1718

1719
  """
1720
  def __init__(self, *items):
1721
    self.items = [re.compile("^%s$" % value) for value in items]
1722

    
1723
  def Extend(self, other_set):
1724
    """Extend the field set with the items from another one"""
1725
    self.items.extend(other_set.items)
1726

    
1727
  def Matches(self, field):
1728
    """Checks if a field matches the current set
1729

1730
    @type field: str
1731
    @param field: the string to match
1732
    @return: either None or a regular expression match object
1733

1734
    """
1735
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
1736
      return m
1737
    return None
1738

    
1739
  def NonMatching(self, items):
1740
    """Returns the list of fields not matching the current set
1741

1742
    @type items: list
1743
    @param items: the list of fields to check
1744
    @rtype: list
1745
    @return: list of non-matching fields
1746

1747
    """
1748
    return [val for val in items if not self.Matches(val)]