Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 17b97ab3

History | View | Annotate | Download (49.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 ResetTempfileModule():
693
  """Resets the random name generator of the tempfile module.
694

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

701
  """
702
  # pylint: disable-msg=W0212
703
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
704
    tempfile._once_lock.acquire()
705
    try:
706
      # Reset random name generator
707
      tempfile._name_sequence = None
708
    finally:
709
      tempfile._once_lock.release()
710
  else:
711
    logging.critical("The tempfile module misses at least one of the"
712
                     " '_once_lock' and '_name_sequence' attributes")
713

    
714

    
715
def ForceDictType(target, key_types, allowed_values=None):
716
  """Force the values of a dict to have certain types.
717

718
  @type target: dict
719
  @param target: the dict to update
720
  @type key_types: dict
721
  @param key_types: dict mapping target dict keys to types
722
                    in constants.ENFORCEABLE_TYPES
723
  @type allowed_values: list
724
  @keyword allowed_values: list of specially allowed values
725

726
  """
727
  if allowed_values is None:
728
    allowed_values = []
729

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

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

    
739
    if target[key] in allowed_values:
740
      continue
741

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

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

    
783

    
784
def _GetProcStatusPath(pid):
785
  """Returns the path for a PID's proc status file.
786

787
  @type pid: int
788
  @param pid: Process ID
789
  @rtype: string
790

791
  """
792
  return "/proc/%d/status" % pid
793

    
794

    
795
def IsProcessAlive(pid):
796
  """Check if a given pid exists on the system.
797

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

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

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

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

    
829

    
830
def _ParseSigsetT(sigset):
831
  """Parse a rendered sigset_t value.
832

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

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

841
  """
842
  result = set()
843

    
844
  signum = 0
845
  for ch in reversed(sigset):
846
    chv = int(ch, 16)
847

    
848
    # The following could be done in a loop, but it's easier to read and
849
    # understand in the unrolled form
850
    if chv & 1:
851
      result.add(signum + 1)
852
    if chv & 2:
853
      result.add(signum + 2)
854
    if chv & 4:
855
      result.add(signum + 3)
856
    if chv & 8:
857
      result.add(signum + 4)
858

    
859
    signum += 4
860

    
861
  return result
862

    
863

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

867
  @type pstatus: string
868
  @param pstatus: Contents of /proc/$pid/status
869
  @type field: string
870
  @param field: Name of field whose value should be returned
871
  @rtype: string
872

873
  """
874
  for line in pstatus.splitlines():
875
    parts = line.split(":", 1)
876

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

    
880
    return parts[1].strip()
881

    
882
  return None
883

    
884

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

888
  @type pid: int
889
  @param pid: Process ID
890
  @type signum: int
891
  @param signum: Signal number
892
  @rtype: bool
893

894
  """
895
  if status_path is None:
896
    status_path = _GetProcStatusPath(pid)
897

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

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

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

    
913

    
914
def ValidateServiceName(name):
915
  """Validate the given service name.
916

917
  @type name: number or string
918
  @param name: Service name or port specification
919

920
  """
921
  try:
922
    numport = int(name)
923
  except (ValueError, TypeError):
924
    # Non-numeric service name
925
    valid = _VALID_SERVICE_NAME_RE.match(name)
926
  else:
927
    # Numeric port (protocols other than TCP or UDP might need adjustments
928
    # here)
929
    valid = (numport >= 0 and numport < (1 << 16))
930

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

    
935
  return name
936

    
937

    
938
def ListVolumeGroups():
939
  """List volume groups and their size
940

941
  @rtype: dict
942
  @return:
943
       Dictionary with keys volume name and values
944
       the size of the volume
945

946
  """
947
  command = "vgs --noheadings --units m --nosuffix -o name,size"
948
  result = RunCmd(command)
949
  retval = {}
950
  if result.failed:
951
    return retval
952

    
953
  for line in result.stdout.splitlines():
954
    try:
955
      name, size = line.split()
956
      size = int(float(size))
957
    except (IndexError, ValueError), err:
958
      logging.error("Invalid output from vgs (%s): %s", err, line)
959
      continue
960

    
961
    retval[name] = size
962

    
963
  return retval
964

    
965

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

969
  @type bridge: str
970
  @param bridge: the bridge name to check
971
  @rtype: boolean
972
  @return: True if it does
973

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

    
977

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

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

986
  @type fn: callable
987
  @param fn: function to apply to the value
988
  @param val: the value to be converted
989
  @return: The converted value if the conversion was successful,
990
      otherwise the original value.
991

992
  """
993
  try:
994
    nv = fn(val)
995
  except (ValueError, TypeError):
996
    nv = val
997
  return nv
998

    
999

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

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

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

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

1015
  """
1016
  return bool(_SHELLPARAM_REGEX.match(word))
1017

    
1018

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

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

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

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

    
1040

    
1041
def ParseCpuMask(cpu_mask):
1042
  """Parse a CPU mask definition and return the list of CPU IDs.
1043

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

1048
  @type cpu_mask: str
1049
  @param cpu_mask: CPU mask definition
1050
  @rtype: list of int
1051
  @return: list of CPU IDs
1052

1053
  """
1054
  if not cpu_mask:
1055
    return []
1056
  cpu_list = []
1057
  for range_def in cpu_mask.split(","):
1058
    boundaries = range_def.split("-")
1059
    n_elements = len(boundaries)
1060
    if n_elements > 2:
1061
      raise errors.ParseError("Invalid CPU ID range definition"
1062
                              " (only one hyphen allowed): %s" % range_def)
1063
    try:
1064
      lower = int(boundaries[0])
1065
    except (ValueError, TypeError), err:
1066
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
1067
                              " CPU ID range: %s" % str(err))
1068
    try:
1069
      higher = int(boundaries[-1])
1070
    except (ValueError, TypeError), err:
1071
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
1072
                              " CPU ID range: %s" % str(err))
1073
    if lower > higher:
1074
      raise errors.ParseError("Invalid CPU ID range definition"
1075
                              " (%d > %d): %s" % (lower, higher, range_def))
1076
    cpu_list.extend(range(lower, higher + 1))
1077
  return cpu_list
1078

    
1079

    
1080
def GetHomeDir(user, default=None):
1081
  """Try to get the homedir of the given user.
1082

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

1087
  """
1088
  try:
1089
    if isinstance(user, basestring):
1090
      result = pwd.getpwnam(user)
1091
    elif isinstance(user, (int, long)):
1092
      result = pwd.getpwuid(user)
1093
    else:
1094
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1095
                                   type(user))
1096
  except KeyError:
1097
    return default
1098
  return result.pw_dir
1099

    
1100

    
1101
def NewUUID():
1102
  """Returns a random UUID.
1103

1104
  @note: This is a Linux-specific method as it uses the /proc
1105
      filesystem.
1106
  @rtype: str
1107

1108
  """
1109
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1110

    
1111

    
1112
def FirstFree(seq, base=0):
1113
  """Returns the first non-existing integer from seq.
1114

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

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

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

1124
  @type seq: sequence
1125
  @param seq: the sequence to be analyzed.
1126
  @type base: int
1127
  @param base: use this value as the base index of the sequence
1128
  @rtype: int
1129
  @return: the first non-used index in the sequence
1130

1131
  """
1132
  for idx, elem in enumerate(seq):
1133
    assert elem >= base, "Passed element is higher than base offset"
1134
    if elem > idx + base:
1135
      # idx is not used
1136
      return idx + base
1137
  return None
1138

    
1139

    
1140
def SingleWaitForFdCondition(fdobj, event, timeout):
1141
  """Waits for a condition to occur on the socket.
1142

1143
  Immediately returns at the first interruption.
1144

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

1154
  """
1155
  check = (event | select.POLLPRI |
1156
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
1157

    
1158
  if timeout is not None:
1159
    # Poller object expects milliseconds
1160
    timeout *= 1000
1161

    
1162
  poller = select.poll()
1163
  poller.register(fdobj, event)
1164
  try:
1165
    # TODO: If the main thread receives a signal and we have no timeout, we
1166
    # could wait forever. This should check a global "quit" flag or something
1167
    # every so often.
1168
    io_events = poller.poll(timeout)
1169
  except select.error, err:
1170
    if err[0] != errno.EINTR:
1171
      raise
1172
    io_events = []
1173
  if io_events and io_events[0][1] & check:
1174
    return io_events[0][1]
1175
  else:
1176
    return None
1177

    
1178

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

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

1186
  """
1187

    
1188
  def __init__(self, timeout):
1189
    self.timeout = timeout
1190

    
1191
  def Poll(self, fdobj, event):
1192
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1193
    if result is None:
1194
      raise RetryAgain()
1195
    else:
1196
      return result
1197

    
1198
  def UpdateTimeout(self, timeout):
1199
    self.timeout = timeout
1200

    
1201

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

1205
  Retries until the timeout is expired, even if interrupted.
1206

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

1216
  """
1217
  if timeout is not None:
1218
    retrywaiter = FdConditionWaiterHelper(timeout)
1219
    try:
1220
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1221
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1222
    except RetryTimeout:
1223
      result = None
1224
  else:
1225
    result = None
1226
    while result is None:
1227
      result = SingleWaitForFdCondition(fdobj, event, timeout)
1228
  return result
1229

    
1230

    
1231
def CloseFDs(noclose_fds=None):
1232
  """Close file descriptors.
1233

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

1237
  @type noclose_fds: list or None
1238
  @param noclose_fds: if given, it denotes a list of file descriptor
1239
      that should not be closed
1240

1241
  """
1242
  # Default maximum for the number of available file descriptors.
1243
  if 'SC_OPEN_MAX' in os.sysconf_names:
1244
    try:
1245
      MAXFD = os.sysconf('SC_OPEN_MAX')
1246
      if MAXFD < 0:
1247
        MAXFD = 1024
1248
    except OSError:
1249
      MAXFD = 1024
1250
  else:
1251
    MAXFD = 1024
1252
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1253
  if (maxfd == resource.RLIM_INFINITY):
1254
    maxfd = MAXFD
1255

    
1256
  # Iterate through and close all file descriptors (except the standard ones)
1257
  for fd in range(3, maxfd):
1258
    if noclose_fds and fd in noclose_fds:
1259
      continue
1260
    CloseFdNoError(fd)
1261

    
1262

    
1263
def Daemonize(logfile):
1264
  """Daemonize the current process.
1265

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

1269
  @type logfile: str
1270
  @param logfile: the logfile to which we should redirect stdout/stderr
1271
  @rtype: int
1272
  @return: the value zero
1273

1274
  """
1275
  # pylint: disable-msg=W0212
1276
  # yes, we really want os._exit
1277

    
1278
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
1279
  # least abstract the pipe functionality between them
1280

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

    
1284
  # this might fail
1285
  pid = os.fork()
1286
  if (pid == 0):  # The first child.
1287
    SetupDaemonEnv()
1288

    
1289
    # this might fail
1290
    pid = os.fork() # Fork a second child.
1291
    if (pid == 0):  # The second child.
1292
      CloseFdNoError(rpipe)
1293
    else:
1294
      # exit() or _exit()?  See below.
1295
      os._exit(0) # Exit parent (the first child) of the second child.
1296
  else:
1297
    CloseFdNoError(wpipe)
1298
    # Wait for daemon to be started (or an error message to
1299
    # arrive) and read up to 100 KB as an error message
1300
    errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
1301
    if errormsg:
1302
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
1303
      rcode = 1
1304
    else:
1305
      rcode = 0
1306
    os._exit(rcode) # Exit parent of the first child.
1307

    
1308
  SetupDaemonFDs(logfile, None)
1309
  return wpipe
1310

    
1311

    
1312
def EnsureDaemon(name):
1313
  """Check for and start daemon if not alive.
1314

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

    
1322
  return True
1323

    
1324

    
1325
def StopDaemon(name):
1326
  """Stop daemon
1327

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

    
1335
  return True
1336

    
1337

    
1338
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1339
                waitpid=False):
1340
  """Kill a process given by its pid.
1341

1342
  @type pid: int
1343
  @param pid: The PID to terminate.
1344
  @type signal_: int
1345
  @param signal_: The signal to send, by default SIGTERM
1346
  @type timeout: int
1347
  @param timeout: The timeout after which, if the process is still alive,
1348
                  a SIGKILL will be sent. If not positive, no such checking
1349
                  will be done
1350
  @type waitpid: boolean
1351
  @param waitpid: If true, we should waitpid on this process after
1352
      sending signals, since it's our own child and otherwise it
1353
      would remain as zombie
1354

1355
  """
1356
  def _helper(pid, signal_, wait):
1357
    """Simple helper to encapsulate the kill/waitpid sequence"""
1358
    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
1359
      try:
1360
        os.waitpid(pid, os.WNOHANG)
1361
      except OSError:
1362
        pass
1363

    
1364
  if pid <= 0:
1365
    # kill with pid=0 == suicide
1366
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1367

    
1368
  if not IsProcessAlive(pid):
1369
    return
1370

    
1371
  _helper(pid, signal_, waitpid)
1372

    
1373
  if timeout <= 0:
1374
    return
1375

    
1376
  def _CheckProcess():
1377
    if not IsProcessAlive(pid):
1378
      return
1379

    
1380
    try:
1381
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1382
    except OSError:
1383
      raise RetryAgain()
1384

    
1385
    if result_pid > 0:
1386
      return
1387

    
1388
    raise RetryAgain()
1389

    
1390
  try:
1391
    # Wait up to $timeout seconds
1392
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1393
  except RetryTimeout:
1394
    pass
1395

    
1396
  if IsProcessAlive(pid):
1397
    # Kill process if it's still alive
1398
    _helper(pid, signal.SIGKILL, waitpid)
1399

    
1400

    
1401
def CheckVolumeGroupSize(vglist, vgname, minsize):
1402
  """Checks if the volume group list is valid.
1403

1404
  The function will check if a given volume group is in the list of
1405
  volume groups and has a minimum size.
1406

1407
  @type vglist: dict
1408
  @param vglist: dictionary of volume group names and their size
1409
  @type vgname: str
1410
  @param vgname: the volume group we should check
1411
  @type minsize: int
1412
  @param minsize: the minimum size we accept
1413
  @rtype: None or str
1414
  @return: None for success, otherwise the error message
1415

1416
  """
1417
  vgsize = vglist.get(vgname, None)
1418
  if vgsize is None:
1419
    return "volume group '%s' missing" % vgname
1420
  elif vgsize < minsize:
1421
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1422
            (vgname, minsize, vgsize))
1423
  return None
1424

    
1425

    
1426
def SplitTime(value):
1427
  """Splits time as floating point number into a tuple.
1428

1429
  @param value: Time in seconds
1430
  @type value: int or float
1431
  @return: Tuple containing (seconds, microseconds)
1432

1433
  """
1434
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1435

    
1436
  assert 0 <= seconds, \
1437
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1438
  assert 0 <= microseconds <= 999999, \
1439
    "Microseconds must be 0-999999, but are %s" % microseconds
1440

    
1441
  return (int(seconds), int(microseconds))
1442

    
1443

    
1444
def MergeTime(timetuple):
1445
  """Merges a tuple into time as a floating point number.
1446

1447
  @param timetuple: Time as tuple, (seconds, microseconds)
1448
  @type timetuple: tuple
1449
  @return: Time as a floating point number expressed in seconds
1450

1451
  """
1452
  (seconds, microseconds) = timetuple
1453

    
1454
  assert 0 <= seconds, \
1455
    "Seconds must be larger than or equal to 0, but are %s" % seconds
1456
  assert 0 <= microseconds <= 999999, \
1457
    "Microseconds must be 0-999999, but are %s" % microseconds
1458

    
1459
  return float(seconds) + (float(microseconds) * 0.000001)
1460

    
1461

    
1462
def FindMatch(data, name):
1463
  """Tries to find an item in a dictionary matching a name.
1464

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

1469
  @type data: dict
1470
  @param data: Dictionary containing data
1471
  @type name: string
1472
  @param name: Name to look for
1473
  @rtype: tuple; (value in dictionary, matched groups as list)
1474

1475
  """
1476
  if name in data:
1477
    return (data[name], [])
1478

    
1479
  for key, value in data.items():
1480
    # Regex objects
1481
    if hasattr(key, "match"):
1482
      m = key.match(name)
1483
      if m:
1484
        return (value, list(m.groups()))
1485

    
1486
  return None
1487

    
1488

    
1489
def GetMounts(filename=constants.PROC_MOUNTS):
1490
  """Returns the list of mounted filesystems.
1491

1492
  This function is Linux-specific.
1493

1494
  @param filename: path of mounts file (/proc/mounts by default)
1495
  @rtype: list of tuples
1496
  @return: list of mount entries (device, mountpoint, fstype, options)
1497

1498
  """
1499
  # TODO(iustin): investigate non-Linux options (e.g. via mount output)
1500
  data = []
1501
  mountlines = ReadFile(filename).splitlines()
1502
  for line in mountlines:
1503
    device, mountpoint, fstype, options, _ = line.split(None, 4)
1504
    data.append((device, mountpoint, fstype, options))
1505

    
1506
  return data
1507

    
1508

    
1509
def RunInSeparateProcess(fn, *args):
1510
  """Runs a function in a separate process.
1511

1512
  Note: Only boolean return values are supported.
1513

1514
  @type fn: callable
1515
  @param fn: Function to be called
1516
  @rtype: bool
1517
  @return: Function's result
1518

1519
  """
1520
  pid = os.fork()
1521
  if pid == 0:
1522
    # Child process
1523
    try:
1524
      # In case the function uses temporary files
1525
      ResetTempfileModule()
1526

    
1527
      # Call function
1528
      result = int(bool(fn(*args)))
1529
      assert result in (0, 1)
1530
    except: # pylint: disable-msg=W0702
1531
      logging.exception("Error while calling function in separate process")
1532
      # 0 and 1 are reserved for the return value
1533
      result = 33
1534

    
1535
    os._exit(result) # pylint: disable-msg=W0212
1536

    
1537
  # Parent process
1538

    
1539
  # Avoid zombies and check exit code
1540
  (_, status) = os.waitpid(pid, 0)
1541

    
1542
  if os.WIFSIGNALED(status):
1543
    exitcode = None
1544
    signum = os.WTERMSIG(status)
1545
  else:
1546
    exitcode = os.WEXITSTATUS(status)
1547
    signum = None
1548

    
1549
  if not (exitcode in (0, 1) and signum is None):
1550
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
1551
                              (exitcode, signum))
1552

    
1553
  return bool(exitcode)
1554

    
1555

    
1556
def SignalHandled(signums):
1557
  """Signal Handled decoration.
1558

1559
  This special decorator installs a signal handler and then calls the target
1560
  function. The function must accept a 'signal_handlers' keyword argument,
1561
  which will contain a dict indexed by signal number, with SignalHandler
1562
  objects as values.
1563

1564
  The decorator can be safely stacked with iself, to handle multiple signals
1565
  with different handlers.
1566

1567
  @type signums: list
1568
  @param signums: signals to intercept
1569

1570
  """
1571
  def wrap(fn):
1572
    def sig_function(*args, **kwargs):
1573
      assert 'signal_handlers' not in kwargs or \
1574
             kwargs['signal_handlers'] is None or \
1575
             isinstance(kwargs['signal_handlers'], dict), \
1576
             "Wrong signal_handlers parameter in original function call"
1577
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
1578
        signal_handlers = kwargs['signal_handlers']
1579
      else:
1580
        signal_handlers = {}
1581
        kwargs['signal_handlers'] = signal_handlers
1582
      sighandler = SignalHandler(signums)
1583
      try:
1584
        for sig in signums:
1585
          signal_handlers[sig] = sighandler
1586
        return fn(*args, **kwargs)
1587
      finally:
1588
        sighandler.Reset()
1589
    return sig_function
1590
  return wrap
1591

    
1592

    
1593
class SignalWakeupFd(object):
1594
  try:
1595
    # This is only supported in Python 2.5 and above (some distributions
1596
    # backported it to Python 2.4)
1597
    _set_wakeup_fd_fn = signal.set_wakeup_fd
1598
  except AttributeError:
1599
    # Not supported
1600
    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
1601
      return -1
1602
  else:
1603
    def _SetWakeupFd(self, fd):
1604
      return self._set_wakeup_fd_fn(fd)
1605

    
1606
  def __init__(self):
1607
    """Initializes this class.
1608

1609
    """
1610
    (read_fd, write_fd) = os.pipe()
1611

    
1612
    # Once these succeeded, the file descriptors will be closed automatically.
1613
    # Buffer size 0 is important, otherwise .read() with a specified length
1614
    # might buffer data and the file descriptors won't be marked readable.
1615
    self._read_fh = os.fdopen(read_fd, "r", 0)
1616
    self._write_fh = os.fdopen(write_fd, "w", 0)
1617

    
1618
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
1619

    
1620
    # Utility functions
1621
    self.fileno = self._read_fh.fileno
1622
    self.read = self._read_fh.read
1623

    
1624
  def Reset(self):
1625
    """Restores the previous wakeup file descriptor.
1626

1627
    """
1628
    if hasattr(self, "_previous") and self._previous is not None:
1629
      self._SetWakeupFd(self._previous)
1630
      self._previous = None
1631

    
1632
  def Notify(self):
1633
    """Notifies the wakeup file descriptor.
1634

1635
    """
1636
    self._write_fh.write("\0")
1637

    
1638
  def __del__(self):
1639
    """Called before object deletion.
1640

1641
    """
1642
    self.Reset()
1643

    
1644

    
1645
class SignalHandler(object):
1646
  """Generic signal handler class.
1647

1648
  It automatically restores the original handler when deconstructed or
1649
  when L{Reset} is called. You can either pass your own handler
1650
  function in or query the L{called} attribute to detect whether the
1651
  signal was sent.
1652

1653
  @type signum: list
1654
  @ivar signum: the signals we handle
1655
  @type called: boolean
1656
  @ivar called: tracks whether any of the signals have been raised
1657

1658
  """
1659
  def __init__(self, signum, handler_fn=None, wakeup=None):
1660
    """Constructs a new SignalHandler instance.
1661

1662
    @type signum: int or list of ints
1663
    @param signum: Single signal number or set of signal numbers
1664
    @type handler_fn: callable
1665
    @param handler_fn: Signal handling function
1666

1667
    """
1668
    assert handler_fn is None or callable(handler_fn)
1669

    
1670
    self.signum = set(signum)
1671
    self.called = False
1672

    
1673
    self._handler_fn = handler_fn
1674
    self._wakeup = wakeup
1675

    
1676
    self._previous = {}
1677
    try:
1678
      for signum in self.signum:
1679
        # Setup handler
1680
        prev_handler = signal.signal(signum, self._HandleSignal)
1681
        try:
1682
          self._previous[signum] = prev_handler
1683
        except:
1684
          # Restore previous handler
1685
          signal.signal(signum, prev_handler)
1686
          raise
1687
    except:
1688
      # Reset all handlers
1689
      self.Reset()
1690
      # Here we have a race condition: a handler may have already been called,
1691
      # but there's not much we can do about it at this point.
1692
      raise
1693

    
1694
  def __del__(self):
1695
    self.Reset()
1696

    
1697
  def Reset(self):
1698
    """Restore previous handler.
1699

1700
    This will reset all the signals to their previous handlers.
1701

1702
    """
1703
    for signum, prev_handler in self._previous.items():
1704
      signal.signal(signum, prev_handler)
1705
      # If successful, remove from dict
1706
      del self._previous[signum]
1707

    
1708
  def Clear(self):
1709
    """Unsets the L{called} flag.
1710

1711
    This function can be used in case a signal may arrive several times.
1712

1713
    """
1714
    self.called = False
1715

    
1716
  def _HandleSignal(self, signum, frame):
1717
    """Actual signal handling function.
1718

1719
    """
1720
    # This is not nice and not absolutely atomic, but it appears to be the only
1721
    # solution in Python -- there are no atomic types.
1722
    self.called = True
1723

    
1724
    if self._wakeup:
1725
      # Notify whoever is interested in signals
1726
      self._wakeup.Notify()
1727

    
1728
    if self._handler_fn:
1729
      self._handler_fn(signum, frame)
1730

    
1731

    
1732
class FieldSet(object):
1733
  """A simple field set.
1734

1735
  Among the features are:
1736
    - checking if a string is among a list of static string or regex objects
1737
    - checking if a whole list of string matches
1738
    - returning the matching groups from a regex match
1739

1740
  Internally, all fields are held as regular expression objects.
1741

1742
  """
1743
  def __init__(self, *items):
1744
    self.items = [re.compile("^%s$" % value) for value in items]
1745

    
1746
  def Extend(self, other_set):
1747
    """Extend the field set with the items from another one"""
1748
    self.items.extend(other_set.items)
1749

    
1750
  def Matches(self, field):
1751
    """Checks if a field matches the current set
1752

1753
    @type field: str
1754
    @param field: the string to match
1755
    @return: either None or a regular expression match object
1756

1757
    """
1758
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
1759
      return m
1760
    return None
1761

    
1762
  def NonMatching(self, items):
1763
    """Returns the list of fields not matching the current set
1764

1765
    @type items: list
1766
    @param items: the list of fields to check
1767
    @rtype: list
1768
    @return: list of non-matching fields
1769

1770
    """
1771
    return [val for val in items if not self.Matches(val)]
1772

    
1773

    
1774
class RunningTimeout(object):
1775
  """Class to calculate remaining timeout when doing several operations.
1776

1777
  """
1778
  __slots__ = [
1779
    "_allow_negative",
1780
    "_start_time",
1781
    "_time_fn",
1782
    "_timeout",
1783
    ]
1784

    
1785
  def __init__(self, timeout, allow_negative, _time_fn=time.time):
1786
    """Initializes this class.
1787

1788
    @type timeout: float
1789
    @param timeout: Timeout duration
1790
    @type allow_negative: bool
1791
    @param allow_negative: Whether to return values below zero
1792
    @param _time_fn: Time function for unittests
1793

1794
    """
1795
    object.__init__(self)
1796

    
1797
    if timeout is not None and timeout < 0.0:
1798
      raise ValueError("Timeout must not be negative")
1799

    
1800
    self._timeout = timeout
1801
    self._allow_negative = allow_negative
1802
    self._time_fn = _time_fn
1803

    
1804
    self._start_time = None
1805

    
1806
  def Remaining(self):
1807
    """Returns the remaining timeout.
1808

1809
    """
1810
    if self._timeout is None:
1811
      return None
1812

    
1813
    # Get start time on first calculation
1814
    if self._start_time is None:
1815
      self._start_time = self._time_fn()
1816

    
1817
    # Calculate remaining time
1818
    remaining_timeout = self._start_time + self._timeout - self._time_fn()
1819

    
1820
    if not self._allow_negative:
1821
      # Ensure timeout is always >= 0
1822
      return max(0.0, remaining_timeout)
1823

    
1824
    return remaining_timeout