Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 7b4baeb1

History | View | Annotate | Download (86.7 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 OpenSSL
47
import datetime
48
import calendar
49

    
50
from cStringIO import StringIO
51

    
52
from ganeti import errors
53
from ganeti import constants
54
from ganeti import compat
55

    
56
from ganeti.utils.algo import * # pylint: disable-msg=W0401
57
from ganeti.utils.retry import * # pylint: disable-msg=W0401
58
from ganeti.utils.text import * # pylint: disable-msg=W0401
59
from ganeti.utils.mlock import * # pylint: disable-msg=W0401
60
from ganeti.utils.log import * # pylint: disable-msg=W0401
61
from ganeti.utils.hash import * # pylint: disable-msg=W0401
62

    
63

    
64
#: when set to True, L{RunCmd} is disabled
65
_no_fork = False
66

    
67
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
68

    
69
HEX_CHAR_RE = r"[a-zA-Z0-9]"
70
VALID_X509_SIGNATURE_SALT = re.compile("^%s+$" % HEX_CHAR_RE, re.S)
71
X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
72
                            (re.escape(constants.X509_CERT_SIGNATURE_HEADER),
73
                             HEX_CHAR_RE, HEX_CHAR_RE),
74
                            re.S | re.I)
75

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

    
78
UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
79
                     '[a-f0-9]{4}-[a-f0-9]{12}$')
80

    
81
# Certificate verification results
82
(CERT_WARNING,
83
 CERT_ERROR) = range(1, 3)
84

    
85
(_TIMEOUT_NONE,
86
 _TIMEOUT_TERM,
87
 _TIMEOUT_KILL) = range(3)
88

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

    
92
#: ASN1 time regexp
93
_ASN1_TIME_REGEX = re.compile(r"^(\d+)([-+]\d\d)(\d\d)$")
94

    
95

    
96
def DisableFork():
97
  """Disables the use of fork(2).
98

99
  """
100
  global _no_fork # pylint: disable-msg=W0603
101

    
102
  _no_fork = True
103

    
104

    
105
class RunResult(object):
106
  """Holds the result of running external programs.
107

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

123
  """
124
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
125
               "failed", "fail_reason", "cmd"]
126

    
127

    
128
  def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
129
               timeout):
130
    self.cmd = cmd
131
    self.exit_code = exit_code
132
    self.signal = signal_
133
    self.stdout = stdout
134
    self.stderr = stderr
135
    self.failed = (signal_ is not None or exit_code != 0)
136

    
137
    fail_msgs = []
138
    if self.signal is not None:
139
      fail_msgs.append("terminated by signal %s" % self.signal)
140
    elif self.exit_code is not None:
141
      fail_msgs.append("exited with exit code %s" % self.exit_code)
142
    else:
143
      fail_msgs.append("unable to determine termination reason")
144

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

    
152
    if fail_msgs and self.failed:
153
      self.fail_reason = CommaJoin(fail_msgs)
154

    
155
    if self.failed:
156
      logging.debug("Command '%s' failed (%s); output: %s",
157
                    self.cmd, self.fail_reason, self.output)
158

    
159
  def _GetOutput(self):
160
    """Returns the combined stdout and stderr for easier usage.
161

162
    """
163
    return self.stdout + self.stderr
164

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

    
167

    
168
def _BuildCmdEnvironment(env, reset):
169
  """Builds the environment for an external program.
170

171
  """
172
  if reset:
173
    cmd_env = {}
174
  else:
175
    cmd_env = os.environ.copy()
176
    cmd_env["LC_ALL"] = "C"
177

    
178
  if env is not None:
179
    cmd_env.update(env)
180

    
181
  return cmd_env
182

    
183

    
184
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
185
           interactive=False, timeout=None):
186
  """Execute a (shell) command.
187

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

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

214
  """
215
  if _no_fork:
216
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
217

    
218
  if output and interactive:
219
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
220
                                 " not be provided at the same time")
221

    
222
  if isinstance(cmd, basestring):
223
    strcmd = cmd
224
    shell = True
225
  else:
226
    cmd = [str(val) for val in cmd]
227
    strcmd = ShellQuoteArgs(cmd)
228
    shell = False
229

    
230
  if output:
231
    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
232
  else:
233
    logging.debug("RunCmd %s", strcmd)
234

    
235
  cmd_env = _BuildCmdEnvironment(env, reset_env)
236

    
237
  try:
238
    if output is None:
239
      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
240
                                                     interactive, timeout)
241
    else:
242
      timeout_action = _TIMEOUT_NONE
243
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
244
      out = err = ""
245
  except OSError, err:
246
    if err.errno == errno.ENOENT:
247
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
248
                               (strcmd, err))
249
    else:
250
      raise
251

    
252
  if status >= 0:
253
    exitcode = status
254
    signal_ = None
255
  else:
256
    exitcode = None
257
    signal_ = -status
258

    
259
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
260

    
261

    
262
def SetupDaemonEnv(cwd="/", umask=077):
263
  """Setup a daemon's environment.
264

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

268
  @param cwd: the directory to which to chdir
269
  @param umask: the umask to setup
270

271
  """
272
  os.chdir(cwd)
273
  os.umask(umask)
274
  os.setsid()
275

    
276

    
277
def SetupDaemonFDs(output_file, output_fd):
278
  """Setups up a daemon's file descriptors.
279

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

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

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

    
291
  if output_fd is not None:
292
    pass
293
  elif output_file is not None:
294
    # Open output file
295
    try:
296
      output_fd = os.open(output_file,
297
                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
298
    except EnvironmentError, err:
299
      raise Exception("Opening output file failed: %s" % err)
300
  else:
301
    output_fd = os.open(os.devnull, os.O_WRONLY)
302

    
303
  # Redirect standard I/O
304
  os.dup2(devnull_fd, 0)
305
  os.dup2(output_fd, 1)
306
  os.dup2(output_fd, 2)
307

    
308

    
309
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
310
                pidfile=None):
311
  """Start a daemon process after forking twice.
312

313
  @type cmd: string or list
314
  @param cmd: Command to run
315
  @type env: dict
316
  @param env: Additional environment variables
317
  @type cwd: string
318
  @param cwd: Working directory for the program
319
  @type output: string
320
  @param output: Path to file in which to save the output
321
  @type output_fd: int
322
  @param output_fd: File descriptor for output
323
  @type pidfile: string
324
  @param pidfile: Process ID file
325
  @rtype: int
326
  @return: Daemon process ID
327
  @raise errors.ProgrammerError: if we call this when forks are disabled
328

329
  """
330
  if _no_fork:
331
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
332
                                 " disabled")
333

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

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

    
341
  strcmd = ShellQuoteArgs(cmd)
342

    
343
  if output:
344
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
345
  else:
346
    logging.debug("StartDaemon %s", strcmd)
347

    
348
  cmd_env = _BuildCmdEnvironment(env, False)
349

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

    
373
        # Wait for daemon to be started (or an error message to
374
        # arrive) and read up to 100 KB as an error message
375
        errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
376
      finally:
377
        CloseFdNoError(errpipe_read)
378
    finally:
379
      CloseFdNoError(pidpipe_write)
380

    
381
    # Read up to 128 bytes for PID
382
    pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
383
  finally:
384
    CloseFdNoError(pidpipe_read)
385

    
386
  # Try to avoid zombies by waiting for child process
387
  try:
388
    os.waitpid(pid, 0)
389
  except OSError:
390
    pass
391

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

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

    
402

    
403
def _StartDaemonChild(errpipe_read, errpipe_write,
404
                      pidpipe_read, pidpipe_write,
405
                      args, env, cwd,
406
                      output, fd_output, pidfile):
407
  """Child process for starting daemon.
408

409
  """
410
  try:
411
    # Close parent's side
412
    CloseFdNoError(errpipe_read)
413
    CloseFdNoError(pidpipe_read)
414

    
415
    # First child process
416
    SetupDaemonEnv()
417

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

    
424
    # Make sure pipe is closed on execv* (and thereby notifies
425
    # original process)
426
    SetCloseOnExecFlag(errpipe_write, True)
427

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

    
431
    # Open PID file
432
    if pidfile:
433
      fd_pidfile = WritePidFile(pidfile)
434

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

    
438
      SetCloseOnExecFlag(fd_pidfile, False)
439
    else:
440
      fd_pidfile = None
441

    
442
    SetupDaemonFDs(output, fd_output)
443

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

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

    
450
    # Change working directory
451
    os.chdir(cwd)
452

    
453
    if env is None:
454
      os.execvp(args[0], args)
455
    else:
456
      os.execvpe(args[0], args, env)
457
  except: # pylint: disable-msg=W0702
458
    try:
459
      # Report errors to original process
460
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
461
    except: # pylint: disable-msg=W0702
462
      # Ignore errors in error handling
463
      pass
464

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

    
467

    
468
def WriteErrorToFD(fd, err):
469
  """Possibly write an error message to a fd.
470

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

475
  """
476
  if fd is None:
477
    return
478

    
479
  if not err:
480
    err = "<unknown error>"
481

    
482
  RetryOnSignal(os.write, fd, err)
483

    
484

    
485
def _CheckIfAlive(child):
486
  """Raises L{RetryAgain} if child is still alive.
487

488
  @raises RetryAgain: If child is still alive
489

490
  """
491
  if child.poll() is None:
492
    raise RetryAgain()
493

    
494

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

498
  """
499
  try:
500
    Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout), args=[child])
501
  except RetryTimeout:
502
    pass
503

    
504

    
505
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
506
                _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
507
  """Run a command and return its output.
508

509
  @type  cmd: string or list
510
  @param cmd: Command to run
511
  @type env: dict
512
  @param env: The environment to use
513
  @type via_shell: bool
514
  @param via_shell: if we should run via the shell
515
  @type cwd: string
516
  @param cwd: the working directory for the program
517
  @type interactive: boolean
518
  @param interactive: Run command interactive (without piping)
519
  @type timeout: int
520
  @param timeout: Timeout after the programm gets terminated
521
  @rtype: tuple
522
  @return: (out, err, status)
523

524
  """
525
  poller = select.poll()
526

    
527
  stderr = subprocess.PIPE
528
  stdout = subprocess.PIPE
529
  stdin = subprocess.PIPE
530

    
531
  if interactive:
532
    stderr = stdout = stdin = None
533

    
534
  child = subprocess.Popen(cmd, shell=via_shell,
535
                           stderr=stderr,
536
                           stdout=stdout,
537
                           stdin=stdin,
538
                           close_fds=True, env=env,
539
                           cwd=cwd)
540

    
541
  out = StringIO()
542
  err = StringIO()
543

    
544
  linger_timeout = None
545

    
546
  if timeout is None:
547
    poll_timeout = None
548
  else:
549
    poll_timeout = RunningTimeout(timeout, True).Remaining
550

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

    
556
  timeout_action = _TIMEOUT_NONE
557

    
558
  if not interactive:
559
    child.stdin.close()
560
    poller.register(child.stdout, select.POLLIN)
561
    poller.register(child.stderr, select.POLLIN)
562
    fdmap = {
563
      child.stdout.fileno(): (out, child.stdout),
564
      child.stderr.fileno(): (err, child.stderr),
565
      }
566
    for fd in fdmap:
567
      SetNonblockFlag(fd, True)
568

    
569
    while fdmap:
570
      if poll_timeout:
571
        pt = poll_timeout() * 1000
572
        if pt < 0:
573
          if linger_timeout is None:
574
            logging.warning(msg_timeout)
575
            if child.poll() is None:
576
              timeout_action = _TIMEOUT_TERM
577
              IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
578
            linger_timeout = RunningTimeout(_linger_timeout, True).Remaining
579
          pt = linger_timeout() * 1000
580
          if pt < 0:
581
            break
582
      else:
583
        pt = None
584

    
585
      pollresult = RetryOnSignal(poller.poll, pt)
586

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

    
601
  if timeout is not None:
602
    assert callable(poll_timeout)
603

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

    
608
    # Terminate if still alive after timeout
609
    if child.poll() is None:
610
      if linger_timeout is None:
611
        logging.warning(msg_timeout)
612
        timeout_action = _TIMEOUT_TERM
613
        IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
614
        lt = _linger_timeout
615
      else:
616
        lt = linger_timeout()
617
      _WaitForProcess(child, lt)
618

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

    
625
  out = out.getvalue()
626
  err = err.getvalue()
627

    
628
  status = child.wait()
629
  return out, err, status, timeout_action
630

    
631

    
632
def _RunCmdFile(cmd, env, via_shell, output, cwd):
633
  """Run a command and save its output to a file.
634

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

648
  """
649
  fh = open(output, "a")
650
  try:
651
    child = subprocess.Popen(cmd, shell=via_shell,
652
                             stderr=subprocess.STDOUT,
653
                             stdout=fh,
654
                             stdin=subprocess.PIPE,
655
                             close_fds=True, env=env,
656
                             cwd=cwd)
657

    
658
    child.stdin.close()
659
    status = child.wait()
660
  finally:
661
    fh.close()
662
  return status
663

    
664

    
665
def SetCloseOnExecFlag(fd, enable):
666
  """Sets or unsets the close-on-exec flag on a file descriptor.
667

668
  @type fd: int
669
  @param fd: File descriptor
670
  @type enable: bool
671
  @param enable: Whether to set or unset it.
672

673
  """
674
  flags = fcntl.fcntl(fd, fcntl.F_GETFD)
675

    
676
  if enable:
677
    flags |= fcntl.FD_CLOEXEC
678
  else:
679
    flags &= ~fcntl.FD_CLOEXEC
680

    
681
  fcntl.fcntl(fd, fcntl.F_SETFD, flags)
682

    
683

    
684
def SetNonblockFlag(fd, enable):
685
  """Sets or unsets the O_NONBLOCK flag on on a file descriptor.
686

687
  @type fd: int
688
  @param fd: File descriptor
689
  @type enable: bool
690
  @param enable: Whether to set or unset it
691

692
  """
693
  flags = fcntl.fcntl(fd, fcntl.F_GETFL)
694

    
695
  if enable:
696
    flags |= os.O_NONBLOCK
697
  else:
698
    flags &= ~os.O_NONBLOCK
699

    
700
  fcntl.fcntl(fd, fcntl.F_SETFL, flags)
701

    
702

    
703
def RetryOnSignal(fn, *args, **kwargs):
704
  """Calls a function again if it failed due to EINTR.
705

706
  """
707
  while True:
708
    try:
709
      return fn(*args, **kwargs)
710
    except EnvironmentError, err:
711
      if err.errno != errno.EINTR:
712
        raise
713
    except (socket.error, select.error), err:
714
      # In python 2.6 and above select.error is an IOError, so it's handled
715
      # above, in 2.5 and below it's not, and it's handled here.
716
      if not (err.args and err.args[0] == errno.EINTR):
717
        raise
718

    
719

    
720
def RunParts(dir_name, env=None, reset_env=False):
721
  """Run Scripts or programs in a directory
722

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

732
  """
733
  rr = []
734

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

    
741
  for relname in sorted(dir_contents):
742
    fname = PathJoin(dir_name, relname)
743
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
744
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
745
      rr.append((relname, constants.RUNPARTS_SKIP, None))
746
    else:
747
      try:
748
        result = RunCmd([fname], env=env, reset_env=reset_env)
749
      except Exception, err: # pylint: disable-msg=W0703
750
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
751
      else:
752
        rr.append((relname, constants.RUNPARTS_RUN, result))
753

    
754
  return rr
755

    
756

    
757
def RemoveFile(filename):
758
  """Remove a file ignoring some errors.
759

760
  Remove a file, ignoring non-existing ones or directories. Other
761
  errors are passed.
762

763
  @type filename: str
764
  @param filename: the file to be removed
765

766
  """
767
  try:
768
    os.unlink(filename)
769
  except OSError, err:
770
    if err.errno not in (errno.ENOENT, errno.EISDIR):
771
      raise
772

    
773

    
774
def RemoveDir(dirname):
775
  """Remove an empty directory.
776

777
  Remove a directory, ignoring non-existing ones.
778
  Other errors are passed. This includes the case,
779
  where the directory is not empty, so it can't be removed.
780

781
  @type dirname: str
782
  @param dirname: the empty directory to be removed
783

784
  """
785
  try:
786
    os.rmdir(dirname)
787
  except OSError, err:
788
    if err.errno != errno.ENOENT:
789
      raise
790

    
791

    
792
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
793
  """Renames a file.
794

795
  @type old: string
796
  @param old: Original path
797
  @type new: string
798
  @param new: New path
799
  @type mkdir: bool
800
  @param mkdir: Whether to create target directory if it doesn't exist
801
  @type mkdir_mode: int
802
  @param mkdir_mode: Mode for newly created directories
803

804
  """
805
  try:
806
    return os.rename(old, new)
807
  except OSError, err:
808
    # In at least one use case of this function, the job queue, directory
809
    # creation is very rare. Checking for the directory before renaming is not
810
    # as efficient.
811
    if mkdir and err.errno == errno.ENOENT:
812
      # Create directory and try again
813
      Makedirs(os.path.dirname(new), mode=mkdir_mode)
814

    
815
      return os.rename(old, new)
816

    
817
    raise
818

    
819

    
820
def Makedirs(path, mode=0750):
821
  """Super-mkdir; create a leaf directory and all intermediate ones.
822

823
  This is a wrapper around C{os.makedirs} adding error handling not implemented
824
  before Python 2.5.
825

826
  """
827
  try:
828
    os.makedirs(path, mode)
829
  except OSError, err:
830
    # Ignore EEXIST. This is only handled in os.makedirs as included in
831
    # Python 2.5 and above.
832
    if err.errno != errno.EEXIST or not os.path.exists(path):
833
      raise
834

    
835

    
836
def ResetTempfileModule():
837
  """Resets the random name generator of the tempfile module.
838

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

845
  """
846
  # pylint: disable-msg=W0212
847
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
848
    tempfile._once_lock.acquire()
849
    try:
850
      # Reset random name generator
851
      tempfile._name_sequence = None
852
    finally:
853
      tempfile._once_lock.release()
854
  else:
855
    logging.critical("The tempfile module misses at least one of the"
856
                     " '_once_lock' and '_name_sequence' attributes")
857

    
858

    
859
def ForceDictType(target, key_types, allowed_values=None):
860
  """Force the values of a dict to have certain types.
861

862
  @type target: dict
863
  @param target: the dict to update
864
  @type key_types: dict
865
  @param key_types: dict mapping target dict keys to types
866
                    in constants.ENFORCEABLE_TYPES
867
  @type allowed_values: list
868
  @keyword allowed_values: list of specially allowed values
869

870
  """
871
  if allowed_values is None:
872
    allowed_values = []
873

    
874
  if not isinstance(target, dict):
875
    msg = "Expected dictionary, got '%s'" % target
876
    raise errors.TypeEnforcementError(msg)
877

    
878
  for key in target:
879
    if key not in key_types:
880
      msg = "Unknown key '%s'" % key
881
      raise errors.TypeEnforcementError(msg)
882

    
883
    if target[key] in allowed_values:
884
      continue
885

    
886
    ktype = key_types[key]
887
    if ktype not in constants.ENFORCEABLE_TYPES:
888
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
889
      raise errors.ProgrammerError(msg)
890

    
891
    if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
892
      if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
893
        pass
894
      elif not isinstance(target[key], basestring):
895
        if isinstance(target[key], bool) and not target[key]:
896
          target[key] = ''
897
        else:
898
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
899
          raise errors.TypeEnforcementError(msg)
900
    elif ktype == constants.VTYPE_BOOL:
901
      if isinstance(target[key], basestring) and target[key]:
902
        if target[key].lower() == constants.VALUE_FALSE:
903
          target[key] = False
904
        elif target[key].lower() == constants.VALUE_TRUE:
905
          target[key] = True
906
        else:
907
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
908
          raise errors.TypeEnforcementError(msg)
909
      elif target[key]:
910
        target[key] = True
911
      else:
912
        target[key] = False
913
    elif ktype == constants.VTYPE_SIZE:
914
      try:
915
        target[key] = ParseUnit(target[key])
916
      except errors.UnitParseError, err:
917
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
918
              (key, target[key], err)
919
        raise errors.TypeEnforcementError(msg)
920
    elif ktype == constants.VTYPE_INT:
921
      try:
922
        target[key] = int(target[key])
923
      except (ValueError, TypeError):
924
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
925
        raise errors.TypeEnforcementError(msg)
926

    
927

    
928
def _GetProcStatusPath(pid):
929
  """Returns the path for a PID's proc status file.
930

931
  @type pid: int
932
  @param pid: Process ID
933
  @rtype: string
934

935
  """
936
  return "/proc/%d/status" % pid
937

    
938

    
939
def IsProcessAlive(pid):
940
  """Check if a given pid exists on the system.
941

942
  @note: zombie status is not handled, so zombie processes
943
      will be returned as alive
944
  @type pid: int
945
  @param pid: the process ID to check
946
  @rtype: boolean
947
  @return: True if the process exists
948

949
  """
950
  def _TryStat(name):
951
    try:
952
      os.stat(name)
953
      return True
954
    except EnvironmentError, err:
955
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
956
        return False
957
      elif err.errno == errno.EINVAL:
958
        raise RetryAgain(err)
959
      raise
960

    
961
  assert isinstance(pid, int), "pid must be an integer"
962
  if pid <= 0:
963
    return False
964

    
965
  # /proc in a multiprocessor environment can have strange behaviors.
966
  # Retry the os.stat a few times until we get a good result.
967
  try:
968
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
969
                 args=[_GetProcStatusPath(pid)])
970
  except RetryTimeout, err:
971
    err.RaiseInner()
972

    
973

    
974
def _ParseSigsetT(sigset):
975
  """Parse a rendered sigset_t value.
976

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

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

985
  """
986
  result = set()
987

    
988
  signum = 0
989
  for ch in reversed(sigset):
990
    chv = int(ch, 16)
991

    
992
    # The following could be done in a loop, but it's easier to read and
993
    # understand in the unrolled form
994
    if chv & 1:
995
      result.add(signum + 1)
996
    if chv & 2:
997
      result.add(signum + 2)
998
    if chv & 4:
999
      result.add(signum + 3)
1000
    if chv & 8:
1001
      result.add(signum + 4)
1002

    
1003
    signum += 4
1004

    
1005
  return result
1006

    
1007

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

1011
  @type pstatus: string
1012
  @param pstatus: Contents of /proc/$pid/status
1013
  @type field: string
1014
  @param field: Name of field whose value should be returned
1015
  @rtype: string
1016

1017
  """
1018
  for line in pstatus.splitlines():
1019
    parts = line.split(":", 1)
1020

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

    
1024
    return parts[1].strip()
1025

    
1026
  return None
1027

    
1028

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

1032
  @type pid: int
1033
  @param pid: Process ID
1034
  @type signum: int
1035
  @param signum: Signal number
1036
  @rtype: bool
1037

1038
  """
1039
  if status_path is None:
1040
    status_path = _GetProcStatusPath(pid)
1041

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

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

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

    
1057

    
1058
def ReadPidFile(pidfile):
1059
  """Read a pid from a file.
1060

1061
  @type  pidfile: string
1062
  @param pidfile: path to the file containing the pid
1063
  @rtype: int
1064
  @return: The process id, if the file exists and contains a valid PID,
1065
           otherwise 0
1066

1067
  """
1068
  try:
1069
    raw_data = ReadOneLineFile(pidfile)
1070
  except EnvironmentError, err:
1071
    if err.errno != errno.ENOENT:
1072
      logging.exception("Can't read pid file")
1073
    return 0
1074

    
1075
  try:
1076
    pid = int(raw_data)
1077
  except (TypeError, ValueError), err:
1078
    logging.info("Can't parse pid file contents", exc_info=True)
1079
    return 0
1080

    
1081
  return pid
1082

    
1083

    
1084
def ReadLockedPidFile(path):
1085
  """Reads a locked PID file.
1086

1087
  This can be used together with L{StartDaemon}.
1088

1089
  @type path: string
1090
  @param path: Path to PID file
1091
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
1092

1093
  """
1094
  try:
1095
    fd = os.open(path, os.O_RDONLY)
1096
  except EnvironmentError, err:
1097
    if err.errno == errno.ENOENT:
1098
      # PID file doesn't exist
1099
      return None
1100
    raise
1101

    
1102
  try:
1103
    try:
1104
      # Try to acquire lock
1105
      LockFile(fd)
1106
    except errors.LockError:
1107
      # Couldn't lock, daemon is running
1108
      return int(os.read(fd, 100))
1109
  finally:
1110
    os.close(fd)
1111

    
1112
  return None
1113

    
1114

    
1115
def ValidateServiceName(name):
1116
  """Validate the given service name.
1117

1118
  @type name: number or string
1119
  @param name: Service name or port specification
1120

1121
  """
1122
  try:
1123
    numport = int(name)
1124
  except (ValueError, TypeError):
1125
    # Non-numeric service name
1126
    valid = _VALID_SERVICE_NAME_RE.match(name)
1127
  else:
1128
    # Numeric port (protocols other than TCP or UDP might need adjustments
1129
    # here)
1130
    valid = (numport >= 0 and numport < (1 << 16))
1131

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

    
1136
  return name
1137

    
1138

    
1139
def ListVolumeGroups():
1140
  """List volume groups and their size
1141

1142
  @rtype: dict
1143
  @return:
1144
       Dictionary with keys volume name and values
1145
       the size of the volume
1146

1147
  """
1148
  command = "vgs --noheadings --units m --nosuffix -o name,size"
1149
  result = RunCmd(command)
1150
  retval = {}
1151
  if result.failed:
1152
    return retval
1153

    
1154
  for line in result.stdout.splitlines():
1155
    try:
1156
      name, size = line.split()
1157
      size = int(float(size))
1158
    except (IndexError, ValueError), err:
1159
      logging.error("Invalid output from vgs (%s): %s", err, line)
1160
      continue
1161

    
1162
    retval[name] = size
1163

    
1164
  return retval
1165

    
1166

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

1170
  @type bridge: str
1171
  @param bridge: the bridge name to check
1172
  @rtype: boolean
1173
  @return: True if it does
1174

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

    
1178

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

1182
  This function tries to apply function I{fn} to I{val}. If no
1183
  C{ValueError} or C{TypeError} exceptions are raised, it will return
1184
  the result, else it will return the original value. Any other
1185
  exceptions are propagated to the caller.
1186

1187
  @type fn: callable
1188
  @param fn: function to apply to the value
1189
  @param val: the value to be converted
1190
  @return: The converted value if the conversion was successful,
1191
      otherwise the original value.
1192

1193
  """
1194
  try:
1195
    nv = fn(val)
1196
  except (ValueError, TypeError):
1197
    nv = val
1198
  return nv
1199

    
1200

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

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

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

1211
  @type word: str
1212
  @param word: the word to check
1213
  @rtype: boolean
1214
  @return: True if the word is 'safe'
1215

1216
  """
1217
  return bool(_SHELLPARAM_REGEX.match(word))
1218

    
1219

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

1223
  This function will check all arguments in the args list so that they
1224
  are valid shell parameters (i.e. they don't contain shell
1225
  metacharacters). If everything is ok, it will return the result of
1226
  template % args.
1227

1228
  @type template: str
1229
  @param template: the string holding the template for the
1230
      string formatting
1231
  @rtype: str
1232
  @return: the expanded command line
1233

1234
  """
1235
  for word in args:
1236
    if not IsValidShellParam(word):
1237
      raise errors.ProgrammerError("Shell argument '%s' contains"
1238
                                   " invalid characters" % word)
1239
  return template % args
1240

    
1241

    
1242
def ParseCpuMask(cpu_mask):
1243
  """Parse a CPU mask definition and return the list of CPU IDs.
1244

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

1249
  @type cpu_mask: str
1250
  @param cpu_mask: CPU mask definition
1251
  @rtype: list of int
1252
  @return: list of CPU IDs
1253

1254
  """
1255
  if not cpu_mask:
1256
    return []
1257
  cpu_list = []
1258
  for range_def in cpu_mask.split(","):
1259
    boundaries = range_def.split("-")
1260
    n_elements = len(boundaries)
1261
    if n_elements > 2:
1262
      raise errors.ParseError("Invalid CPU ID range definition"
1263
                              " (only one hyphen allowed): %s" % range_def)
1264
    try:
1265
      lower = int(boundaries[0])
1266
    except (ValueError, TypeError), err:
1267
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
1268
                              " CPU ID range: %s" % str(err))
1269
    try:
1270
      higher = int(boundaries[-1])
1271
    except (ValueError, TypeError), err:
1272
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
1273
                              " CPU ID range: %s" % str(err))
1274
    if lower > higher:
1275
      raise errors.ParseError("Invalid CPU ID range definition"
1276
                              " (%d > %d): %s" % (lower, higher, range_def))
1277
    cpu_list.extend(range(lower, higher + 1))
1278
  return cpu_list
1279

    
1280

    
1281
def AddAuthorizedKey(file_obj, key):
1282
  """Adds an SSH public key to an authorized_keys file.
1283

1284
  @type file_obj: str or file handle
1285
  @param file_obj: path to authorized_keys file
1286
  @type key: str
1287
  @param key: string containing key
1288

1289
  """
1290
  key_fields = key.split()
1291

    
1292
  if isinstance(file_obj, basestring):
1293
    f = open(file_obj, 'a+')
1294
  else:
1295
    f = file_obj
1296

    
1297
  try:
1298
    nl = True
1299
    for line in f:
1300
      # Ignore whitespace changes
1301
      if line.split() == key_fields:
1302
        break
1303
      nl = line.endswith('\n')
1304
    else:
1305
      if not nl:
1306
        f.write("\n")
1307
      f.write(key.rstrip('\r\n'))
1308
      f.write("\n")
1309
      f.flush()
1310
  finally:
1311
    f.close()
1312

    
1313

    
1314
def RemoveAuthorizedKey(file_name, key):
1315
  """Removes an SSH public key from an authorized_keys file.
1316

1317
  @type file_name: str
1318
  @param file_name: path to authorized_keys file
1319
  @type key: str
1320
  @param key: string containing key
1321

1322
  """
1323
  key_fields = key.split()
1324

    
1325
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1326
  try:
1327
    out = os.fdopen(fd, 'w')
1328
    try:
1329
      f = open(file_name, 'r')
1330
      try:
1331
        for line in f:
1332
          # Ignore whitespace changes while comparing lines
1333
          if line.split() != key_fields:
1334
            out.write(line)
1335

    
1336
        out.flush()
1337
        os.rename(tmpname, file_name)
1338
      finally:
1339
        f.close()
1340
    finally:
1341
      out.close()
1342
  except:
1343
    RemoveFile(tmpname)
1344
    raise
1345

    
1346

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

1350
  @type file_name: str
1351
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1352
  @type ip: str
1353
  @param ip: the IP address
1354
  @type hostname: str
1355
  @param hostname: the hostname to be added
1356
  @type aliases: list
1357
  @param aliases: the list of aliases to add for the hostname
1358

1359
  """
1360
  # Ensure aliases are unique
1361
  aliases = UniqueSequence([hostname] + aliases)[1:]
1362

    
1363
  def _WriteEtcHosts(fd):
1364
    # Duplicating file descriptor because os.fdopen's result will automatically
1365
    # close the descriptor, but we would still like to have its functionality.
1366
    out = os.fdopen(os.dup(fd), "w")
1367
    try:
1368
      for line in ReadFile(file_name).splitlines(True):
1369
        fields = line.split()
1370
        if fields and not fields[0].startswith("#") and ip == fields[0]:
1371
          continue
1372
        out.write(line)
1373

    
1374
      out.write("%s\t%s" % (ip, hostname))
1375
      if aliases:
1376
        out.write(" %s" % " ".join(aliases))
1377
      out.write("\n")
1378
      out.flush()
1379
    finally:
1380
      out.close()
1381

    
1382
  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
1383

    
1384

    
1385
def AddHostToEtcHosts(hostname, ip):
1386
  """Wrapper around SetEtcHostsEntry.
1387

1388
  @type hostname: str
1389
  @param hostname: a hostname that will be resolved and added to
1390
      L{constants.ETC_HOSTS}
1391
  @type ip: str
1392
  @param ip: The ip address of the host
1393

1394
  """
1395
  SetEtcHostsEntry(constants.ETC_HOSTS, ip, hostname, [hostname.split(".")[0]])
1396

    
1397

    
1398
def RemoveEtcHostsEntry(file_name, hostname):
1399
  """Removes a hostname from /etc/hosts.
1400

1401
  IP addresses without names are removed from the file.
1402

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

1408
  """
1409
  def _WriteEtcHosts(fd):
1410
    # Duplicating file descriptor because os.fdopen's result will automatically
1411
    # close the descriptor, but we would still like to have its functionality.
1412
    out = os.fdopen(os.dup(fd), "w")
1413
    try:
1414
      for line in ReadFile(file_name).splitlines(True):
1415
        fields = line.split()
1416
        if len(fields) > 1 and not fields[0].startswith("#"):
1417
          names = fields[1:]
1418
          if hostname in names:
1419
            while hostname in names:
1420
              names.remove(hostname)
1421
            if names:
1422
              out.write("%s %s\n" % (fields[0], " ".join(names)))
1423
            continue
1424

    
1425
        out.write(line)
1426

    
1427
      out.flush()
1428
    finally:
1429
      out.close()
1430

    
1431
  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
1432

    
1433

    
1434
def RemoveHostFromEtcHosts(hostname):
1435
  """Wrapper around RemoveEtcHostsEntry.
1436

1437
  @type hostname: str
1438
  @param hostname: hostname that will be resolved and its
1439
      full and shot name will be removed from
1440
      L{constants.ETC_HOSTS}
1441

1442
  """
1443
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname)
1444
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname.split(".")[0])
1445

    
1446

    
1447
def TimestampForFilename():
1448
  """Returns the current time formatted for filenames.
1449

1450
  The format doesn't contain colons as some shells and applications treat them
1451
  as separators. Uses the local timezone.
1452

1453
  """
1454
  return time.strftime("%Y-%m-%d_%H_%M_%S")
1455

    
1456

    
1457
def CreateBackup(file_name):
1458
  """Creates a backup of a file.
1459

1460
  @type file_name: str
1461
  @param file_name: file to be backed up
1462
  @rtype: str
1463
  @return: the path to the newly created backup
1464
  @raise errors.ProgrammerError: for invalid file names
1465

1466
  """
1467
  if not os.path.isfile(file_name):
1468
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1469
                                file_name)
1470

    
1471
  prefix = ("%s.backup-%s." %
1472
            (os.path.basename(file_name), TimestampForFilename()))
1473
  dir_name = os.path.dirname(file_name)
1474

    
1475
  fsrc = open(file_name, 'rb')
1476
  try:
1477
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1478
    fdst = os.fdopen(fd, 'wb')
1479
    try:
1480
      logging.debug("Backing up %s at %s", file_name, backup_name)
1481
      shutil.copyfileobj(fsrc, fdst)
1482
    finally:
1483
      fdst.close()
1484
  finally:
1485
    fsrc.close()
1486

    
1487
  return backup_name
1488

    
1489

    
1490
def ListVisibleFiles(path):
1491
  """Returns a list of visible files in a directory.
1492

1493
  @type path: str
1494
  @param path: the directory to enumerate
1495
  @rtype: list
1496
  @return: the list of all files not starting with a dot
1497
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
1498

1499
  """
1500
  if not IsNormAbsPath(path):
1501
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1502
                                 " absolute/normalized: '%s'" % path)
1503
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1504
  return files
1505

    
1506

    
1507
def GetHomeDir(user, default=None):
1508
  """Try to get the homedir of the given user.
1509

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

1514
  """
1515
  try:
1516
    if isinstance(user, basestring):
1517
      result = pwd.getpwnam(user)
1518
    elif isinstance(user, (int, long)):
1519
      result = pwd.getpwuid(user)
1520
    else:
1521
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1522
                                   type(user))
1523
  except KeyError:
1524
    return default
1525
  return result.pw_dir
1526

    
1527

    
1528
def NewUUID():
1529
  """Returns a random UUID.
1530

1531
  @note: This is a Linux-specific method as it uses the /proc
1532
      filesystem.
1533
  @rtype: str
1534

1535
  """
1536
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1537

    
1538

    
1539
def EnsureDirs(dirs):
1540
  """Make required directories, if they don't exist.
1541

1542
  @param dirs: list of tuples (dir_name, dir_mode)
1543
  @type dirs: list of (string, integer)
1544

1545
  """
1546
  for dir_name, dir_mode in dirs:
1547
    try:
1548
      os.mkdir(dir_name, dir_mode)
1549
    except EnvironmentError, err:
1550
      if err.errno != errno.EEXIST:
1551
        raise errors.GenericError("Cannot create needed directory"
1552
                                  " '%s': %s" % (dir_name, err))
1553
    try:
1554
      os.chmod(dir_name, dir_mode)
1555
    except EnvironmentError, err:
1556
      raise errors.GenericError("Cannot change directory permissions on"
1557
                                " '%s': %s" % (dir_name, err))
1558
    if not os.path.isdir(dir_name):
1559
      raise errors.GenericError("%s is not a directory" % dir_name)
1560

    
1561

    
1562
def ReadFile(file_name, size=-1):
1563
  """Reads a file.
1564

1565
  @type size: int
1566
  @param size: Read at most size bytes (if negative, entire file)
1567
  @rtype: str
1568
  @return: the (possibly partial) content of the file
1569

1570
  """
1571
  f = open(file_name, "r")
1572
  try:
1573
    return f.read(size)
1574
  finally:
1575
    f.close()
1576

    
1577

    
1578
def WriteFile(file_name, fn=None, data=None,
1579
              mode=None, uid=-1, gid=-1,
1580
              atime=None, mtime=None, close=True,
1581
              dry_run=False, backup=False,
1582
              prewrite=None, postwrite=None):
1583
  """(Over)write a file atomically.
1584

1585
  The file_name and either fn (a function taking one argument, the
1586
  file descriptor, and which should write the data to it) or data (the
1587
  contents of the file) must be passed. The other arguments are
1588
  optional and allow setting the file mode, owner and group, and the
1589
  mtime/atime of the file.
1590

1591
  If the function doesn't raise an exception, it has succeeded and the
1592
  target file has the new contents. If the function has raised an
1593
  exception, an existing target file should be unmodified and the
1594
  temporary file should be removed.
1595

1596
  @type file_name: str
1597
  @param file_name: the target filename
1598
  @type fn: callable
1599
  @param fn: content writing function, called with
1600
      file descriptor as parameter
1601
  @type data: str
1602
  @param data: contents of the file
1603
  @type mode: int
1604
  @param mode: file mode
1605
  @type uid: int
1606
  @param uid: the owner of the file
1607
  @type gid: int
1608
  @param gid: the group of the file
1609
  @type atime: int
1610
  @param atime: a custom access time to be set on the file
1611
  @type mtime: int
1612
  @param mtime: a custom modification time to be set on the file
1613
  @type close: boolean
1614
  @param close: whether to close file after writing it
1615
  @type prewrite: callable
1616
  @param prewrite: function to be called before writing content
1617
  @type postwrite: callable
1618
  @param postwrite: function to be called after writing content
1619

1620
  @rtype: None or int
1621
  @return: None if the 'close' parameter evaluates to True,
1622
      otherwise the file descriptor
1623

1624
  @raise errors.ProgrammerError: if any of the arguments are not valid
1625

1626
  """
1627
  if not os.path.isabs(file_name):
1628
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1629
                                 " absolute: '%s'" % file_name)
1630

    
1631
  if [fn, data].count(None) != 1:
1632
    raise errors.ProgrammerError("fn or data required")
1633

    
1634
  if [atime, mtime].count(None) == 1:
1635
    raise errors.ProgrammerError("Both atime and mtime must be either"
1636
                                 " set or None")
1637

    
1638
  if backup and not dry_run and os.path.isfile(file_name):
1639
    CreateBackup(file_name)
1640

    
1641
  dir_name, base_name = os.path.split(file_name)
1642
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1643
  do_remove = True
1644
  # here we need to make sure we remove the temp file, if any error
1645
  # leaves it in place
1646
  try:
1647
    if uid != -1 or gid != -1:
1648
      os.chown(new_name, uid, gid)
1649
    if mode:
1650
      os.chmod(new_name, mode)
1651
    if callable(prewrite):
1652
      prewrite(fd)
1653
    if data is not None:
1654
      os.write(fd, data)
1655
    else:
1656
      fn(fd)
1657
    if callable(postwrite):
1658
      postwrite(fd)
1659
    os.fsync(fd)
1660
    if atime is not None and mtime is not None:
1661
      os.utime(new_name, (atime, mtime))
1662
    if not dry_run:
1663
      os.rename(new_name, file_name)
1664
      do_remove = False
1665
  finally:
1666
    if close:
1667
      os.close(fd)
1668
      result = None
1669
    else:
1670
      result = fd
1671
    if do_remove:
1672
      RemoveFile(new_name)
1673

    
1674
  return result
1675

    
1676

    
1677
def GetFileID(path=None, fd=None):
1678
  """Returns the file 'id', i.e. the dev/inode and mtime information.
1679

1680
  Either the path to the file or the fd must be given.
1681

1682
  @param path: the file path
1683
  @param fd: a file descriptor
1684
  @return: a tuple of (device number, inode number, mtime)
1685

1686
  """
1687
  if [path, fd].count(None) != 1:
1688
    raise errors.ProgrammerError("One and only one of fd/path must be given")
1689

    
1690
  if fd is None:
1691
    st = os.stat(path)
1692
  else:
1693
    st = os.fstat(fd)
1694

    
1695
  return (st.st_dev, st.st_ino, st.st_mtime)
1696

    
1697

    
1698
def VerifyFileID(fi_disk, fi_ours):
1699
  """Verifies that two file IDs are matching.
1700

1701
  Differences in the inode/device are not accepted, but and older
1702
  timestamp for fi_disk is accepted.
1703

1704
  @param fi_disk: tuple (dev, inode, mtime) representing the actual
1705
      file data
1706
  @param fi_ours: tuple (dev, inode, mtime) representing the last
1707
      written file data
1708
  @rtype: boolean
1709

1710
  """
1711
  (d1, i1, m1) = fi_disk
1712
  (d2, i2, m2) = fi_ours
1713

    
1714
  return (d1, i1) == (d2, i2) and m1 <= m2
1715

    
1716

    
1717
def SafeWriteFile(file_name, file_id, **kwargs):
1718
  """Wraper over L{WriteFile} that locks the target file.
1719

1720
  By keeping the target file locked during WriteFile, we ensure that
1721
  cooperating writers will safely serialise access to the file.
1722

1723
  @type file_name: str
1724
  @param file_name: the target filename
1725
  @type file_id: tuple
1726
  @param file_id: a result from L{GetFileID}
1727

1728
  """
1729
  fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
1730
  try:
1731
    LockFile(fd)
1732
    if file_id is not None:
1733
      disk_id = GetFileID(fd=fd)
1734
      if not VerifyFileID(disk_id, file_id):
1735
        raise errors.LockError("Cannot overwrite file %s, it has been modified"
1736
                               " since last written" % file_name)
1737
    return WriteFile(file_name, **kwargs)
1738
  finally:
1739
    os.close(fd)
1740

    
1741

    
1742
def ReadOneLineFile(file_name, strict=False):
1743
  """Return the first non-empty line from a file.
1744

1745
  @type strict: boolean
1746
  @param strict: if True, abort if the file has more than one
1747
      non-empty line
1748

1749
  """
1750
  file_lines = ReadFile(file_name).splitlines()
1751
  full_lines = filter(bool, file_lines)
1752
  if not file_lines or not full_lines:
1753
    raise errors.GenericError("No data in one-liner file %s" % file_name)
1754
  elif strict and len(full_lines) > 1:
1755
    raise errors.GenericError("Too many lines in one-liner file %s" %
1756
                              file_name)
1757
  return full_lines[0]
1758

    
1759

    
1760
def FirstFree(seq, base=0):
1761
  """Returns the first non-existing integer from seq.
1762

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

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

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

1772
  @type seq: sequence
1773
  @param seq: the sequence to be analyzed.
1774
  @type base: int
1775
  @param base: use this value as the base index of the sequence
1776
  @rtype: int
1777
  @return: the first non-used index in the sequence
1778

1779
  """
1780
  for idx, elem in enumerate(seq):
1781
    assert elem >= base, "Passed element is higher than base offset"
1782
    if elem > idx + base:
1783
      # idx is not used
1784
      return idx + base
1785
  return None
1786

    
1787

    
1788
def SingleWaitForFdCondition(fdobj, event, timeout):
1789
  """Waits for a condition to occur on the socket.
1790

1791
  Immediately returns at the first interruption.
1792

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

1802
  """
1803
  check = (event | select.POLLPRI |
1804
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
1805

    
1806
  if timeout is not None:
1807
    # Poller object expects milliseconds
1808
    timeout *= 1000
1809

    
1810
  poller = select.poll()
1811
  poller.register(fdobj, event)
1812
  try:
1813
    # TODO: If the main thread receives a signal and we have no timeout, we
1814
    # could wait forever. This should check a global "quit" flag or something
1815
    # every so often.
1816
    io_events = poller.poll(timeout)
1817
  except select.error, err:
1818
    if err[0] != errno.EINTR:
1819
      raise
1820
    io_events = []
1821
  if io_events and io_events[0][1] & check:
1822
    return io_events[0][1]
1823
  else:
1824
    return None
1825

    
1826

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

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

1834
  """
1835

    
1836
  def __init__(self, timeout):
1837
    self.timeout = timeout
1838

    
1839
  def Poll(self, fdobj, event):
1840
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1841
    if result is None:
1842
      raise RetryAgain()
1843
    else:
1844
      return result
1845

    
1846
  def UpdateTimeout(self, timeout):
1847
    self.timeout = timeout
1848

    
1849

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

1853
  Retries until the timeout is expired, even if interrupted.
1854

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

1864
  """
1865
  if timeout is not None:
1866
    retrywaiter = FdConditionWaiterHelper(timeout)
1867
    try:
1868
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1869
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1870
    except RetryTimeout:
1871
      result = None
1872
  else:
1873
    result = None
1874
    while result is None:
1875
      result = SingleWaitForFdCondition(fdobj, event, timeout)
1876
  return result
1877

    
1878

    
1879
def TestDelay(duration):
1880
  """Sleep for a fixed amount of time.
1881

1882
  @type duration: float
1883
  @param duration: the sleep duration
1884
  @rtype: boolean
1885
  @return: False for negative value, True otherwise
1886

1887
  """
1888
  if duration < 0:
1889
    return False, "Invalid sleep duration"
1890
  time.sleep(duration)
1891
  return True, None
1892

    
1893

    
1894
def CloseFdNoError(fd, retries=5):
1895
  """Close a file descriptor ignoring errors.
1896

1897
  @type fd: int
1898
  @param fd: the file descriptor
1899
  @type retries: int
1900
  @param retries: how many retries to make, in case we get any
1901
      other error than EBADF
1902

1903
  """
1904
  try:
1905
    os.close(fd)
1906
  except OSError, err:
1907
    if err.errno != errno.EBADF:
1908
      if retries > 0:
1909
        CloseFdNoError(fd, retries - 1)
1910
    # else either it's closed already or we're out of retries, so we
1911
    # ignore this and go on
1912

    
1913

    
1914
def CloseFDs(noclose_fds=None):
1915
  """Close file descriptors.
1916

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

1920
  @type noclose_fds: list or None
1921
  @param noclose_fds: if given, it denotes a list of file descriptor
1922
      that should not be closed
1923

1924
  """
1925
  # Default maximum for the number of available file descriptors.
1926
  if 'SC_OPEN_MAX' in os.sysconf_names:
1927
    try:
1928
      MAXFD = os.sysconf('SC_OPEN_MAX')
1929
      if MAXFD < 0:
1930
        MAXFD = 1024
1931
    except OSError:
1932
      MAXFD = 1024
1933
  else:
1934
    MAXFD = 1024
1935
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1936
  if (maxfd == resource.RLIM_INFINITY):
1937
    maxfd = MAXFD
1938

    
1939
  # Iterate through and close all file descriptors (except the standard ones)
1940
  for fd in range(3, maxfd):
1941
    if noclose_fds and fd in noclose_fds:
1942
      continue
1943
    CloseFdNoError(fd)
1944

    
1945

    
1946
def Daemonize(logfile):
1947
  """Daemonize the current process.
1948

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

1952
  @type logfile: str
1953
  @param logfile: the logfile to which we should redirect stdout/stderr
1954
  @rtype: int
1955
  @return: the value zero
1956

1957
  """
1958
  # pylint: disable-msg=W0212
1959
  # yes, we really want os._exit
1960

    
1961
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
1962
  # least abstract the pipe functionality between them
1963

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

    
1967
  # this might fail
1968
  pid = os.fork()
1969
  if (pid == 0):  # The first child.
1970
    SetupDaemonEnv()
1971

    
1972
    # this might fail
1973
    pid = os.fork() # Fork a second child.
1974
    if (pid == 0):  # The second child.
1975
      CloseFdNoError(rpipe)
1976
    else:
1977
      # exit() or _exit()?  See below.
1978
      os._exit(0) # Exit parent (the first child) of the second child.
1979
  else:
1980
    CloseFdNoError(wpipe)
1981
    # Wait for daemon to be started (or an error message to
1982
    # arrive) and read up to 100 KB as an error message
1983
    errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
1984
    if errormsg:
1985
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
1986
      rcode = 1
1987
    else:
1988
      rcode = 0
1989
    os._exit(rcode) # Exit parent of the first child.
1990

    
1991
  SetupDaemonFDs(logfile, None)
1992
  return wpipe
1993

    
1994

    
1995
def DaemonPidFileName(name):
1996
  """Compute a ganeti pid file absolute path
1997

1998
  @type name: str
1999
  @param name: the daemon name
2000
  @rtype: str
2001
  @return: the full path to the pidfile corresponding to the given
2002
      daemon name
2003

2004
  """
2005
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
2006

    
2007

    
2008
def EnsureDaemon(name):
2009
  """Check for and start daemon if not alive.
2010

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

    
2018
  return True
2019

    
2020

    
2021
def StopDaemon(name):
2022
  """Stop daemon
2023

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

    
2031
  return True
2032

    
2033

    
2034
def WritePidFile(pidfile):
2035
  """Write the current process pidfile.
2036

2037
  @type pidfile: string
2038
  @param pidfile: the path to the file to be written
2039
  @raise errors.LockError: if the pid file already exists and
2040
      points to a live process
2041
  @rtype: int
2042
  @return: the file descriptor of the lock file; do not close this unless
2043
      you want to unlock the pid file
2044

2045
  """
2046
  # We don't rename nor truncate the file to not drop locks under
2047
  # existing processes
2048
  fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
2049

    
2050
  # Lock the PID file (and fail if not possible to do so). Any code
2051
  # wanting to send a signal to the daemon should try to lock the PID
2052
  # file before reading it. If acquiring the lock succeeds, the daemon is
2053
  # no longer running and the signal should not be sent.
2054
  LockFile(fd_pidfile)
2055

    
2056
  os.write(fd_pidfile, "%d\n" % os.getpid())
2057

    
2058
  return fd_pidfile
2059

    
2060

    
2061
def RemovePidFile(pidfile):
2062
  """Remove the current process pidfile.
2063

2064
  Any errors are ignored.
2065

2066
  @type pidfile: string
2067
  @param pidfile: Path to the file to be removed
2068

2069
  """
2070
  # TODO: we could check here that the file contains our pid
2071
  try:
2072
    RemoveFile(pidfile)
2073
  except Exception: # pylint: disable-msg=W0703
2074
    pass
2075

    
2076

    
2077
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
2078
                waitpid=False):
2079
  """Kill a process given by its pid.
2080

2081
  @type pid: int
2082
  @param pid: The PID to terminate.
2083
  @type signal_: int
2084
  @param signal_: The signal to send, by default SIGTERM
2085
  @type timeout: int
2086
  @param timeout: The timeout after which, if the process is still alive,
2087
                  a SIGKILL will be sent. If not positive, no such checking
2088
                  will be done
2089
  @type waitpid: boolean
2090
  @param waitpid: If true, we should waitpid on this process after
2091
      sending signals, since it's our own child and otherwise it
2092
      would remain as zombie
2093

2094
  """
2095
  def _helper(pid, signal_, wait):
2096
    """Simple helper to encapsulate the kill/waitpid sequence"""
2097
    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
2098
      try:
2099
        os.waitpid(pid, os.WNOHANG)
2100
      except OSError:
2101
        pass
2102

    
2103
  if pid <= 0:
2104
    # kill with pid=0 == suicide
2105
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
2106

    
2107
  if not IsProcessAlive(pid):
2108
    return
2109

    
2110
  _helper(pid, signal_, waitpid)
2111

    
2112
  if timeout <= 0:
2113
    return
2114

    
2115
  def _CheckProcess():
2116
    if not IsProcessAlive(pid):
2117
      return
2118

    
2119
    try:
2120
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
2121
    except OSError:
2122
      raise RetryAgain()
2123

    
2124
    if result_pid > 0:
2125
      return
2126

    
2127
    raise RetryAgain()
2128

    
2129
  try:
2130
    # Wait up to $timeout seconds
2131
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
2132
  except RetryTimeout:
2133
    pass
2134

    
2135
  if IsProcessAlive(pid):
2136
    # Kill process if it's still alive
2137
    _helper(pid, signal.SIGKILL, waitpid)
2138

    
2139

    
2140
def FindFile(name, search_path, test=os.path.exists):
2141
  """Look for a filesystem object in a given path.
2142

2143
  This is an abstract method to search for filesystem object (files,
2144
  dirs) under a given search path.
2145

2146
  @type name: str
2147
  @param name: the name to look for
2148
  @type search_path: str
2149
  @param search_path: location to start at
2150
  @type test: callable
2151
  @param test: a function taking one argument that should return True
2152
      if the a given object is valid; the default value is
2153
      os.path.exists, causing only existing files to be returned
2154
  @rtype: str or None
2155
  @return: full path to the object if found, None otherwise
2156

2157
  """
2158
  # validate the filename mask
2159
  if constants.EXT_PLUGIN_MASK.match(name) is None:
2160
    logging.critical("Invalid value passed for external script name: '%s'",
2161
                     name)
2162
    return None
2163

    
2164
  for dir_name in search_path:
2165
    # FIXME: investigate switch to PathJoin
2166
    item_name = os.path.sep.join([dir_name, name])
2167
    # check the user test and that we're indeed resolving to the given
2168
    # basename
2169
    if test(item_name) and os.path.basename(item_name) == name:
2170
      return item_name
2171
  return None
2172

    
2173

    
2174
def CheckVolumeGroupSize(vglist, vgname, minsize):
2175
  """Checks if the volume group list is valid.
2176

2177
  The function will check if a given volume group is in the list of
2178
  volume groups and has a minimum size.
2179

2180
  @type vglist: dict
2181
  @param vglist: dictionary of volume group names and their size
2182
  @type vgname: str
2183
  @param vgname: the volume group we should check
2184
  @type minsize: int
2185
  @param minsize: the minimum size we accept
2186
  @rtype: None or str
2187
  @return: None for success, otherwise the error message
2188

2189
  """
2190
  vgsize = vglist.get(vgname, None)
2191
  if vgsize is None:
2192
    return "volume group '%s' missing" % vgname
2193
  elif vgsize < minsize:
2194
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
2195
            (vgname, minsize, vgsize))
2196
  return None
2197

    
2198

    
2199
def SplitTime(value):
2200
  """Splits time as floating point number into a tuple.
2201

2202
  @param value: Time in seconds
2203
  @type value: int or float
2204
  @return: Tuple containing (seconds, microseconds)
2205

2206
  """
2207
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
2208

    
2209
  assert 0 <= seconds, \
2210
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2211
  assert 0 <= microseconds <= 999999, \
2212
    "Microseconds must be 0-999999, but are %s" % microseconds
2213

    
2214
  return (int(seconds), int(microseconds))
2215

    
2216

    
2217
def MergeTime(timetuple):
2218
  """Merges a tuple into time as a floating point number.
2219

2220
  @param timetuple: Time as tuple, (seconds, microseconds)
2221
  @type timetuple: tuple
2222
  @return: Time as a floating point number expressed in seconds
2223

2224
  """
2225
  (seconds, microseconds) = timetuple
2226

    
2227
  assert 0 <= seconds, \
2228
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2229
  assert 0 <= microseconds <= 999999, \
2230
    "Microseconds must be 0-999999, but are %s" % microseconds
2231

    
2232
  return float(seconds) + (float(microseconds) * 0.000001)
2233

    
2234

    
2235
def IsNormAbsPath(path):
2236
  """Check whether a path is absolute and also normalized
2237

2238
  This avoids things like /dir/../../other/path to be valid.
2239

2240
  """
2241
  return os.path.normpath(path) == path and os.path.isabs(path)
2242

    
2243

    
2244
def PathJoin(*args):
2245
  """Safe-join a list of path components.
2246

2247
  Requirements:
2248
      - the first argument must be an absolute path
2249
      - no component in the path must have backtracking (e.g. /../),
2250
        since we check for normalization at the end
2251

2252
  @param args: the path components to be joined
2253
  @raise ValueError: for invalid paths
2254

2255
  """
2256
  # ensure we're having at least one path passed in
2257
  assert args
2258
  # ensure the first component is an absolute and normalized path name
2259
  root = args[0]
2260
  if not IsNormAbsPath(root):
2261
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2262
  result = os.path.join(*args)
2263
  # ensure that the whole path is normalized
2264
  if not IsNormAbsPath(result):
2265
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2266
  # check that we're still under the original prefix
2267
  prefix = os.path.commonprefix([root, result])
2268
  if prefix != root:
2269
    raise ValueError("Error: path joining resulted in different prefix"
2270
                     " (%s != %s)" % (prefix, root))
2271
  return result
2272

    
2273

    
2274
def TailFile(fname, lines=20):
2275
  """Return the last lines from a file.
2276

2277
  @note: this function will only read and parse the last 4KB of
2278
      the file; if the lines are very long, it could be that less
2279
      than the requested number of lines are returned
2280

2281
  @param fname: the file name
2282
  @type lines: int
2283
  @param lines: the (maximum) number of lines to return
2284

2285
  """
2286
  fd = open(fname, "r")
2287
  try:
2288
    fd.seek(0, 2)
2289
    pos = fd.tell()
2290
    pos = max(0, pos-4096)
2291
    fd.seek(pos, 0)
2292
    raw_data = fd.read()
2293
  finally:
2294
    fd.close()
2295

    
2296
  rows = raw_data.splitlines()
2297
  return rows[-lines:]
2298

    
2299

    
2300
def _ParseAsn1Generalizedtime(value):
2301
  """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2302

2303
  @type value: string
2304
  @param value: ASN1 GENERALIZEDTIME timestamp
2305
  @return: Seconds since the Epoch (1970-01-01 00:00:00 UTC)
2306

2307
  """
2308
  m = _ASN1_TIME_REGEX.match(value)
2309
  if m:
2310
    # We have an offset
2311
    asn1time = m.group(1)
2312
    hours = int(m.group(2))
2313
    minutes = int(m.group(3))
2314
    utcoffset = (60 * hours) + minutes
2315
  else:
2316
    if not value.endswith("Z"):
2317
      raise ValueError("Missing timezone")
2318
    asn1time = value[:-1]
2319
    utcoffset = 0
2320

    
2321
  parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2322

    
2323
  tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2324

    
2325
  return calendar.timegm(tt.utctimetuple())
2326

    
2327

    
2328
def GetX509CertValidity(cert):
2329
  """Returns the validity period of the certificate.
2330

2331
  @type cert: OpenSSL.crypto.X509
2332
  @param cert: X509 certificate object
2333

2334
  """
2335
  # The get_notBefore and get_notAfter functions are only supported in
2336
  # pyOpenSSL 0.7 and above.
2337
  try:
2338
    get_notbefore_fn = cert.get_notBefore
2339
  except AttributeError:
2340
    not_before = None
2341
  else:
2342
    not_before_asn1 = get_notbefore_fn()
2343

    
2344
    if not_before_asn1 is None:
2345
      not_before = None
2346
    else:
2347
      not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2348

    
2349
  try:
2350
    get_notafter_fn = cert.get_notAfter
2351
  except AttributeError:
2352
    not_after = None
2353
  else:
2354
    not_after_asn1 = get_notafter_fn()
2355

    
2356
    if not_after_asn1 is None:
2357
      not_after = None
2358
    else:
2359
      not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2360

    
2361
  return (not_before, not_after)
2362

    
2363

    
2364
def _VerifyCertificateInner(expired, not_before, not_after, now,
2365
                            warn_days, error_days):
2366
  """Verifies certificate validity.
2367

2368
  @type expired: bool
2369
  @param expired: Whether pyOpenSSL considers the certificate as expired
2370
  @type not_before: number or None
2371
  @param not_before: Unix timestamp before which certificate is not valid
2372
  @type not_after: number or None
2373
  @param not_after: Unix timestamp after which certificate is invalid
2374
  @type now: number
2375
  @param now: Current time as Unix timestamp
2376
  @type warn_days: number or None
2377
  @param warn_days: How many days before expiration a warning should be reported
2378
  @type error_days: number or None
2379
  @param error_days: How many days before expiration an error should be reported
2380

2381
  """
2382
  if expired:
2383
    msg = "Certificate is expired"
2384

    
2385
    if not_before is not None and not_after is not None:
2386
      msg += (" (valid from %s to %s)" %
2387
              (FormatTime(not_before), FormatTime(not_after)))
2388
    elif not_before is not None:
2389
      msg += " (valid from %s)" % FormatTime(not_before)
2390
    elif not_after is not None:
2391
      msg += " (valid until %s)" % FormatTime(not_after)
2392

    
2393
    return (CERT_ERROR, msg)
2394

    
2395
  elif not_before is not None and not_before > now:
2396
    return (CERT_WARNING,
2397
            "Certificate not yet valid (valid from %s)" %
2398
            FormatTime(not_before))
2399

    
2400
  elif not_after is not None:
2401
    remaining_days = int((not_after - now) / (24 * 3600))
2402

    
2403
    msg = "Certificate expires in about %d days" % remaining_days
2404

    
2405
    if error_days is not None and remaining_days <= error_days:
2406
      return (CERT_ERROR, msg)
2407

    
2408
    if warn_days is not None and remaining_days <= warn_days:
2409
      return (CERT_WARNING, msg)
2410

    
2411
  return (None, None)
2412

    
2413

    
2414
def VerifyX509Certificate(cert, warn_days, error_days):
2415
  """Verifies a certificate for LUVerifyCluster.
2416

2417
  @type cert: OpenSSL.crypto.X509
2418
  @param cert: X509 certificate object
2419
  @type warn_days: number or None
2420
  @param warn_days: How many days before expiration a warning should be reported
2421
  @type error_days: number or None
2422
  @param error_days: How many days before expiration an error should be reported
2423

2424
  """
2425
  # Depending on the pyOpenSSL version, this can just return (None, None)
2426
  (not_before, not_after) = GetX509CertValidity(cert)
2427

    
2428
  return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
2429
                                 time.time(), warn_days, error_days)
2430

    
2431

    
2432
def SignX509Certificate(cert, key, salt):
2433
  """Sign a X509 certificate.
2434

2435
  An RFC822-like signature header is added in front of the certificate.
2436

2437
  @type cert: OpenSSL.crypto.X509
2438
  @param cert: X509 certificate object
2439
  @type key: string
2440
  @param key: Key for HMAC
2441
  @type salt: string
2442
  @param salt: Salt for HMAC
2443
  @rtype: string
2444
  @return: Serialized and signed certificate in PEM format
2445

2446
  """
2447
  if not VALID_X509_SIGNATURE_SALT.match(salt):
2448
    raise errors.GenericError("Invalid salt: %r" % salt)
2449

    
2450
  # Dumping as PEM here ensures the certificate is in a sane format
2451
  cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2452

    
2453
  return ("%s: %s/%s\n\n%s" %
2454
          (constants.X509_CERT_SIGNATURE_HEADER, salt,
2455
           Sha1Hmac(key, cert_pem, salt=salt),
2456
           cert_pem))
2457

    
2458

    
2459
def _ExtractX509CertificateSignature(cert_pem):
2460
  """Helper function to extract signature from X509 certificate.
2461

2462
  """
2463
  # Extract signature from original PEM data
2464
  for line in cert_pem.splitlines():
2465
    if line.startswith("---"):
2466
      break
2467

    
2468
    m = X509_SIGNATURE.match(line.strip())
2469
    if m:
2470
      return (m.group("salt"), m.group("sign"))
2471

    
2472
  raise errors.GenericError("X509 certificate signature is missing")
2473

    
2474

    
2475
def LoadSignedX509Certificate(cert_pem, key):
2476
  """Verifies a signed X509 certificate.
2477

2478
  @type cert_pem: string
2479
  @param cert_pem: Certificate in PEM format and with signature header
2480
  @type key: string
2481
  @param key: Key for HMAC
2482
  @rtype: tuple; (OpenSSL.crypto.X509, string)
2483
  @return: X509 certificate object and salt
2484

2485
  """
2486
  (salt, signature) = _ExtractX509CertificateSignature(cert_pem)
2487

    
2488
  # Load certificate
2489
  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
2490

    
2491
  # Dump again to ensure it's in a sane format
2492
  sane_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2493

    
2494
  if not VerifySha1Hmac(key, sane_pem, signature, salt=salt):
2495
    raise errors.GenericError("X509 certificate signature is invalid")
2496

    
2497
  return (cert, salt)
2498

    
2499

    
2500
def FindMatch(data, name):
2501
  """Tries to find an item in a dictionary matching a name.
2502

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

2507
  @type data: dict
2508
  @param data: Dictionary containing data
2509
  @type name: string
2510
  @param name: Name to look for
2511
  @rtype: tuple; (value in dictionary, matched groups as list)
2512

2513
  """
2514
  if name in data:
2515
    return (data[name], [])
2516

    
2517
  for key, value in data.items():
2518
    # Regex objects
2519
    if hasattr(key, "match"):
2520
      m = key.match(name)
2521
      if m:
2522
        return (value, list(m.groups()))
2523

    
2524
  return None
2525

    
2526

    
2527
def BytesToMebibyte(value):
2528
  """Converts bytes to mebibytes.
2529

2530
  @type value: int
2531
  @param value: Value in bytes
2532
  @rtype: int
2533
  @return: Value in mebibytes
2534

2535
  """
2536
  return int(round(value / (1024.0 * 1024.0), 0))
2537

    
2538

    
2539
def CalculateDirectorySize(path):
2540
  """Calculates the size of a directory recursively.
2541

2542
  @type path: string
2543
  @param path: Path to directory
2544
  @rtype: int
2545
  @return: Size in mebibytes
2546

2547
  """
2548
  size = 0
2549

    
2550
  for (curpath, _, files) in os.walk(path):
2551
    for filename in files:
2552
      st = os.lstat(PathJoin(curpath, filename))
2553
      size += st.st_size
2554

    
2555
  return BytesToMebibyte(size)
2556

    
2557

    
2558
def GetMounts(filename=constants.PROC_MOUNTS):
2559
  """Returns the list of mounted filesystems.
2560

2561
  This function is Linux-specific.
2562

2563
  @param filename: path of mounts file (/proc/mounts by default)
2564
  @rtype: list of tuples
2565
  @return: list of mount entries (device, mountpoint, fstype, options)
2566

2567
  """
2568
  # TODO(iustin): investigate non-Linux options (e.g. via mount output)
2569
  data = []
2570
  mountlines = ReadFile(filename).splitlines()
2571
  for line in mountlines:
2572
    device, mountpoint, fstype, options, _ = line.split(None, 4)
2573
    data.append((device, mountpoint, fstype, options))
2574

    
2575
  return data
2576

    
2577

    
2578
def GetFilesystemStats(path):
2579
  """Returns the total and free space on a filesystem.
2580

2581
  @type path: string
2582
  @param path: Path on filesystem to be examined
2583
  @rtype: int
2584
  @return: tuple of (Total space, Free space) in mebibytes
2585

2586
  """
2587
  st = os.statvfs(path)
2588

    
2589
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2590
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2591
  return (tsize, fsize)
2592

    
2593

    
2594
def RunInSeparateProcess(fn, *args):
2595
  """Runs a function in a separate process.
2596

2597
  Note: Only boolean return values are supported.
2598

2599
  @type fn: callable
2600
  @param fn: Function to be called
2601
  @rtype: bool
2602
  @return: Function's result
2603

2604
  """
2605
  pid = os.fork()
2606
  if pid == 0:
2607
    # Child process
2608
    try:
2609
      # In case the function uses temporary files
2610
      ResetTempfileModule()
2611

    
2612
      # Call function
2613
      result = int(bool(fn(*args)))
2614
      assert result in (0, 1)
2615
    except: # pylint: disable-msg=W0702
2616
      logging.exception("Error while calling function in separate process")
2617
      # 0 and 1 are reserved for the return value
2618
      result = 33
2619

    
2620
    os._exit(result) # pylint: disable-msg=W0212
2621

    
2622
  # Parent process
2623

    
2624
  # Avoid zombies and check exit code
2625
  (_, status) = os.waitpid(pid, 0)
2626

    
2627
  if os.WIFSIGNALED(status):
2628
    exitcode = None
2629
    signum = os.WTERMSIG(status)
2630
  else:
2631
    exitcode = os.WEXITSTATUS(status)
2632
    signum = None
2633

    
2634
  if not (exitcode in (0, 1) and signum is None):
2635
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2636
                              (exitcode, signum))
2637

    
2638
  return bool(exitcode)
2639

    
2640

    
2641
def IgnoreProcessNotFound(fn, *args, **kwargs):
2642
  """Ignores ESRCH when calling a process-related function.
2643

2644
  ESRCH is raised when a process is not found.
2645

2646
  @rtype: bool
2647
  @return: Whether process was found
2648

2649
  """
2650
  try:
2651
    fn(*args, **kwargs)
2652
  except EnvironmentError, err:
2653
    # Ignore ESRCH
2654
    if err.errno == errno.ESRCH:
2655
      return False
2656
    raise
2657

    
2658
  return True
2659

    
2660

    
2661
def IgnoreSignals(fn, *args, **kwargs):
2662
  """Tries to call a function ignoring failures due to EINTR.
2663

2664
  """
2665
  try:
2666
    return fn(*args, **kwargs)
2667
  except EnvironmentError, err:
2668
    if err.errno == errno.EINTR:
2669
      return None
2670
    else:
2671
      raise
2672
  except (select.error, socket.error), err:
2673
    # In python 2.6 and above select.error is an IOError, so it's handled
2674
    # above, in 2.5 and below it's not, and it's handled here.
2675
    if err.args and err.args[0] == errno.EINTR:
2676
      return None
2677
    else:
2678
      raise
2679

    
2680

    
2681
def LockFile(fd):
2682
  """Locks a file using POSIX locks.
2683

2684
  @type fd: int
2685
  @param fd: the file descriptor we need to lock
2686

2687
  """
2688
  try:
2689
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2690
  except IOError, err:
2691
    if err.errno == errno.EAGAIN:
2692
      raise errors.LockError("File already locked")
2693
    raise
2694

    
2695

    
2696
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2697
  """Reads the watcher pause file.
2698

2699
  @type filename: string
2700
  @param filename: Path to watcher pause file
2701
  @type now: None, float or int
2702
  @param now: Current time as Unix timestamp
2703
  @type remove_after: int
2704
  @param remove_after: Remove watcher pause file after specified amount of
2705
    seconds past the pause end time
2706

2707
  """
2708
  if now is None:
2709
    now = time.time()
2710

    
2711
  try:
2712
    value = ReadFile(filename)
2713
  except IOError, err:
2714
    if err.errno != errno.ENOENT:
2715
      raise
2716
    value = None
2717

    
2718
  if value is not None:
2719
    try:
2720
      value = int(value)
2721
    except ValueError:
2722
      logging.warning(("Watcher pause file (%s) contains invalid value,"
2723
                       " removing it"), filename)
2724
      RemoveFile(filename)
2725
      value = None
2726

    
2727
    if value is not None:
2728
      # Remove file if it's outdated
2729
      if now > (value + remove_after):
2730
        RemoveFile(filename)
2731
        value = None
2732

    
2733
      elif now > value:
2734
        value = None
2735

    
2736
  return value
2737

    
2738

    
2739
def GetClosedTempfile(*args, **kwargs):
2740
  """Creates a temporary file and returns its path.
2741

2742
  """
2743
  (fd, path) = tempfile.mkstemp(*args, **kwargs)
2744
  CloseFdNoError(fd)
2745
  return path
2746

    
2747

    
2748
def GenerateSelfSignedX509Cert(common_name, validity):
2749
  """Generates a self-signed X509 certificate.
2750

2751
  @type common_name: string
2752
  @param common_name: commonName value
2753
  @type validity: int
2754
  @param validity: Validity for certificate in seconds
2755

2756
  """
2757
  # Create private and public key
2758
  key = OpenSSL.crypto.PKey()
2759
  key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
2760

    
2761
  # Create self-signed certificate
2762
  cert = OpenSSL.crypto.X509()
2763
  if common_name:
2764
    cert.get_subject().CN = common_name
2765
  cert.set_serial_number(1)
2766
  cert.gmtime_adj_notBefore(0)
2767
  cert.gmtime_adj_notAfter(validity)
2768
  cert.set_issuer(cert.get_subject())
2769
  cert.set_pubkey(key)
2770
  cert.sign(key, constants.X509_CERT_SIGN_DIGEST)
2771

    
2772
  key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
2773
  cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2774

    
2775
  return (key_pem, cert_pem)
2776

    
2777

    
2778
def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
2779
                              validity=constants.X509_CERT_DEFAULT_VALIDITY):
2780
  """Legacy function to generate self-signed X509 certificate.
2781

2782
  @type filename: str
2783
  @param filename: path to write certificate to
2784
  @type common_name: string
2785
  @param common_name: commonName value
2786
  @type validity: int
2787
  @param validity: validity of certificate in number of days
2788

2789
  """
2790
  # TODO: Investigate using the cluster name instead of X505_CERT_CN for
2791
  # common_name, as cluster-renames are very seldom, and it'd be nice if RAPI
2792
  # and node daemon certificates have the proper Subject/Issuer.
2793
  (key_pem, cert_pem) = GenerateSelfSignedX509Cert(common_name,
2794
                                                   validity * 24 * 60 * 60)
2795

    
2796
  WriteFile(filename, mode=0400, data=key_pem + cert_pem)
2797

    
2798

    
2799
class FileLock(object):
2800
  """Utility class for file locks.
2801

2802
  """
2803
  def __init__(self, fd, filename):
2804
    """Constructor for FileLock.
2805

2806
    @type fd: file
2807
    @param fd: File object
2808
    @type filename: str
2809
    @param filename: Path of the file opened at I{fd}
2810

2811
    """
2812
    self.fd = fd
2813
    self.filename = filename
2814

    
2815
  @classmethod
2816
  def Open(cls, filename):
2817
    """Creates and opens a file to be used as a file-based lock.
2818

2819
    @type filename: string
2820
    @param filename: path to the file to be locked
2821

2822
    """
2823
    # Using "os.open" is necessary to allow both opening existing file
2824
    # read/write and creating if not existing. Vanilla "open" will truncate an
2825
    # existing file -or- allow creating if not existing.
2826
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2827
               filename)
2828

    
2829
  def __del__(self):
2830
    self.Close()
2831

    
2832
  def Close(self):
2833
    """Close the file and release the lock.
2834

2835
    """
2836
    if hasattr(self, "fd") and self.fd:
2837
      self.fd.close()
2838
      self.fd = None
2839

    
2840
  def _flock(self, flag, blocking, timeout, errmsg):
2841
    """Wrapper for fcntl.flock.
2842

2843
    @type flag: int
2844
    @param flag: operation flag
2845
    @type blocking: bool
2846
    @param blocking: whether the operation should be done in blocking mode.
2847
    @type timeout: None or float
2848
    @param timeout: for how long the operation should be retried (implies
2849
                    non-blocking mode).
2850
    @type errmsg: string
2851
    @param errmsg: error message in case operation fails.
2852

2853
    """
2854
    assert self.fd, "Lock was closed"
2855
    assert timeout is None or timeout >= 0, \
2856
      "If specified, timeout must be positive"
2857
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2858

    
2859
    # When a timeout is used, LOCK_NB must always be set
2860
    if not (timeout is None and blocking):
2861
      flag |= fcntl.LOCK_NB
2862

    
2863
    if timeout is None:
2864
      self._Lock(self.fd, flag, timeout)
2865
    else:
2866
      try:
2867
        Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2868
              args=(self.fd, flag, timeout))
2869
      except RetryTimeout:
2870
        raise errors.LockError(errmsg)
2871

    
2872
  @staticmethod
2873
  def _Lock(fd, flag, timeout):
2874
    try:
2875
      fcntl.flock(fd, flag)
2876
    except IOError, err:
2877
      if timeout is not None and err.errno == errno.EAGAIN:
2878
        raise RetryAgain()
2879

    
2880
      logging.exception("fcntl.flock failed")
2881
      raise
2882

    
2883
  def Exclusive(self, blocking=False, timeout=None):
2884
    """Locks the file in exclusive mode.
2885

2886
    @type blocking: boolean
2887
    @param blocking: whether to block and wait until we
2888
        can lock the file or return immediately
2889
    @type timeout: int or None
2890
    @param timeout: if not None, the duration to wait for the lock
2891
        (in blocking mode)
2892

2893
    """
2894
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2895
                "Failed to lock %s in exclusive mode" % self.filename)
2896

    
2897
  def Shared(self, blocking=False, timeout=None):
2898
    """Locks the file in shared mode.
2899

2900
    @type blocking: boolean
2901
    @param blocking: whether to block and wait until we
2902
        can lock the file or return immediately
2903
    @type timeout: int or None
2904
    @param timeout: if not None, the duration to wait for the lock
2905
        (in blocking mode)
2906

2907
    """
2908
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2909
                "Failed to lock %s in shared mode" % self.filename)
2910

    
2911
  def Unlock(self, blocking=True, timeout=None):
2912
    """Unlocks the file.
2913

2914
    According to C{flock(2)}, unlocking can also be a nonblocking
2915
    operation::
2916

2917
      To make a non-blocking request, include LOCK_NB with any of the above
2918
      operations.
2919

2920
    @type blocking: boolean
2921
    @param blocking: whether to block and wait until we
2922
        can lock the file or return immediately
2923
    @type timeout: int or None
2924
    @param timeout: if not None, the duration to wait for the lock
2925
        (in blocking mode)
2926

2927
    """
2928
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2929
                "Failed to unlock %s" % self.filename)
2930

    
2931

    
2932
def SignalHandled(signums):
2933
  """Signal Handled decoration.
2934

2935
  This special decorator installs a signal handler and then calls the target
2936
  function. The function must accept a 'signal_handlers' keyword argument,
2937
  which will contain a dict indexed by signal number, with SignalHandler
2938
  objects as values.
2939

2940
  The decorator can be safely stacked with iself, to handle multiple signals
2941
  with different handlers.
2942

2943
  @type signums: list
2944
  @param signums: signals to intercept
2945

2946
  """
2947
  def wrap(fn):
2948
    def sig_function(*args, **kwargs):
2949
      assert 'signal_handlers' not in kwargs or \
2950
             kwargs['signal_handlers'] is None or \
2951
             isinstance(kwargs['signal_handlers'], dict), \
2952
             "Wrong signal_handlers parameter in original function call"
2953
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2954
        signal_handlers = kwargs['signal_handlers']
2955
      else:
2956
        signal_handlers = {}
2957
        kwargs['signal_handlers'] = signal_handlers
2958
      sighandler = SignalHandler(signums)
2959
      try:
2960
        for sig in signums:
2961
          signal_handlers[sig] = sighandler
2962
        return fn(*args, **kwargs)
2963
      finally:
2964
        sighandler.Reset()
2965
    return sig_function
2966
  return wrap
2967

    
2968

    
2969
class SignalWakeupFd(object):
2970
  try:
2971
    # This is only supported in Python 2.5 and above (some distributions
2972
    # backported it to Python 2.4)
2973
    _set_wakeup_fd_fn = signal.set_wakeup_fd
2974
  except AttributeError:
2975
    # Not supported
2976
    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
2977
      return -1
2978
  else:
2979
    def _SetWakeupFd(self, fd):
2980
      return self._set_wakeup_fd_fn(fd)
2981

    
2982
  def __init__(self):
2983
    """Initializes this class.
2984

2985
    """
2986
    (read_fd, write_fd) = os.pipe()
2987

    
2988
    # Once these succeeded, the file descriptors will be closed automatically.
2989
    # Buffer size 0 is important, otherwise .read() with a specified length
2990
    # might buffer data and the file descriptors won't be marked readable.
2991
    self._read_fh = os.fdopen(read_fd, "r", 0)
2992
    self._write_fh = os.fdopen(write_fd, "w", 0)
2993

    
2994
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
2995

    
2996
    # Utility functions
2997
    self.fileno = self._read_fh.fileno
2998
    self.read = self._read_fh.read
2999

    
3000
  def Reset(self):
3001
    """Restores the previous wakeup file descriptor.
3002

3003
    """
3004
    if hasattr(self, "_previous") and self._previous is not None:
3005
      self._SetWakeupFd(self._previous)
3006
      self._previous = None
3007

    
3008
  def Notify(self):
3009
    """Notifies the wakeup file descriptor.
3010

3011
    """
3012
    self._write_fh.write("\0")
3013

    
3014
  def __del__(self):
3015
    """Called before object deletion.
3016

3017
    """
3018
    self.Reset()
3019

    
3020

    
3021
class SignalHandler(object):
3022
  """Generic signal handler class.
3023

3024
  It automatically restores the original handler when deconstructed or
3025
  when L{Reset} is called. You can either pass your own handler
3026
  function in or query the L{called} attribute to detect whether the
3027
  signal was sent.
3028

3029
  @type signum: list
3030
  @ivar signum: the signals we handle
3031
  @type called: boolean
3032
  @ivar called: tracks whether any of the signals have been raised
3033

3034
  """
3035
  def __init__(self, signum, handler_fn=None, wakeup=None):
3036
    """Constructs a new SignalHandler instance.
3037

3038
    @type signum: int or list of ints
3039
    @param signum: Single signal number or set of signal numbers
3040
    @type handler_fn: callable
3041
    @param handler_fn: Signal handling function
3042

3043
    """
3044
    assert handler_fn is None or callable(handler_fn)
3045

    
3046
    self.signum = set(signum)
3047
    self.called = False
3048

    
3049
    self._handler_fn = handler_fn
3050
    self._wakeup = wakeup
3051

    
3052
    self._previous = {}
3053
    try:
3054
      for signum in self.signum:
3055
        # Setup handler
3056
        prev_handler = signal.signal(signum, self._HandleSignal)
3057
        try:
3058
          self._previous[signum] = prev_handler
3059
        except:
3060
          # Restore previous handler
3061
          signal.signal(signum, prev_handler)
3062
          raise
3063
    except:
3064
      # Reset all handlers
3065
      self.Reset()
3066
      # Here we have a race condition: a handler may have already been called,
3067
      # but there's not much we can do about it at this point.
3068
      raise
3069

    
3070
  def __del__(self):
3071
    self.Reset()
3072

    
3073
  def Reset(self):
3074
    """Restore previous handler.
3075

3076
    This will reset all the signals to their previous handlers.
3077

3078
    """
3079
    for signum, prev_handler in self._previous.items():
3080
      signal.signal(signum, prev_handler)
3081
      # If successful, remove from dict
3082
      del self._previous[signum]
3083

    
3084
  def Clear(self):
3085
    """Unsets the L{called} flag.
3086

3087
    This function can be used in case a signal may arrive several times.
3088

3089
    """
3090
    self.called = False
3091

    
3092
  def _HandleSignal(self, signum, frame):
3093
    """Actual signal handling function.
3094

3095
    """
3096
    # This is not nice and not absolutely atomic, but it appears to be the only
3097
    # solution in Python -- there are no atomic types.
3098
    self.called = True
3099

    
3100
    if self._wakeup:
3101
      # Notify whoever is interested in signals
3102
      self._wakeup.Notify()
3103

    
3104
    if self._handler_fn:
3105
      self._handler_fn(signum, frame)
3106

    
3107

    
3108
class FieldSet(object):
3109
  """A simple field set.
3110

3111
  Among the features are:
3112
    - checking if a string is among a list of static string or regex objects
3113
    - checking if a whole list of string matches
3114
    - returning the matching groups from a regex match
3115

3116
  Internally, all fields are held as regular expression objects.
3117

3118
  """
3119
  def __init__(self, *items):
3120
    self.items = [re.compile("^%s$" % value) for value in items]
3121

    
3122
  def Extend(self, other_set):
3123
    """Extend the field set with the items from another one"""
3124
    self.items.extend(other_set.items)
3125

    
3126
  def Matches(self, field):
3127
    """Checks if a field matches the current set
3128

3129
    @type field: str
3130
    @param field: the string to match
3131
    @return: either None or a regular expression match object
3132

3133
    """
3134
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
3135
      return m
3136
    return None
3137

    
3138
  def NonMatching(self, items):
3139
    """Returns the list of fields not matching the current set
3140

3141
    @type items: list
3142
    @param items: the list of fields to check
3143
    @rtype: list
3144
    @return: list of non-matching fields
3145

3146
    """
3147
    return [val for val in items if not self.Matches(val)]
3148

    
3149

    
3150
class RunningTimeout(object):
3151
  """Class to calculate remaining timeout when doing several operations.
3152

3153
  """
3154
  __slots__ = [
3155
    "_allow_negative",
3156
    "_start_time",
3157
    "_time_fn",
3158
    "_timeout",
3159
    ]
3160

    
3161
  def __init__(self, timeout, allow_negative, _time_fn=time.time):
3162
    """Initializes this class.
3163

3164
    @type timeout: float
3165
    @param timeout: Timeout duration
3166
    @type allow_negative: bool
3167
    @param allow_negative: Whether to return values below zero
3168
    @param _time_fn: Time function for unittests
3169

3170
    """
3171
    object.__init__(self)
3172

    
3173
    if timeout is not None and timeout < 0.0:
3174
      raise ValueError("Timeout must not be negative")
3175

    
3176
    self._timeout = timeout
3177
    self._allow_negative = allow_negative
3178
    self._time_fn = _time_fn
3179

    
3180
    self._start_time = None
3181

    
3182
  def Remaining(self):
3183
    """Returns the remaining timeout.
3184

3185
    """
3186
    if self._timeout is None:
3187
      return None
3188

    
3189
    # Get start time on first calculation
3190
    if self._start_time is None:
3191
      self._start_time = self._time_fn()
3192

    
3193
    # Calculate remaining time
3194
    remaining_timeout = self._start_time + self._timeout - self._time_fn()
3195

    
3196
    if not self._allow_negative:
3197
      # Ensure timeout is always >= 0
3198
      return max(0.0, remaining_timeout)
3199

    
3200
    return remaining_timeout