Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 557838c1

History | View | Annotate | Download (106.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010 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 logging.handlers
46
import signal
47
import OpenSSL
48
import datetime
49
import calendar
50
import hmac
51
import collections
52

    
53
from cStringIO import StringIO
54

    
55
try:
56
  # pylint: disable-msg=F0401
57
  import ctypes
58
except ImportError:
59
  ctypes = None
60

    
61
from ganeti import errors
62
from ganeti import constants
63
from ganeti import compat
64

    
65

    
66
_locksheld = []
67
_re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
68

    
69
debug_locks = False
70

    
71
#: when set to True, L{RunCmd} is disabled
72
no_fork = False
73

    
74
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
75

    
76
HEX_CHAR_RE = r"[a-zA-Z0-9]"
77
VALID_X509_SIGNATURE_SALT = re.compile("^%s+$" % HEX_CHAR_RE, re.S)
78
X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
79
                            (re.escape(constants.X509_CERT_SIGNATURE_HEADER),
80
                             HEX_CHAR_RE, HEX_CHAR_RE),
81
                            re.S | re.I)
82

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

    
85
UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
86
                     '[a-f0-9]{4}-[a-f0-9]{12}$')
87

    
88
# Certificate verification results
89
(CERT_WARNING,
90
 CERT_ERROR) = range(1, 3)
91

    
92
# Flags for mlockall() (from bits/mman.h)
93
_MCL_CURRENT = 1
94
_MCL_FUTURE = 2
95

    
96
#: MAC checker regexp
97
_MAC_CHECK = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I)
98

    
99

    
100
class RunResult(object):
101
  """Holds the result of running external programs.
102

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

118
  """
119
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
120
               "failed", "fail_reason", "cmd"]
121

    
122

    
123
  def __init__(self, exit_code, signal_, stdout, stderr, cmd):
124
    self.cmd = cmd
125
    self.exit_code = exit_code
126
    self.signal = signal_
127
    self.stdout = stdout
128
    self.stderr = stderr
129
    self.failed = (signal_ is not None or exit_code != 0)
130

    
131
    if self.signal is not None:
132
      self.fail_reason = "terminated by signal %s" % self.signal
133
    elif self.exit_code is not None:
134
      self.fail_reason = "exited with exit code %s" % self.exit_code
135
    else:
136
      self.fail_reason = "unable to determine termination reason"
137

    
138
    if self.failed:
139
      logging.debug("Command '%s' failed (%s); output: %s",
140
                    self.cmd, self.fail_reason, self.output)
141

    
142
  def _GetOutput(self):
143
    """Returns the combined stdout and stderr for easier usage.
144

145
    """
146
    return self.stdout + self.stderr
147

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

    
150

    
151
def _BuildCmdEnvironment(env, reset):
152
  """Builds the environment for an external program.
153

154
  """
155
  if reset:
156
    cmd_env = {}
157
  else:
158
    cmd_env = os.environ.copy()
159
    cmd_env["LC_ALL"] = "C"
160

    
161
  if env is not None:
162
    cmd_env.update(env)
163

    
164
  return cmd_env
165

    
166

    
167
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
168
           interactive=False):
169
  """Execute a (shell) command.
170

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

174
  @type cmd: string or list
175
  @param cmd: Command to run
176
  @type env: dict
177
  @param env: Additional environment variables
178
  @type output: str
179
  @param output: if desired, the output of the command can be
180
      saved in a file instead of the RunResult instance; this
181
      parameter denotes the file name (if not None)
182
  @type cwd: string
183
  @param cwd: if specified, will be used as the working
184
      directory for the command; the default will be /
185
  @type reset_env: boolean
186
  @param reset_env: whether to reset or keep the default os environment
187
  @type interactive: boolean
188
  @param interactive: weather we pipe stdin, stdout and stderr
189
                      (default behaviour) or run the command interactive
190
  @rtype: L{RunResult}
191
  @return: RunResult instance
192
  @raise errors.ProgrammerError: if we call this when forks are disabled
193

194
  """
195
  if no_fork:
196
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
197

    
198
  if output and interactive:
199
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
200
                                 " not be provided at the same time")
201

    
202
  if isinstance(cmd, basestring):
203
    strcmd = cmd
204
    shell = True
205
  else:
206
    cmd = [str(val) for val in cmd]
207
    strcmd = ShellQuoteArgs(cmd)
208
    shell = False
209

    
210
  if output:
211
    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
212
  else:
213
    logging.debug("RunCmd %s", strcmd)
214

    
215
  cmd_env = _BuildCmdEnvironment(env, reset_env)
216

    
217
  try:
218
    if output is None:
219
      out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd, interactive)
220
    else:
221
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
222
      out = err = ""
223
  except OSError, err:
224
    if err.errno == errno.ENOENT:
225
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
226
                               (strcmd, err))
227
    else:
228
      raise
229

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

    
237
  return RunResult(exitcode, signal_, out, err, strcmd)
238

    
239

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

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

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

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

    
254

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

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

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

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

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

    
281
  # Redirect standard I/O
282
  os.dup2(devnull_fd, 0)
283
  os.dup2(output_fd, 1)
284
  os.dup2(output_fd, 2)
285

    
286

    
287
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
288
                pidfile=None):
289
  """Start a daemon process after forking twice.
290

291
  @type cmd: string or list
292
  @param cmd: Command to run
293
  @type env: dict
294
  @param env: Additional environment variables
295
  @type cwd: string
296
  @param cwd: Working directory for the program
297
  @type output: string
298
  @param output: Path to file in which to save the output
299
  @type output_fd: int
300
  @param output_fd: File descriptor for output
301
  @type pidfile: string
302
  @param pidfile: Process ID file
303
  @rtype: int
304
  @return: Daemon process ID
305
  @raise errors.ProgrammerError: if we call this when forks are disabled
306

307
  """
308
  if no_fork:
309
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
310
                                 " disabled")
311

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

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

    
319
  strcmd = ShellQuoteArgs(cmd)
320

    
321
  if output:
322
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
323
  else:
324
    logging.debug("StartDaemon %s", strcmd)
325

    
326
  cmd_env = _BuildCmdEnvironment(env, False)
327

    
328
  # Create pipe for sending PID back
329
  (pidpipe_read, pidpipe_write) = os.pipe()
330
  try:
331
    try:
332
      # Create pipe for sending error messages
333
      (errpipe_read, errpipe_write) = os.pipe()
334
      try:
335
        try:
336
          # First fork
337
          pid = os.fork()
338
          if pid == 0:
339
            try:
340
              # Child process, won't return
341
              _StartDaemonChild(errpipe_read, errpipe_write,
342
                                pidpipe_read, pidpipe_write,
343
                                cmd, cmd_env, cwd,
344
                                output, output_fd, pidfile)
345
            finally:
346
              # Well, maybe child process failed
347
              os._exit(1) # pylint: disable-msg=W0212
348
        finally:
349
          _CloseFDNoErr(errpipe_write)
350

    
351
        # Wait for daemon to be started (or an error message to
352
        # arrive) and read up to 100 KB as an error message
353
        errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
354
      finally:
355
        _CloseFDNoErr(errpipe_read)
356
    finally:
357
      _CloseFDNoErr(pidpipe_write)
358

    
359
    # Read up to 128 bytes for PID
360
    pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
361
  finally:
362
    _CloseFDNoErr(pidpipe_read)
363

    
364
  # Try to avoid zombies by waiting for child process
365
  try:
366
    os.waitpid(pid, 0)
367
  except OSError:
368
    pass
369

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

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

    
380

    
381
def _StartDaemonChild(errpipe_read, errpipe_write,
382
                      pidpipe_read, pidpipe_write,
383
                      args, env, cwd,
384
                      output, fd_output, pidfile):
385
  """Child process for starting daemon.
386

387
  """
388
  try:
389
    # Close parent's side
390
    _CloseFDNoErr(errpipe_read)
391
    _CloseFDNoErr(pidpipe_read)
392

    
393
    # First child process
394
    SetupDaemonEnv()
395

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

    
402
    # Make sure pipe is closed on execv* (and thereby notifies
403
    # original process)
404
    SetCloseOnExecFlag(errpipe_write, True)
405

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

    
409
    # Open PID file
410
    if pidfile:
411
      fd_pidfile = WritePidFile(pidfile)
412

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

    
416
      SetCloseOnExecFlag(fd_pidfile, False)
417
    else:
418
      fd_pidfile = None
419

    
420
    SetupDaemonFDs(output, fd_output)
421

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

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

    
428
    # Change working directory
429
    os.chdir(cwd)
430

    
431
    if env is None:
432
      os.execvp(args[0], args)
433
    else:
434
      os.execvpe(args[0], args, env)
435
  except: # pylint: disable-msg=W0702
436
    try:
437
      # Report errors to original process
438
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
439
    except: # pylint: disable-msg=W0702
440
      # Ignore errors in error handling
441
      pass
442

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

    
445

    
446
def WriteErrorToFD(fd, err):
447
  """Possibly write an error message to a fd.
448

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

453
  """
454
  if fd is None:
455
    return
456

    
457
  if not err:
458
    err = "<unknown error>"
459

    
460
  RetryOnSignal(os.write, fd, err)
461

    
462

    
463
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive):
464
  """Run a command and return its output.
465

466
  @type  cmd: string or list
467
  @param cmd: Command to run
468
  @type env: dict
469
  @param env: The environment to use
470
  @type via_shell: bool
471
  @param via_shell: if we should run via the shell
472
  @type cwd: string
473
  @param cwd: the working directory for the program
474
  @type interactive: boolean
475
  @param interactive: Run command interactive (without piping)
476
  @rtype: tuple
477
  @return: (out, err, status)
478

479
  """
480
  poller = select.poll()
481

    
482
  stderr = subprocess.PIPE
483
  stdout = subprocess.PIPE
484
  stdin = subprocess.PIPE
485

    
486
  if interactive:
487
    stderr = stdout = stdin = None
488

    
489
  child = subprocess.Popen(cmd, shell=via_shell,
490
                           stderr=stderr,
491
                           stdout=stdout,
492
                           stdin=stdin,
493
                           close_fds=True, env=env,
494
                           cwd=cwd)
495

    
496
  out = StringIO()
497
  err = StringIO()
498
  if not interactive:
499
    child.stdin.close()
500
    poller.register(child.stdout, select.POLLIN)
501
    poller.register(child.stderr, select.POLLIN)
502
    fdmap = {
503
      child.stdout.fileno(): (out, child.stdout),
504
      child.stderr.fileno(): (err, child.stderr),
505
      }
506
    for fd in fdmap:
507
      SetNonblockFlag(fd, True)
508

    
509
    while fdmap:
510
      pollresult = RetryOnSignal(poller.poll)
511

    
512
      for fd, event in pollresult:
513
        if event & select.POLLIN or event & select.POLLPRI:
514
          data = fdmap[fd][1].read()
515
          # no data from read signifies EOF (the same as POLLHUP)
516
          if not data:
517
            poller.unregister(fd)
518
            del fdmap[fd]
519
            continue
520
          fdmap[fd][0].write(data)
521
        if (event & select.POLLNVAL or event & select.POLLHUP or
522
            event & select.POLLERR):
523
          poller.unregister(fd)
524
          del fdmap[fd]
525

    
526
  out = out.getvalue()
527
  err = err.getvalue()
528

    
529
  status = child.wait()
530
  return out, err, status
531

    
532

    
533
def _RunCmdFile(cmd, env, via_shell, output, cwd):
534
  """Run a command and save its output to a file.
535

536
  @type  cmd: string or list
537
  @param cmd: Command to run
538
  @type env: dict
539
  @param env: The environment to use
540
  @type via_shell: bool
541
  @param via_shell: if we should run via the shell
542
  @type output: str
543
  @param output: the filename in which to save the output
544
  @type cwd: string
545
  @param cwd: the working directory for the program
546
  @rtype: int
547
  @return: the exit status
548

549
  """
550
  fh = open(output, "a")
551
  try:
552
    child = subprocess.Popen(cmd, shell=via_shell,
553
                             stderr=subprocess.STDOUT,
554
                             stdout=fh,
555
                             stdin=subprocess.PIPE,
556
                             close_fds=True, env=env,
557
                             cwd=cwd)
558

    
559
    child.stdin.close()
560
    status = child.wait()
561
  finally:
562
    fh.close()
563
  return status
564

    
565

    
566
def SetCloseOnExecFlag(fd, enable):
567
  """Sets or unsets the close-on-exec flag on a file descriptor.
568

569
  @type fd: int
570
  @param fd: File descriptor
571
  @type enable: bool
572
  @param enable: Whether to set or unset it.
573

574
  """
575
  flags = fcntl.fcntl(fd, fcntl.F_GETFD)
576

    
577
  if enable:
578
    flags |= fcntl.FD_CLOEXEC
579
  else:
580
    flags &= ~fcntl.FD_CLOEXEC
581

    
582
  fcntl.fcntl(fd, fcntl.F_SETFD, flags)
583

    
584

    
585
def SetNonblockFlag(fd, enable):
586
  """Sets or unsets the O_NONBLOCK flag on on a file descriptor.
587

588
  @type fd: int
589
  @param fd: File descriptor
590
  @type enable: bool
591
  @param enable: Whether to set or unset it
592

593
  """
594
  flags = fcntl.fcntl(fd, fcntl.F_GETFL)
595

    
596
  if enable:
597
    flags |= os.O_NONBLOCK
598
  else:
599
    flags &= ~os.O_NONBLOCK
600

    
601
  fcntl.fcntl(fd, fcntl.F_SETFL, flags)
602

    
603

    
604
def RetryOnSignal(fn, *args, **kwargs):
605
  """Calls a function again if it failed due to EINTR.
606

607
  """
608
  while True:
609
    try:
610
      return fn(*args, **kwargs)
611
    except EnvironmentError, err:
612
      if err.errno != errno.EINTR:
613
        raise
614
    except (socket.error, select.error), err:
615
      # In python 2.6 and above select.error is an IOError, so it's handled
616
      # above, in 2.5 and below it's not, and it's handled here.
617
      if not (err.args and err.args[0] == errno.EINTR):
618
        raise
619

    
620

    
621
def RunParts(dir_name, env=None, reset_env=False):
622
  """Run Scripts or programs in a directory
623

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

633
  """
634
  rr = []
635

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

    
642
  for relname in sorted(dir_contents):
643
    fname = PathJoin(dir_name, relname)
644
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
645
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
646
      rr.append((relname, constants.RUNPARTS_SKIP, None))
647
    else:
648
      try:
649
        result = RunCmd([fname], env=env, reset_env=reset_env)
650
      except Exception, err: # pylint: disable-msg=W0703
651
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
652
      else:
653
        rr.append((relname, constants.RUNPARTS_RUN, result))
654

    
655
  return rr
656

    
657

    
658
def RemoveFile(filename):
659
  """Remove a file ignoring some errors.
660

661
  Remove a file, ignoring non-existing ones or directories. Other
662
  errors are passed.
663

664
  @type filename: str
665
  @param filename: the file to be removed
666

667
  """
668
  try:
669
    os.unlink(filename)
670
  except OSError, err:
671
    if err.errno not in (errno.ENOENT, errno.EISDIR):
672
      raise
673

    
674

    
675
def RemoveDir(dirname):
676
  """Remove an empty directory.
677

678
  Remove a directory, ignoring non-existing ones.
679
  Other errors are passed. This includes the case,
680
  where the directory is not empty, so it can't be removed.
681

682
  @type dirname: str
683
  @param dirname: the empty directory to be removed
684

685
  """
686
  try:
687
    os.rmdir(dirname)
688
  except OSError, err:
689
    if err.errno != errno.ENOENT:
690
      raise
691

    
692

    
693
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
694
  """Renames a file.
695

696
  @type old: string
697
  @param old: Original path
698
  @type new: string
699
  @param new: New path
700
  @type mkdir: bool
701
  @param mkdir: Whether to create target directory if it doesn't exist
702
  @type mkdir_mode: int
703
  @param mkdir_mode: Mode for newly created directories
704

705
  """
706
  try:
707
    return os.rename(old, new)
708
  except OSError, err:
709
    # In at least one use case of this function, the job queue, directory
710
    # creation is very rare. Checking for the directory before renaming is not
711
    # as efficient.
712
    if mkdir and err.errno == errno.ENOENT:
713
      # Create directory and try again
714
      Makedirs(os.path.dirname(new), mode=mkdir_mode)
715

    
716
      return os.rename(old, new)
717

    
718
    raise
719

    
720

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

724
  This is a wrapper around C{os.makedirs} adding error handling not implemented
725
  before Python 2.5.
726

727
  """
728
  try:
729
    os.makedirs(path, mode)
730
  except OSError, err:
731
    # Ignore EEXIST. This is only handled in os.makedirs as included in
732
    # Python 2.5 and above.
733
    if err.errno != errno.EEXIST or not os.path.exists(path):
734
      raise
735

    
736

    
737
def ResetTempfileModule():
738
  """Resets the random name generator of the tempfile module.
739

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

746
  """
747
  # pylint: disable-msg=W0212
748
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
749
    tempfile._once_lock.acquire()
750
    try:
751
      # Reset random name generator
752
      tempfile._name_sequence = None
753
    finally:
754
      tempfile._once_lock.release()
755
  else:
756
    logging.critical("The tempfile module misses at least one of the"
757
                     " '_once_lock' and '_name_sequence' attributes")
758

    
759

    
760
def _FingerprintFile(filename):
761
  """Compute the fingerprint of a file.
762

763
  If the file does not exist, a None will be returned
764
  instead.
765

766
  @type filename: str
767
  @param filename: the filename to checksum
768
  @rtype: str
769
  @return: the hex digest of the sha checksum of the contents
770
      of the file
771

772
  """
773
  if not (os.path.exists(filename) and os.path.isfile(filename)):
774
    return None
775

    
776
  f = open(filename)
777

    
778
  fp = compat.sha1_hash()
779
  while True:
780
    data = f.read(4096)
781
    if not data:
782
      break
783

    
784
    fp.update(data)
785

    
786
  return fp.hexdigest()
787

    
788

    
789
def FingerprintFiles(files):
790
  """Compute fingerprints for a list of files.
791

792
  @type files: list
793
  @param files: the list of filename to fingerprint
794
  @rtype: dict
795
  @return: a dictionary filename: fingerprint, holding only
796
      existing files
797

798
  """
799
  ret = {}
800

    
801
  for filename in files:
802
    cksum = _FingerprintFile(filename)
803
    if cksum:
804
      ret[filename] = cksum
805

    
806
  return ret
807

    
808

    
809
def ForceDictType(target, key_types, allowed_values=None):
810
  """Force the values of a dict to have certain types.
811

812
  @type target: dict
813
  @param target: the dict to update
814
  @type key_types: dict
815
  @param key_types: dict mapping target dict keys to types
816
                    in constants.ENFORCEABLE_TYPES
817
  @type allowed_values: list
818
  @keyword allowed_values: list of specially allowed values
819

820
  """
821
  if allowed_values is None:
822
    allowed_values = []
823

    
824
  if not isinstance(target, dict):
825
    msg = "Expected dictionary, got '%s'" % target
826
    raise errors.TypeEnforcementError(msg)
827

    
828
  for key in target:
829
    if key not in key_types:
830
      msg = "Unknown key '%s'" % key
831
      raise errors.TypeEnforcementError(msg)
832

    
833
    if target[key] in allowed_values:
834
      continue
835

    
836
    ktype = key_types[key]
837
    if ktype not in constants.ENFORCEABLE_TYPES:
838
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
839
      raise errors.ProgrammerError(msg)
840

    
841
    if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
842
      if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
843
        pass
844
      elif not isinstance(target[key], basestring):
845
        if isinstance(target[key], bool) and not target[key]:
846
          target[key] = ''
847
        else:
848
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
849
          raise errors.TypeEnforcementError(msg)
850
    elif ktype == constants.VTYPE_BOOL:
851
      if isinstance(target[key], basestring) and target[key]:
852
        if target[key].lower() == constants.VALUE_FALSE:
853
          target[key] = False
854
        elif target[key].lower() == constants.VALUE_TRUE:
855
          target[key] = True
856
        else:
857
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
858
          raise errors.TypeEnforcementError(msg)
859
      elif target[key]:
860
        target[key] = True
861
      else:
862
        target[key] = False
863
    elif ktype == constants.VTYPE_SIZE:
864
      try:
865
        target[key] = ParseUnit(target[key])
866
      except errors.UnitParseError, err:
867
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
868
              (key, target[key], err)
869
        raise errors.TypeEnforcementError(msg)
870
    elif ktype == constants.VTYPE_INT:
871
      try:
872
        target[key] = int(target[key])
873
      except (ValueError, TypeError):
874
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
875
        raise errors.TypeEnforcementError(msg)
876

    
877

    
878
def _GetProcStatusPath(pid):
879
  """Returns the path for a PID's proc status file.
880

881
  @type pid: int
882
  @param pid: Process ID
883
  @rtype: string
884

885
  """
886
  return "/proc/%d/status" % pid
887

    
888

    
889
def IsProcessAlive(pid):
890
  """Check if a given pid exists on the system.
891

892
  @note: zombie status is not handled, so zombie processes
893
      will be returned as alive
894
  @type pid: int
895
  @param pid: the process ID to check
896
  @rtype: boolean
897
  @return: True if the process exists
898

899
  """
900
  def _TryStat(name):
901
    try:
902
      os.stat(name)
903
      return True
904
    except EnvironmentError, err:
905
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
906
        return False
907
      elif err.errno == errno.EINVAL:
908
        raise RetryAgain(err)
909
      raise
910

    
911
  assert isinstance(pid, int), "pid must be an integer"
912
  if pid <= 0:
913
    return False
914

    
915
  # /proc in a multiprocessor environment can have strange behaviors.
916
  # Retry the os.stat a few times until we get a good result.
917
  try:
918
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
919
                 args=[_GetProcStatusPath(pid)])
920
  except RetryTimeout, err:
921
    err.RaiseInner()
922

    
923

    
924
def _ParseSigsetT(sigset):
925
  """Parse a rendered sigset_t value.
926

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

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

935
  """
936
  result = set()
937

    
938
  signum = 0
939
  for ch in reversed(sigset):
940
    chv = int(ch, 16)
941

    
942
    # The following could be done in a loop, but it's easier to read and
943
    # understand in the unrolled form
944
    if chv & 1:
945
      result.add(signum + 1)
946
    if chv & 2:
947
      result.add(signum + 2)
948
    if chv & 4:
949
      result.add(signum + 3)
950
    if chv & 8:
951
      result.add(signum + 4)
952

    
953
    signum += 4
954

    
955
  return result
956

    
957

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

961
  @type pstatus: string
962
  @param pstatus: Contents of /proc/$pid/status
963
  @type field: string
964
  @param field: Name of field whose value should be returned
965
  @rtype: string
966

967
  """
968
  for line in pstatus.splitlines():
969
    parts = line.split(":", 1)
970

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

    
974
    return parts[1].strip()
975

    
976
  return None
977

    
978

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

982
  @type pid: int
983
  @param pid: Process ID
984
  @type signum: int
985
  @param signum: Signal number
986
  @rtype: bool
987

988
  """
989
  if status_path is None:
990
    status_path = _GetProcStatusPath(pid)
991

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

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

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

    
1007

    
1008
def ReadPidFile(pidfile):
1009
  """Read a pid from a file.
1010

1011
  @type  pidfile: string
1012
  @param pidfile: path to the file containing the pid
1013
  @rtype: int
1014
  @return: The process id, if the file exists and contains a valid PID,
1015
           otherwise 0
1016

1017
  """
1018
  try:
1019
    raw_data = ReadOneLineFile(pidfile)
1020
  except EnvironmentError, err:
1021
    if err.errno != errno.ENOENT:
1022
      logging.exception("Can't read pid file")
1023
    return 0
1024

    
1025
  try:
1026
    pid = int(raw_data)
1027
  except (TypeError, ValueError), err:
1028
    logging.info("Can't parse pid file contents", exc_info=True)
1029
    return 0
1030

    
1031
  return pid
1032

    
1033

    
1034
def ReadLockedPidFile(path):
1035
  """Reads a locked PID file.
1036

1037
  This can be used together with L{StartDaemon}.
1038

1039
  @type path: string
1040
  @param path: Path to PID file
1041
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
1042

1043
  """
1044
  try:
1045
    fd = os.open(path, os.O_RDONLY)
1046
  except EnvironmentError, err:
1047
    if err.errno == errno.ENOENT:
1048
      # PID file doesn't exist
1049
      return None
1050
    raise
1051

    
1052
  try:
1053
    try:
1054
      # Try to acquire lock
1055
      LockFile(fd)
1056
    except errors.LockError:
1057
      # Couldn't lock, daemon is running
1058
      return int(os.read(fd, 100))
1059
  finally:
1060
    os.close(fd)
1061

    
1062
  return None
1063

    
1064

    
1065
def MatchNameComponent(key, name_list, case_sensitive=True):
1066
  """Try to match a name against a list.
1067

1068
  This function will try to match a name like test1 against a list
1069
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
1070
  this list, I{'test1'} as well as I{'test1.example'} will match, but
1071
  not I{'test1.ex'}. A multiple match will be considered as no match
1072
  at all (e.g. I{'test1'} against C{['test1.example.com',
1073
  'test1.example.org']}), except when the key fully matches an entry
1074
  (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
1075

1076
  @type key: str
1077
  @param key: the name to be searched
1078
  @type name_list: list
1079
  @param name_list: the list of strings against which to search the key
1080
  @type case_sensitive: boolean
1081
  @param case_sensitive: whether to provide a case-sensitive match
1082

1083
  @rtype: None or str
1084
  @return: None if there is no match I{or} if there are multiple matches,
1085
      otherwise the element from the list which matches
1086

1087
  """
1088
  if key in name_list:
1089
    return key
1090

    
1091
  re_flags = 0
1092
  if not case_sensitive:
1093
    re_flags |= re.IGNORECASE
1094
    key = key.upper()
1095
  mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
1096
  names_filtered = []
1097
  string_matches = []
1098
  for name in name_list:
1099
    if mo.match(name) is not None:
1100
      names_filtered.append(name)
1101
      if not case_sensitive and key == name.upper():
1102
        string_matches.append(name)
1103

    
1104
  if len(string_matches) == 1:
1105
    return string_matches[0]
1106
  if len(names_filtered) == 1:
1107
    return names_filtered[0]
1108
  return None
1109

    
1110

    
1111
def ValidateServiceName(name):
1112
  """Validate the given service name.
1113

1114
  @type name: number or string
1115
  @param name: Service name or port specification
1116

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

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

    
1132
  return name
1133

    
1134

    
1135
def ListVolumeGroups():
1136
  """List volume groups and their size
1137

1138
  @rtype: dict
1139
  @return:
1140
       Dictionary with keys volume name and values
1141
       the size of the volume
1142

1143
  """
1144
  command = "vgs --noheadings --units m --nosuffix -o name,size"
1145
  result = RunCmd(command)
1146
  retval = {}
1147
  if result.failed:
1148
    return retval
1149

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

    
1158
    retval[name] = size
1159

    
1160
  return retval
1161

    
1162

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

1166
  @type bridge: str
1167
  @param bridge: the bridge name to check
1168
  @rtype: boolean
1169
  @return: True if it does
1170

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

    
1174

    
1175
def NiceSort(name_list):
1176
  """Sort a list of strings based on digit and non-digit groupings.
1177

1178
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
1179
  will sort the list in the logical order C{['a1', 'a2', 'a10',
1180
  'a11']}.
1181

1182
  The sort algorithm breaks each name in groups of either only-digits
1183
  or no-digits. Only the first eight such groups are considered, and
1184
  after that we just use what's left of the string.
1185

1186
  @type name_list: list
1187
  @param name_list: the names to be sorted
1188
  @rtype: list
1189
  @return: a copy of the name list sorted with our algorithm
1190

1191
  """
1192
  _SORTER_BASE = "(\D+|\d+)"
1193
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
1194
                                                  _SORTER_BASE, _SORTER_BASE,
1195
                                                  _SORTER_BASE, _SORTER_BASE,
1196
                                                  _SORTER_BASE, _SORTER_BASE)
1197
  _SORTER_RE = re.compile(_SORTER_FULL)
1198
  _SORTER_NODIGIT = re.compile("^\D*$")
1199
  def _TryInt(val):
1200
    """Attempts to convert a variable to integer."""
1201
    if val is None or _SORTER_NODIGIT.match(val):
1202
      return val
1203
    rval = int(val)
1204
    return rval
1205

    
1206
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
1207
             for name in name_list]
1208
  to_sort.sort()
1209
  return [tup[1] for tup in to_sort]
1210

    
1211

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

1215
  This function tries to apply function I{fn} to I{val}. If no
1216
  C{ValueError} or C{TypeError} exceptions are raised, it will return
1217
  the result, else it will return the original value. Any other
1218
  exceptions are propagated to the caller.
1219

1220
  @type fn: callable
1221
  @param fn: function to apply to the value
1222
  @param val: the value to be converted
1223
  @return: The converted value if the conversion was successful,
1224
      otherwise the original value.
1225

1226
  """
1227
  try:
1228
    nv = fn(val)
1229
  except (ValueError, TypeError):
1230
    nv = val
1231
  return nv
1232

    
1233

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

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

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

1244
  @type word: str
1245
  @param word: the word to check
1246
  @rtype: boolean
1247
  @return: True if the word is 'safe'
1248

1249
  """
1250
  return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
1251

    
1252

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

1256
  This function will check all arguments in the args list so that they
1257
  are valid shell parameters (i.e. they don't contain shell
1258
  metacharacters). If everything is ok, it will return the result of
1259
  template % args.
1260

1261
  @type template: str
1262
  @param template: the string holding the template for the
1263
      string formatting
1264
  @rtype: str
1265
  @return: the expanded command line
1266

1267
  """
1268
  for word in args:
1269
    if not IsValidShellParam(word):
1270
      raise errors.ProgrammerError("Shell argument '%s' contains"
1271
                                   " invalid characters" % word)
1272
  return template % args
1273

    
1274

    
1275
def FormatUnit(value, units):
1276
  """Formats an incoming number of MiB with the appropriate unit.
1277

1278
  @type value: int
1279
  @param value: integer representing the value in MiB (1048576)
1280
  @type units: char
1281
  @param units: the type of formatting we should do:
1282
      - 'h' for automatic scaling
1283
      - 'm' for MiBs
1284
      - 'g' for GiBs
1285
      - 't' for TiBs
1286
  @rtype: str
1287
  @return: the formatted value (with suffix)
1288

1289
  """
1290
  if units not in ('m', 'g', 't', 'h'):
1291
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
1292

    
1293
  suffix = ''
1294

    
1295
  if units == 'm' or (units == 'h' and value < 1024):
1296
    if units == 'h':
1297
      suffix = 'M'
1298
    return "%d%s" % (round(value, 0), suffix)
1299

    
1300
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
1301
    if units == 'h':
1302
      suffix = 'G'
1303
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
1304

    
1305
  else:
1306
    if units == 'h':
1307
      suffix = 'T'
1308
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
1309

    
1310

    
1311
def ParseUnit(input_string):
1312
  """Tries to extract number and scale from the given string.
1313

1314
  Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
1315
  [UNIT]}. If no unit is specified, it defaults to MiB. Return value
1316
  is always an int in MiB.
1317

1318
  """
1319
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
1320
  if not m:
1321
    raise errors.UnitParseError("Invalid format")
1322

    
1323
  value = float(m.groups()[0])
1324

    
1325
  unit = m.groups()[1]
1326
  if unit:
1327
    lcunit = unit.lower()
1328
  else:
1329
    lcunit = 'm'
1330

    
1331
  if lcunit in ('m', 'mb', 'mib'):
1332
    # Value already in MiB
1333
    pass
1334

    
1335
  elif lcunit in ('g', 'gb', 'gib'):
1336
    value *= 1024
1337

    
1338
  elif lcunit in ('t', 'tb', 'tib'):
1339
    value *= 1024 * 1024
1340

    
1341
  else:
1342
    raise errors.UnitParseError("Unknown unit: %s" % unit)
1343

    
1344
  # Make sure we round up
1345
  if int(value) < value:
1346
    value += 1
1347

    
1348
  # Round up to the next multiple of 4
1349
  value = int(value)
1350
  if value % 4:
1351
    value += 4 - value % 4
1352

    
1353
  return value
1354

    
1355

    
1356
def ParseCpuMask(cpu_mask):
1357
  """Parse a CPU mask definition and return the list of CPU IDs.
1358

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

1363
  @type cpu_mask: str
1364
  @param cpu_mask: CPU mask definition
1365
  @rtype: list of int
1366
  @return: list of CPU IDs
1367

1368
  """
1369
  if not cpu_mask:
1370
    return []
1371
  cpu_list = []
1372
  for range_def in cpu_mask.split(","):
1373
    boundaries = range_def.split("-")
1374
    n_elements = len(boundaries)
1375
    if n_elements > 2:
1376
      raise errors.ParseError("Invalid CPU ID range definition"
1377
                              " (only one hyphen allowed): %s" % range_def)
1378
    try:
1379
      lower = int(boundaries[0])
1380
    except (ValueError, TypeError), err:
1381
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
1382
                              " CPU ID range: %s" % str(err))
1383
    try:
1384
      higher = int(boundaries[-1])
1385
    except (ValueError, TypeError), err:
1386
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
1387
                              " CPU ID range: %s" % str(err))
1388
    if lower > higher:
1389
      raise errors.ParseError("Invalid CPU ID range definition"
1390
                              " (%d > %d): %s" % (lower, higher, range_def))
1391
    cpu_list.extend(range(lower, higher + 1))
1392
  return cpu_list
1393

    
1394

    
1395
def AddAuthorizedKey(file_obj, key):
1396
  """Adds an SSH public key to an authorized_keys file.
1397

1398
  @type file_obj: str or file handle
1399
  @param file_obj: path to authorized_keys file
1400
  @type key: str
1401
  @param key: string containing key
1402

1403
  """
1404
  key_fields = key.split()
1405

    
1406
  if isinstance(file_obj, basestring):
1407
    f = open(file_obj, 'a+')
1408
  else:
1409
    f = file_obj
1410

    
1411
  try:
1412
    nl = True
1413
    for line in f:
1414
      # Ignore whitespace changes
1415
      if line.split() == key_fields:
1416
        break
1417
      nl = line.endswith('\n')
1418
    else:
1419
      if not nl:
1420
        f.write("\n")
1421
      f.write(key.rstrip('\r\n'))
1422
      f.write("\n")
1423
      f.flush()
1424
  finally:
1425
    f.close()
1426

    
1427

    
1428
def RemoveAuthorizedKey(file_name, key):
1429
  """Removes an SSH public key from an authorized_keys file.
1430

1431
  @type file_name: str
1432
  @param file_name: path to authorized_keys file
1433
  @type key: str
1434
  @param key: string containing key
1435

1436
  """
1437
  key_fields = key.split()
1438

    
1439
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1440
  try:
1441
    out = os.fdopen(fd, 'w')
1442
    try:
1443
      f = open(file_name, 'r')
1444
      try:
1445
        for line in f:
1446
          # Ignore whitespace changes while comparing lines
1447
          if line.split() != key_fields:
1448
            out.write(line)
1449

    
1450
        out.flush()
1451
        os.rename(tmpname, file_name)
1452
      finally:
1453
        f.close()
1454
    finally:
1455
      out.close()
1456
  except:
1457
    RemoveFile(tmpname)
1458
    raise
1459

    
1460

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

1464
  @type file_name: str
1465
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1466
  @type ip: str
1467
  @param ip: the IP address
1468
  @type hostname: str
1469
  @param hostname: the hostname to be added
1470
  @type aliases: list
1471
  @param aliases: the list of aliases to add for the hostname
1472

1473
  """
1474
  # Ensure aliases are unique
1475
  aliases = UniqueSequence([hostname] + aliases)[1:]
1476

    
1477
  def _WriteEtcHosts(fd):
1478
    # Duplicating file descriptor because os.fdopen's result will automatically
1479
    # close the descriptor, but we would still like to have its functionality.
1480
    out = os.fdopen(os.dup(fd), "w")
1481
    try:
1482
      for line in ReadFile(file_name).splitlines(True):
1483
        fields = line.split()
1484
        if fields and not fields[0].startswith("#") and ip == fields[0]:
1485
          continue
1486
        out.write(line)
1487

    
1488
      out.write("%s\t%s" % (ip, hostname))
1489
      if aliases:
1490
        out.write(" %s" % " ".join(aliases))
1491
      out.write("\n")
1492
      out.flush()
1493
    finally:
1494
      out.close()
1495

    
1496
  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
1497

    
1498

    
1499
def AddHostToEtcHosts(hostname, ip):
1500
  """Wrapper around SetEtcHostsEntry.
1501

1502
  @type hostname: str
1503
  @param hostname: a hostname that will be resolved and added to
1504
      L{constants.ETC_HOSTS}
1505
  @type ip: str
1506
  @param ip: The ip address of the host
1507

1508
  """
1509
  SetEtcHostsEntry(constants.ETC_HOSTS, ip, hostname, [hostname.split(".")[0]])
1510

    
1511

    
1512
def RemoveEtcHostsEntry(file_name, hostname):
1513
  """Removes a hostname from /etc/hosts.
1514

1515
  IP addresses without names are removed from the file.
1516

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

1522
  """
1523
  def _WriteEtcHosts(fd):
1524
    # Duplicating file descriptor because os.fdopen's result will automatically
1525
    # close the descriptor, but we would still like to have its functionality.
1526
    out = os.fdopen(os.dup(fd), "w")
1527
    try:
1528
      for line in ReadFile(file_name).splitlines(True):
1529
        fields = line.split()
1530
        if len(fields) > 1 and not fields[0].startswith("#"):
1531
          names = fields[1:]
1532
          if hostname in names:
1533
            while hostname in names:
1534
              names.remove(hostname)
1535
            if names:
1536
              out.write("%s %s\n" % (fields[0], " ".join(names)))
1537
            continue
1538

    
1539
        out.write(line)
1540

    
1541
      out.flush()
1542
    finally:
1543
      out.close()
1544

    
1545
  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
1546

    
1547

    
1548
def RemoveHostFromEtcHosts(hostname):
1549
  """Wrapper around RemoveEtcHostsEntry.
1550

1551
  @type hostname: str
1552
  @param hostname: hostname that will be resolved and its
1553
      full and shot name will be removed from
1554
      L{constants.ETC_HOSTS}
1555

1556
  """
1557
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname)
1558
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname.split(".")[0])
1559

    
1560

    
1561
def TimestampForFilename():
1562
  """Returns the current time formatted for filenames.
1563

1564
  The format doesn't contain colons as some shells and applications them as
1565
  separators.
1566

1567
  """
1568
  return time.strftime("%Y-%m-%d_%H_%M_%S")
1569

    
1570

    
1571
def CreateBackup(file_name):
1572
  """Creates a backup of a file.
1573

1574
  @type file_name: str
1575
  @param file_name: file to be backed up
1576
  @rtype: str
1577
  @return: the path to the newly created backup
1578
  @raise errors.ProgrammerError: for invalid file names
1579

1580
  """
1581
  if not os.path.isfile(file_name):
1582
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1583
                                file_name)
1584

    
1585
  prefix = ("%s.backup-%s." %
1586
            (os.path.basename(file_name), TimestampForFilename()))
1587
  dir_name = os.path.dirname(file_name)
1588

    
1589
  fsrc = open(file_name, 'rb')
1590
  try:
1591
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1592
    fdst = os.fdopen(fd, 'wb')
1593
    try:
1594
      logging.debug("Backing up %s at %s", file_name, backup_name)
1595
      shutil.copyfileobj(fsrc, fdst)
1596
    finally:
1597
      fdst.close()
1598
  finally:
1599
    fsrc.close()
1600

    
1601
  return backup_name
1602

    
1603

    
1604
def ShellQuote(value):
1605
  """Quotes shell argument according to POSIX.
1606

1607
  @type value: str
1608
  @param value: the argument to be quoted
1609
  @rtype: str
1610
  @return: the quoted value
1611

1612
  """
1613
  if _re_shell_unquoted.match(value):
1614
    return value
1615
  else:
1616
    return "'%s'" % value.replace("'", "'\\''")
1617

    
1618

    
1619
def ShellQuoteArgs(args):
1620
  """Quotes a list of shell arguments.
1621

1622
  @type args: list
1623
  @param args: list of arguments to be quoted
1624
  @rtype: str
1625
  @return: the quoted arguments concatenated with spaces
1626

1627
  """
1628
  return ' '.join([ShellQuote(i) for i in args])
1629

    
1630

    
1631
class ShellWriter:
1632
  """Helper class to write scripts with indentation.
1633

1634
  """
1635
  INDENT_STR = "  "
1636

    
1637
  def __init__(self, fh):
1638
    """Initializes this class.
1639

1640
    """
1641
    self._fh = fh
1642
    self._indent = 0
1643

    
1644
  def IncIndent(self):
1645
    """Increase indentation level by 1.
1646

1647
    """
1648
    self._indent += 1
1649

    
1650
  def DecIndent(self):
1651
    """Decrease indentation level by 1.
1652

1653
    """
1654
    assert self._indent > 0
1655
    self._indent -= 1
1656

    
1657
  def Write(self, txt, *args):
1658
    """Write line to output file.
1659

1660
    """
1661
    assert self._indent >= 0
1662

    
1663
    self._fh.write(self._indent * self.INDENT_STR)
1664

    
1665
    if args:
1666
      self._fh.write(txt % args)
1667
    else:
1668
      self._fh.write(txt)
1669

    
1670
    self._fh.write("\n")
1671

    
1672

    
1673
def ListVisibleFiles(path):
1674
  """Returns a list of visible files in a directory.
1675

1676
  @type path: str
1677
  @param path: the directory to enumerate
1678
  @rtype: list
1679
  @return: the list of all files not starting with a dot
1680
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
1681

1682
  """
1683
  if not IsNormAbsPath(path):
1684
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1685
                                 " absolute/normalized: '%s'" % path)
1686
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1687
  return files
1688

    
1689

    
1690
def GetHomeDir(user, default=None):
1691
  """Try to get the homedir of the given user.
1692

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

1697
  """
1698
  try:
1699
    if isinstance(user, basestring):
1700
      result = pwd.getpwnam(user)
1701
    elif isinstance(user, (int, long)):
1702
      result = pwd.getpwuid(user)
1703
    else:
1704
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1705
                                   type(user))
1706
  except KeyError:
1707
    return default
1708
  return result.pw_dir
1709

    
1710

    
1711
def NewUUID():
1712
  """Returns a random UUID.
1713

1714
  @note: This is a Linux-specific method as it uses the /proc
1715
      filesystem.
1716
  @rtype: str
1717

1718
  """
1719
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1720

    
1721

    
1722
def GenerateSecret(numbytes=20):
1723
  """Generates a random secret.
1724

1725
  This will generate a pseudo-random secret returning an hex string
1726
  (so that it can be used where an ASCII string is needed).
1727

1728
  @param numbytes: the number of bytes which will be represented by the returned
1729
      string (defaulting to 20, the length of a SHA1 hash)
1730
  @rtype: str
1731
  @return: an hex representation of the pseudo-random sequence
1732

1733
  """
1734
  return os.urandom(numbytes).encode('hex')
1735

    
1736

    
1737
def EnsureDirs(dirs):
1738
  """Make required directories, if they don't exist.
1739

1740
  @param dirs: list of tuples (dir_name, dir_mode)
1741
  @type dirs: list of (string, integer)
1742

1743
  """
1744
  for dir_name, dir_mode in dirs:
1745
    try:
1746
      os.mkdir(dir_name, dir_mode)
1747
    except EnvironmentError, err:
1748
      if err.errno != errno.EEXIST:
1749
        raise errors.GenericError("Cannot create needed directory"
1750
                                  " '%s': %s" % (dir_name, err))
1751
    try:
1752
      os.chmod(dir_name, dir_mode)
1753
    except EnvironmentError, err:
1754
      raise errors.GenericError("Cannot change directory permissions on"
1755
                                " '%s': %s" % (dir_name, err))
1756
    if not os.path.isdir(dir_name):
1757
      raise errors.GenericError("%s is not a directory" % dir_name)
1758

    
1759

    
1760
def ReadFile(file_name, size=-1):
1761
  """Reads a file.
1762

1763
  @type size: int
1764
  @param size: Read at most size bytes (if negative, entire file)
1765
  @rtype: str
1766
  @return: the (possibly partial) content of the file
1767

1768
  """
1769
  f = open(file_name, "r")
1770
  try:
1771
    return f.read(size)
1772
  finally:
1773
    f.close()
1774

    
1775

    
1776
def WriteFile(file_name, fn=None, data=None,
1777
              mode=None, uid=-1, gid=-1,
1778
              atime=None, mtime=None, close=True,
1779
              dry_run=False, backup=False,
1780
              prewrite=None, postwrite=None):
1781
  """(Over)write a file atomically.
1782

1783
  The file_name and either fn (a function taking one argument, the
1784
  file descriptor, and which should write the data to it) or data (the
1785
  contents of the file) must be passed. The other arguments are
1786
  optional and allow setting the file mode, owner and group, and the
1787
  mtime/atime of the file.
1788

1789
  If the function doesn't raise an exception, it has succeeded and the
1790
  target file has the new contents. If the function has raised an
1791
  exception, an existing target file should be unmodified and the
1792
  temporary file should be removed.
1793

1794
  @type file_name: str
1795
  @param file_name: the target filename
1796
  @type fn: callable
1797
  @param fn: content writing function, called with
1798
      file descriptor as parameter
1799
  @type data: str
1800
  @param data: contents of the file
1801
  @type mode: int
1802
  @param mode: file mode
1803
  @type uid: int
1804
  @param uid: the owner of the file
1805
  @type gid: int
1806
  @param gid: the group of the file
1807
  @type atime: int
1808
  @param atime: a custom access time to be set on the file
1809
  @type mtime: int
1810
  @param mtime: a custom modification time to be set on the file
1811
  @type close: boolean
1812
  @param close: whether to close file after writing it
1813
  @type prewrite: callable
1814
  @param prewrite: function to be called before writing content
1815
  @type postwrite: callable
1816
  @param postwrite: function to be called after writing content
1817

1818
  @rtype: None or int
1819
  @return: None if the 'close' parameter evaluates to True,
1820
      otherwise the file descriptor
1821

1822
  @raise errors.ProgrammerError: if any of the arguments are not valid
1823

1824
  """
1825
  if not os.path.isabs(file_name):
1826
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1827
                                 " absolute: '%s'" % file_name)
1828

    
1829
  if [fn, data].count(None) != 1:
1830
    raise errors.ProgrammerError("fn or data required")
1831

    
1832
  if [atime, mtime].count(None) == 1:
1833
    raise errors.ProgrammerError("Both atime and mtime must be either"
1834
                                 " set or None")
1835

    
1836
  if backup and not dry_run and os.path.isfile(file_name):
1837
    CreateBackup(file_name)
1838

    
1839
  dir_name, base_name = os.path.split(file_name)
1840
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1841
  do_remove = True
1842
  # here we need to make sure we remove the temp file, if any error
1843
  # leaves it in place
1844
  try:
1845
    if uid != -1 or gid != -1:
1846
      os.chown(new_name, uid, gid)
1847
    if mode:
1848
      os.chmod(new_name, mode)
1849
    if callable(prewrite):
1850
      prewrite(fd)
1851
    if data is not None:
1852
      os.write(fd, data)
1853
    else:
1854
      fn(fd)
1855
    if callable(postwrite):
1856
      postwrite(fd)
1857
    os.fsync(fd)
1858
    if atime is not None and mtime is not None:
1859
      os.utime(new_name, (atime, mtime))
1860
    if not dry_run:
1861
      os.rename(new_name, file_name)
1862
      do_remove = False
1863
  finally:
1864
    if close:
1865
      os.close(fd)
1866
      result = None
1867
    else:
1868
      result = fd
1869
    if do_remove:
1870
      RemoveFile(new_name)
1871

    
1872
  return result
1873

    
1874

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

1878
  Either the path to the file or the fd must be given.
1879

1880
  @param path: the file path
1881
  @param fd: a file descriptor
1882
  @return: a tuple of (device number, inode number, mtime)
1883

1884
  """
1885
  if [path, fd].count(None) != 1:
1886
    raise errors.ProgrammerError("One and only one of fd/path must be given")
1887

    
1888
  if fd is None:
1889
    st = os.stat(path)
1890
  else:
1891
    st = os.fstat(fd)
1892

    
1893
  return (st.st_dev, st.st_ino, st.st_mtime)
1894

    
1895

    
1896
def VerifyFileID(fi_disk, fi_ours):
1897
  """Verifies that two file IDs are matching.
1898

1899
  Differences in the inode/device are not accepted, but and older
1900
  timestamp for fi_disk is accepted.
1901

1902
  @param fi_disk: tuple (dev, inode, mtime) representing the actual
1903
      file data
1904
  @param fi_ours: tuple (dev, inode, mtime) representing the last
1905
      written file data
1906
  @rtype: boolean
1907

1908
  """
1909
  (d1, i1, m1) = fi_disk
1910
  (d2, i2, m2) = fi_ours
1911

    
1912
  return (d1, i1) == (d2, i2) and m1 <= m2
1913

    
1914

    
1915
def SafeWriteFile(file_name, file_id, **kwargs):
1916
  """Wraper over L{WriteFile} that locks the target file.
1917

1918
  By keeping the target file locked during WriteFile, we ensure that
1919
  cooperating writers will safely serialise access to the file.
1920

1921
  @type file_name: str
1922
  @param file_name: the target filename
1923
  @type file_id: tuple
1924
  @param file_id: a result from L{GetFileID}
1925

1926
  """
1927
  fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
1928
  try:
1929
    LockFile(fd)
1930
    if file_id is not None:
1931
      disk_id = GetFileID(fd=fd)
1932
      if not VerifyFileID(disk_id, file_id):
1933
        raise errors.LockError("Cannot overwrite file %s, it has been modified"
1934
                               " since last written" % file_name)
1935
    return WriteFile(file_name, **kwargs)
1936
  finally:
1937
    os.close(fd)
1938

    
1939

    
1940
def ReadOneLineFile(file_name, strict=False):
1941
  """Return the first non-empty line from a file.
1942

1943
  @type strict: boolean
1944
  @param strict: if True, abort if the file has more than one
1945
      non-empty line
1946

1947
  """
1948
  file_lines = ReadFile(file_name).splitlines()
1949
  full_lines = filter(bool, file_lines)
1950
  if not file_lines or not full_lines:
1951
    raise errors.GenericError("No data in one-liner file %s" % file_name)
1952
  elif strict and len(full_lines) > 1:
1953
    raise errors.GenericError("Too many lines in one-liner file %s" %
1954
                              file_name)
1955
  return full_lines[0]
1956

    
1957

    
1958
def FirstFree(seq, base=0):
1959
  """Returns the first non-existing integer from seq.
1960

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

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

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

1970
  @type seq: sequence
1971
  @param seq: the sequence to be analyzed.
1972
  @type base: int
1973
  @param base: use this value as the base index of the sequence
1974
  @rtype: int
1975
  @return: the first non-used index in the sequence
1976

1977
  """
1978
  for idx, elem in enumerate(seq):
1979
    assert elem >= base, "Passed element is higher than base offset"
1980
    if elem > idx + base:
1981
      # idx is not used
1982
      return idx + base
1983
  return None
1984

    
1985

    
1986
def SingleWaitForFdCondition(fdobj, event, timeout):
1987
  """Waits for a condition to occur on the socket.
1988

1989
  Immediately returns at the first interruption.
1990

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

2000
  """
2001
  check = (event | select.POLLPRI |
2002
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
2003

    
2004
  if timeout is not None:
2005
    # Poller object expects milliseconds
2006
    timeout *= 1000
2007

    
2008
  poller = select.poll()
2009
  poller.register(fdobj, event)
2010
  try:
2011
    # TODO: If the main thread receives a signal and we have no timeout, we
2012
    # could wait forever. This should check a global "quit" flag or something
2013
    # every so often.
2014
    io_events = poller.poll(timeout)
2015
  except select.error, err:
2016
    if err[0] != errno.EINTR:
2017
      raise
2018
    io_events = []
2019
  if io_events and io_events[0][1] & check:
2020
    return io_events[0][1]
2021
  else:
2022
    return None
2023

    
2024

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

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

2032
  """
2033

    
2034
  def __init__(self, timeout):
2035
    self.timeout = timeout
2036

    
2037
  def Poll(self, fdobj, event):
2038
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
2039
    if result is None:
2040
      raise RetryAgain()
2041
    else:
2042
      return result
2043

    
2044
  def UpdateTimeout(self, timeout):
2045
    self.timeout = timeout
2046

    
2047

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

2051
  Retries until the timeout is expired, even if interrupted.
2052

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

2062
  """
2063
  if timeout is not None:
2064
    retrywaiter = FdConditionWaiterHelper(timeout)
2065
    try:
2066
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
2067
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
2068
    except RetryTimeout:
2069
      result = None
2070
  else:
2071
    result = None
2072
    while result is None:
2073
      result = SingleWaitForFdCondition(fdobj, event, timeout)
2074
  return result
2075

    
2076

    
2077
def UniqueSequence(seq):
2078
  """Returns a list with unique elements.
2079

2080
  Element order is preserved.
2081

2082
  @type seq: sequence
2083
  @param seq: the sequence with the source elements
2084
  @rtype: list
2085
  @return: list of unique elements from seq
2086

2087
  """
2088
  seen = set()
2089
  return [i for i in seq if i not in seen and not seen.add(i)]
2090

    
2091

    
2092
def NormalizeAndValidateMac(mac):
2093
  """Normalizes and check if a MAC address is valid.
2094

2095
  Checks whether the supplied MAC address is formally correct, only
2096
  accepts colon separated format. Normalize it to all lower.
2097

2098
  @type mac: str
2099
  @param mac: the MAC to be validated
2100
  @rtype: str
2101
  @return: returns the normalized and validated MAC.
2102

2103
  @raise errors.OpPrereqError: If the MAC isn't valid
2104

2105
  """
2106
  if not _MAC_CHECK.match(mac):
2107
    raise errors.OpPrereqError("Invalid MAC address specified: %s" %
2108
                               mac, errors.ECODE_INVAL)
2109

    
2110
  return mac.lower()
2111

    
2112

    
2113
def TestDelay(duration):
2114
  """Sleep for a fixed amount of time.
2115

2116
  @type duration: float
2117
  @param duration: the sleep duration
2118
  @rtype: boolean
2119
  @return: False for negative value, True otherwise
2120

2121
  """
2122
  if duration < 0:
2123
    return False, "Invalid sleep duration"
2124
  time.sleep(duration)
2125
  return True, None
2126

    
2127

    
2128
def _CloseFDNoErr(fd, retries=5):
2129
  """Close a file descriptor ignoring errors.
2130

2131
  @type fd: int
2132
  @param fd: the file descriptor
2133
  @type retries: int
2134
  @param retries: how many retries to make, in case we get any
2135
      other error than EBADF
2136

2137
  """
2138
  try:
2139
    os.close(fd)
2140
  except OSError, err:
2141
    if err.errno != errno.EBADF:
2142
      if retries > 0:
2143
        _CloseFDNoErr(fd, retries - 1)
2144
    # else either it's closed already or we're out of retries, so we
2145
    # ignore this and go on
2146

    
2147

    
2148
def CloseFDs(noclose_fds=None):
2149
  """Close file descriptors.
2150

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

2154
  @type noclose_fds: list or None
2155
  @param noclose_fds: if given, it denotes a list of file descriptor
2156
      that should not be closed
2157

2158
  """
2159
  # Default maximum for the number of available file descriptors.
2160
  if 'SC_OPEN_MAX' in os.sysconf_names:
2161
    try:
2162
      MAXFD = os.sysconf('SC_OPEN_MAX')
2163
      if MAXFD < 0:
2164
        MAXFD = 1024
2165
    except OSError:
2166
      MAXFD = 1024
2167
  else:
2168
    MAXFD = 1024
2169
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
2170
  if (maxfd == resource.RLIM_INFINITY):
2171
    maxfd = MAXFD
2172

    
2173
  # Iterate through and close all file descriptors (except the standard ones)
2174
  for fd in range(3, maxfd):
2175
    if noclose_fds and fd in noclose_fds:
2176
      continue
2177
    _CloseFDNoErr(fd)
2178

    
2179

    
2180
def Mlockall(_ctypes=ctypes):
2181
  """Lock current process' virtual address space into RAM.
2182

2183
  This is equivalent to the C call mlockall(MCL_CURRENT|MCL_FUTURE),
2184
  see mlock(2) for more details. This function requires ctypes module.
2185

2186
  @raises errors.NoCtypesError: if ctypes module is not found
2187

2188
  """
2189
  if _ctypes is None:
2190
    raise errors.NoCtypesError()
2191

    
2192
  libc = _ctypes.cdll.LoadLibrary("libc.so.6")
2193
  if libc is None:
2194
    logging.error("Cannot set memory lock, ctypes cannot load libc")
2195
    return
2196

    
2197
  # Some older version of the ctypes module don't have built-in functionality
2198
  # to access the errno global variable, where function error codes are stored.
2199
  # By declaring this variable as a pointer to an integer we can then access
2200
  # its value correctly, should the mlockall call fail, in order to see what
2201
  # the actual error code was.
2202
  # pylint: disable-msg=W0212
2203
  libc.__errno_location.restype = _ctypes.POINTER(_ctypes.c_int)
2204

    
2205
  if libc.mlockall(_MCL_CURRENT | _MCL_FUTURE):
2206
    # pylint: disable-msg=W0212
2207
    logging.error("Cannot set memory lock: %s",
2208
                  os.strerror(libc.__errno_location().contents.value))
2209
    return
2210

    
2211
  logging.debug("Memory lock set")
2212

    
2213

    
2214
def Daemonize(logfile):
2215
  """Daemonize the current process.
2216

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

2220
  @type logfile: str
2221
  @param logfile: the logfile to which we should redirect stdout/stderr
2222
  @rtype: int
2223
  @return: the value zero
2224

2225
  """
2226
  # pylint: disable-msg=W0212
2227
  # yes, we really want os._exit
2228

    
2229
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
2230
  # least abstract the pipe functionality between them
2231

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

    
2235
  # this might fail
2236
  pid = os.fork()
2237
  if (pid == 0):  # The first child.
2238
    SetupDaemonEnv()
2239

    
2240
    # this might fail
2241
    pid = os.fork() # Fork a second child.
2242
    if (pid == 0):  # The second child.
2243
      _CloseFDNoErr(rpipe)
2244
    else:
2245
      # exit() or _exit()?  See below.
2246
      os._exit(0) # Exit parent (the first child) of the second child.
2247
  else:
2248
    _CloseFDNoErr(wpipe)
2249
    # Wait for daemon to be started (or an error message to
2250
    # arrive) and read up to 100 KB as an error message
2251
    errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
2252
    if errormsg:
2253
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
2254
      rcode = 1
2255
    else:
2256
      rcode = 0
2257
    os._exit(rcode) # Exit parent of the first child.
2258

    
2259
  SetupDaemonFDs(logfile, None)
2260
  return wpipe
2261

    
2262

    
2263
def DaemonPidFileName(name):
2264
  """Compute a ganeti pid file absolute path
2265

2266
  @type name: str
2267
  @param name: the daemon name
2268
  @rtype: str
2269
  @return: the full path to the pidfile corresponding to the given
2270
      daemon name
2271

2272
  """
2273
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
2274

    
2275

    
2276
def EnsureDaemon(name):
2277
  """Check for and start daemon if not alive.
2278

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

    
2286
  return True
2287

    
2288

    
2289
def StopDaemon(name):
2290
  """Stop daemon
2291

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

    
2299
  return True
2300

    
2301

    
2302
def WritePidFile(pidfile):
2303
  """Write the current process pidfile.
2304

2305
  @type pidfile: sting
2306
  @param pidfile: the path to the file to be written
2307
  @raise errors.LockError: if the pid file already exists and
2308
      points to a live process
2309
  @rtype: int
2310
  @return: the file descriptor of the lock file; do not close this unless
2311
      you want to unlock the pid file
2312

2313
  """
2314
  # We don't rename nor truncate the file to not drop locks under
2315
  # existing processes
2316
  fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
2317

    
2318
  # Lock the PID file (and fail if not possible to do so). Any code
2319
  # wanting to send a signal to the daemon should try to lock the PID
2320
  # file before reading it. If acquiring the lock succeeds, the daemon is
2321
  # no longer running and the signal should not be sent.
2322
  LockFile(fd_pidfile)
2323

    
2324
  os.write(fd_pidfile, "%d\n" % os.getpid())
2325

    
2326
  return fd_pidfile
2327

    
2328

    
2329
def RemovePidFile(name):
2330
  """Remove the current process pidfile.
2331

2332
  Any errors are ignored.
2333

2334
  @type name: str
2335
  @param name: the daemon name used to derive the pidfile name
2336

2337
  """
2338
  pidfilename = DaemonPidFileName(name)
2339
  # TODO: we could check here that the file contains our pid
2340
  try:
2341
    RemoveFile(pidfilename)
2342
  except: # pylint: disable-msg=W0702
2343
    pass
2344

    
2345

    
2346
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
2347
                waitpid=False):
2348
  """Kill a process given by its pid.
2349

2350
  @type pid: int
2351
  @param pid: The PID to terminate.
2352
  @type signal_: int
2353
  @param signal_: The signal to send, by default SIGTERM
2354
  @type timeout: int
2355
  @param timeout: The timeout after which, if the process is still alive,
2356
                  a SIGKILL will be sent. If not positive, no such checking
2357
                  will be done
2358
  @type waitpid: boolean
2359
  @param waitpid: If true, we should waitpid on this process after
2360
      sending signals, since it's our own child and otherwise it
2361
      would remain as zombie
2362

2363
  """
2364
  def _helper(pid, signal_, wait):
2365
    """Simple helper to encapsulate the kill/waitpid sequence"""
2366
    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
2367
      try:
2368
        os.waitpid(pid, os.WNOHANG)
2369
      except OSError:
2370
        pass
2371

    
2372
  if pid <= 0:
2373
    # kill with pid=0 == suicide
2374
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
2375

    
2376
  if not IsProcessAlive(pid):
2377
    return
2378

    
2379
  _helper(pid, signal_, waitpid)
2380

    
2381
  if timeout <= 0:
2382
    return
2383

    
2384
  def _CheckProcess():
2385
    if not IsProcessAlive(pid):
2386
      return
2387

    
2388
    try:
2389
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
2390
    except OSError:
2391
      raise RetryAgain()
2392

    
2393
    if result_pid > 0:
2394
      return
2395

    
2396
    raise RetryAgain()
2397

    
2398
  try:
2399
    # Wait up to $timeout seconds
2400
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
2401
  except RetryTimeout:
2402
    pass
2403

    
2404
  if IsProcessAlive(pid):
2405
    # Kill process if it's still alive
2406
    _helper(pid, signal.SIGKILL, waitpid)
2407

    
2408

    
2409
def FindFile(name, search_path, test=os.path.exists):
2410
  """Look for a filesystem object in a given path.
2411

2412
  This is an abstract method to search for filesystem object (files,
2413
  dirs) under a given search path.
2414

2415
  @type name: str
2416
  @param name: the name to look for
2417
  @type search_path: str
2418
  @param search_path: location to start at
2419
  @type test: callable
2420
  @param test: a function taking one argument that should return True
2421
      if the a given object is valid; the default value is
2422
      os.path.exists, causing only existing files to be returned
2423
  @rtype: str or None
2424
  @return: full path to the object if found, None otherwise
2425

2426
  """
2427
  # validate the filename mask
2428
  if constants.EXT_PLUGIN_MASK.match(name) is None:
2429
    logging.critical("Invalid value passed for external script name: '%s'",
2430
                     name)
2431
    return None
2432

    
2433
  for dir_name in search_path:
2434
    # FIXME: investigate switch to PathJoin
2435
    item_name = os.path.sep.join([dir_name, name])
2436
    # check the user test and that we're indeed resolving to the given
2437
    # basename
2438
    if test(item_name) and os.path.basename(item_name) == name:
2439
      return item_name
2440
  return None
2441

    
2442

    
2443
def CheckVolumeGroupSize(vglist, vgname, minsize):
2444
  """Checks if the volume group list is valid.
2445

2446
  The function will check if a given volume group is in the list of
2447
  volume groups and has a minimum size.
2448

2449
  @type vglist: dict
2450
  @param vglist: dictionary of volume group names and their size
2451
  @type vgname: str
2452
  @param vgname: the volume group we should check
2453
  @type minsize: int
2454
  @param minsize: the minimum size we accept
2455
  @rtype: None or str
2456
  @return: None for success, otherwise the error message
2457

2458
  """
2459
  vgsize = vglist.get(vgname, None)
2460
  if vgsize is None:
2461
    return "volume group '%s' missing" % vgname
2462
  elif vgsize < minsize:
2463
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
2464
            (vgname, minsize, vgsize))
2465
  return None
2466

    
2467

    
2468
def SplitTime(value):
2469
  """Splits time as floating point number into a tuple.
2470

2471
  @param value: Time in seconds
2472
  @type value: int or float
2473
  @return: Tuple containing (seconds, microseconds)
2474

2475
  """
2476
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
2477

    
2478
  assert 0 <= seconds, \
2479
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2480
  assert 0 <= microseconds <= 999999, \
2481
    "Microseconds must be 0-999999, but are %s" % microseconds
2482

    
2483
  return (int(seconds), int(microseconds))
2484

    
2485

    
2486
def MergeTime(timetuple):
2487
  """Merges a tuple into time as a floating point number.
2488

2489
  @param timetuple: Time as tuple, (seconds, microseconds)
2490
  @type timetuple: tuple
2491
  @return: Time as a floating point number expressed in seconds
2492

2493
  """
2494
  (seconds, microseconds) = timetuple
2495

    
2496
  assert 0 <= seconds, \
2497
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2498
  assert 0 <= microseconds <= 999999, \
2499
    "Microseconds must be 0-999999, but are %s" % microseconds
2500

    
2501
  return float(seconds) + (float(microseconds) * 0.000001)
2502

    
2503

    
2504
class LogFileHandler(logging.FileHandler):
2505
  """Log handler that doesn't fallback to stderr.
2506

2507
  When an error occurs while writing on the logfile, logging.FileHandler tries
2508
  to log on stderr. This doesn't work in ganeti since stderr is redirected to
2509
  the logfile. This class avoids failures reporting errors to /dev/console.
2510

2511
  """
2512
  def __init__(self, filename, mode="a", encoding=None):
2513
    """Open the specified file and use it as the stream for logging.
2514

2515
    Also open /dev/console to report errors while logging.
2516

2517
    """
2518
    logging.FileHandler.__init__(self, filename, mode, encoding)
2519
    self.console = open(constants.DEV_CONSOLE, "a")
2520

    
2521
  def handleError(self, record): # pylint: disable-msg=C0103
2522
    """Handle errors which occur during an emit() call.
2523

2524
    Try to handle errors with FileHandler method, if it fails write to
2525
    /dev/console.
2526

2527
    """
2528
    try:
2529
      logging.FileHandler.handleError(self, record)
2530
    except Exception: # pylint: disable-msg=W0703
2531
      try:
2532
        self.console.write("Cannot log message:\n%s\n" % self.format(record))
2533
      except Exception: # pylint: disable-msg=W0703
2534
        # Log handler tried everything it could, now just give up
2535
        pass
2536

    
2537

    
2538
def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
2539
                 multithreaded=False, syslog=constants.SYSLOG_USAGE,
2540
                 console_logging=False):
2541
  """Configures the logging module.
2542

2543
  @type logfile: str
2544
  @param logfile: the filename to which we should log
2545
  @type debug: integer
2546
  @param debug: if greater than zero, enable debug messages, otherwise
2547
      only those at C{INFO} and above level
2548
  @type stderr_logging: boolean
2549
  @param stderr_logging: whether we should also log to the standard error
2550
  @type program: str
2551
  @param program: the name under which we should log messages
2552
  @type multithreaded: boolean
2553
  @param multithreaded: if True, will add the thread name to the log file
2554
  @type syslog: string
2555
  @param syslog: one of 'no', 'yes', 'only':
2556
      - if no, syslog is not used
2557
      - if yes, syslog is used (in addition to file-logging)
2558
      - if only, only syslog is used
2559
  @type console_logging: boolean
2560
  @param console_logging: if True, will use a FileHandler which falls back to
2561
      the system console if logging fails
2562
  @raise EnvironmentError: if we can't open the log file and
2563
      syslog/stderr logging is disabled
2564

2565
  """
2566
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
2567
  sft = program + "[%(process)d]:"
2568
  if multithreaded:
2569
    fmt += "/%(threadName)s"
2570
    sft += " (%(threadName)s)"
2571
  if debug:
2572
    fmt += " %(module)s:%(lineno)s"
2573
    # no debug info for syslog loggers
2574
  fmt += " %(levelname)s %(message)s"
2575
  # yes, we do want the textual level, as remote syslog will probably
2576
  # lose the error level, and it's easier to grep for it
2577
  sft += " %(levelname)s %(message)s"
2578
  formatter = logging.Formatter(fmt)
2579
  sys_fmt = logging.Formatter(sft)
2580

    
2581
  root_logger = logging.getLogger("")
2582
  root_logger.setLevel(logging.NOTSET)
2583

    
2584
  # Remove all previously setup handlers
2585
  for handler in root_logger.handlers:
2586
    handler.close()
2587
    root_logger.removeHandler(handler)
2588

    
2589
  if stderr_logging:
2590
    stderr_handler = logging.StreamHandler()
2591
    stderr_handler.setFormatter(formatter)
2592
    if debug:
2593
      stderr_handler.setLevel(logging.NOTSET)
2594
    else:
2595
      stderr_handler.setLevel(logging.CRITICAL)
2596
    root_logger.addHandler(stderr_handler)
2597

    
2598
  if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2599
    facility = logging.handlers.SysLogHandler.LOG_DAEMON
2600
    syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2601
                                                    facility)
2602
    syslog_handler.setFormatter(sys_fmt)
2603
    # Never enable debug over syslog
2604
    syslog_handler.setLevel(logging.INFO)
2605
    root_logger.addHandler(syslog_handler)
2606

    
2607
  if syslog != constants.SYSLOG_ONLY:
2608
    # this can fail, if the logging directories are not setup or we have
2609
    # a permisssion problem; in this case, it's best to log but ignore
2610
    # the error if stderr_logging is True, and if false we re-raise the
2611
    # exception since otherwise we could run but without any logs at all
2612
    try:
2613
      if console_logging:
2614
        logfile_handler = LogFileHandler(logfile)
2615
      else:
2616
        logfile_handler = logging.FileHandler(logfile)
2617
      logfile_handler.setFormatter(formatter)
2618
      if debug:
2619
        logfile_handler.setLevel(logging.DEBUG)
2620
      else:
2621
        logfile_handler.setLevel(logging.INFO)
2622
      root_logger.addHandler(logfile_handler)
2623
    except EnvironmentError:
2624
      if stderr_logging or syslog == constants.SYSLOG_YES:
2625
        logging.exception("Failed to enable logging to file '%s'", logfile)
2626
      else:
2627
        # we need to re-raise the exception
2628
        raise
2629

    
2630

    
2631
def IsNormAbsPath(path):
2632
  """Check whether a path is absolute and also normalized
2633

2634
  This avoids things like /dir/../../other/path to be valid.
2635

2636
  """
2637
  return os.path.normpath(path) == path and os.path.isabs(path)
2638

    
2639

    
2640
def PathJoin(*args):
2641
  """Safe-join a list of path components.
2642

2643
  Requirements:
2644
      - the first argument must be an absolute path
2645
      - no component in the path must have backtracking (e.g. /../),
2646
        since we check for normalization at the end
2647

2648
  @param args: the path components to be joined
2649
  @raise ValueError: for invalid paths
2650

2651
  """
2652
  # ensure we're having at least one path passed in
2653
  assert args
2654
  # ensure the first component is an absolute and normalized path name
2655
  root = args[0]
2656
  if not IsNormAbsPath(root):
2657
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2658
  result = os.path.join(*args)
2659
  # ensure that the whole path is normalized
2660
  if not IsNormAbsPath(result):
2661
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2662
  # check that we're still under the original prefix
2663
  prefix = os.path.commonprefix([root, result])
2664
  if prefix != root:
2665
    raise ValueError("Error: path joining resulted in different prefix"
2666
                     " (%s != %s)" % (prefix, root))
2667
  return result
2668

    
2669

    
2670
def TailFile(fname, lines=20):
2671
  """Return the last lines from a file.
2672

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

2677
  @param fname: the file name
2678
  @type lines: int
2679
  @param lines: the (maximum) number of lines to return
2680

2681
  """
2682
  fd = open(fname, "r")
2683
  try:
2684
    fd.seek(0, 2)
2685
    pos = fd.tell()
2686
    pos = max(0, pos-4096)
2687
    fd.seek(pos, 0)
2688
    raw_data = fd.read()
2689
  finally:
2690
    fd.close()
2691

    
2692
  rows = raw_data.splitlines()
2693
  return rows[-lines:]
2694

    
2695

    
2696
def FormatTimestampWithTZ(secs):
2697
  """Formats a Unix timestamp with the local timezone.
2698

2699
  """
2700
  return time.strftime("%F %T %Z", time.gmtime(secs))
2701

    
2702

    
2703
def _ParseAsn1Generalizedtime(value):
2704
  """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2705

2706
  @type value: string
2707
  @param value: ASN1 GENERALIZEDTIME timestamp
2708

2709
  """
2710
  m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2711
  if m:
2712
    # We have an offset
2713
    asn1time = m.group(1)
2714
    hours = int(m.group(2))
2715
    minutes = int(m.group(3))
2716
    utcoffset = (60 * hours) + minutes
2717
  else:
2718
    if not value.endswith("Z"):
2719
      raise ValueError("Missing timezone")
2720
    asn1time = value[:-1]
2721
    utcoffset = 0
2722

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

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

    
2727
  return calendar.timegm(tt.utctimetuple())
2728

    
2729

    
2730
def GetX509CertValidity(cert):
2731
  """Returns the validity period of the certificate.
2732

2733
  @type cert: OpenSSL.crypto.X509
2734
  @param cert: X509 certificate object
2735

2736
  """
2737
  # The get_notBefore and get_notAfter functions are only supported in
2738
  # pyOpenSSL 0.7 and above.
2739
  try:
2740
    get_notbefore_fn = cert.get_notBefore
2741
  except AttributeError:
2742
    not_before = None
2743
  else:
2744
    not_before_asn1 = get_notbefore_fn()
2745

    
2746
    if not_before_asn1 is None:
2747
      not_before = None
2748
    else:
2749
      not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2750

    
2751
  try:
2752
    get_notafter_fn = cert.get_notAfter
2753
  except AttributeError:
2754
    not_after = None
2755
  else:
2756
    not_after_asn1 = get_notafter_fn()
2757

    
2758
    if not_after_asn1 is None:
2759
      not_after = None
2760
    else:
2761
      not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2762

    
2763
  return (not_before, not_after)
2764

    
2765

    
2766
def _VerifyCertificateInner(expired, not_before, not_after, now,
2767
                            warn_days, error_days):
2768
  """Verifies certificate validity.
2769

2770
  @type expired: bool
2771
  @param expired: Whether pyOpenSSL considers the certificate as expired
2772
  @type not_before: number or None
2773
  @param not_before: Unix timestamp before which certificate is not valid
2774
  @type not_after: number or None
2775
  @param not_after: Unix timestamp after which certificate is invalid
2776
  @type now: number
2777
  @param now: Current time as Unix timestamp
2778
  @type warn_days: number or None
2779
  @param warn_days: How many days before expiration a warning should be reported
2780
  @type error_days: number or None
2781
  @param error_days: How many days before expiration an error should be reported
2782

2783
  """
2784
  if expired:
2785
    msg = "Certificate is expired"
2786

    
2787
    if not_before is not None and not_after is not None:
2788
      msg += (" (valid from %s to %s)" %
2789
              (FormatTimestampWithTZ(not_before),
2790
               FormatTimestampWithTZ(not_after)))
2791
    elif not_before is not None:
2792
      msg += " (valid from %s)" % FormatTimestampWithTZ(not_before)
2793
    elif not_after is not None:
2794
      msg += " (valid until %s)" % FormatTimestampWithTZ(not_after)
2795

    
2796
    return (CERT_ERROR, msg)
2797

    
2798
  elif not_before is not None and not_before > now:
2799
    return (CERT_WARNING,
2800
            "Certificate not yet valid (valid from %s)" %
2801
            FormatTimestampWithTZ(not_before))
2802

    
2803
  elif not_after is not None:
2804
    remaining_days = int((not_after - now) / (24 * 3600))
2805

    
2806
    msg = "Certificate expires in about %d days" % remaining_days
2807

    
2808
    if error_days is not None and remaining_days <= error_days:
2809
      return (CERT_ERROR, msg)
2810

    
2811
    if warn_days is not None and remaining_days <= warn_days:
2812
      return (CERT_WARNING, msg)
2813

    
2814
  return (None, None)
2815

    
2816

    
2817
def VerifyX509Certificate(cert, warn_days, error_days):
2818
  """Verifies a certificate for LUVerifyCluster.
2819

2820
  @type cert: OpenSSL.crypto.X509
2821
  @param cert: X509 certificate object
2822
  @type warn_days: number or None
2823
  @param warn_days: How many days before expiration a warning should be reported
2824
  @type error_days: number or None
2825
  @param error_days: How many days before expiration an error should be reported
2826

2827
  """
2828
  # Depending on the pyOpenSSL version, this can just return (None, None)
2829
  (not_before, not_after) = GetX509CertValidity(cert)
2830

    
2831
  return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
2832
                                 time.time(), warn_days, error_days)
2833

    
2834

    
2835
def SignX509Certificate(cert, key, salt):
2836
  """Sign a X509 certificate.
2837

2838
  An RFC822-like signature header is added in front of the certificate.
2839

2840
  @type cert: OpenSSL.crypto.X509
2841
  @param cert: X509 certificate object
2842
  @type key: string
2843
  @param key: Key for HMAC
2844
  @type salt: string
2845
  @param salt: Salt for HMAC
2846
  @rtype: string
2847
  @return: Serialized and signed certificate in PEM format
2848

2849
  """
2850
  if not VALID_X509_SIGNATURE_SALT.match(salt):
2851
    raise errors.GenericError("Invalid salt: %r" % salt)
2852

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

    
2856
  return ("%s: %s/%s\n\n%s" %
2857
          (constants.X509_CERT_SIGNATURE_HEADER, salt,
2858
           Sha1Hmac(key, cert_pem, salt=salt),
2859
           cert_pem))
2860

    
2861

    
2862
def _ExtractX509CertificateSignature(cert_pem):
2863
  """Helper function to extract signature from X509 certificate.
2864

2865
  """
2866
  # Extract signature from original PEM data
2867
  for line in cert_pem.splitlines():
2868
    if line.startswith("---"):
2869
      break
2870

    
2871
    m = X509_SIGNATURE.match(line.strip())
2872
    if m:
2873
      return (m.group("salt"), m.group("sign"))
2874

    
2875
  raise errors.GenericError("X509 certificate signature is missing")
2876

    
2877

    
2878
def LoadSignedX509Certificate(cert_pem, key):
2879
  """Verifies a signed X509 certificate.
2880

2881
  @type cert_pem: string
2882
  @param cert_pem: Certificate in PEM format and with signature header
2883
  @type key: string
2884
  @param key: Key for HMAC
2885
  @rtype: tuple; (OpenSSL.crypto.X509, string)
2886
  @return: X509 certificate object and salt
2887

2888
  """
2889
  (salt, signature) = _ExtractX509CertificateSignature(cert_pem)
2890

    
2891
  # Load certificate
2892
  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
2893

    
2894
  # Dump again to ensure it's in a sane format
2895
  sane_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2896

    
2897
  if not VerifySha1Hmac(key, sane_pem, signature, salt=salt):
2898
    raise errors.GenericError("X509 certificate signature is invalid")
2899

    
2900
  return (cert, salt)
2901

    
2902

    
2903
def Sha1Hmac(key, text, salt=None):
2904
  """Calculates the HMAC-SHA1 digest of a text.
2905

2906
  HMAC is defined in RFC2104.
2907

2908
  @type key: string
2909
  @param key: Secret key
2910
  @type text: string
2911

2912
  """
2913
  if salt:
2914
    salted_text = salt + text
2915
  else:
2916
    salted_text = text
2917

    
2918
  return hmac.new(key, salted_text, compat.sha1).hexdigest()
2919

    
2920

    
2921
def VerifySha1Hmac(key, text, digest, salt=None):
2922
  """Verifies the HMAC-SHA1 digest of a text.
2923

2924
  HMAC is defined in RFC2104.
2925

2926
  @type key: string
2927
  @param key: Secret key
2928
  @type text: string
2929
  @type digest: string
2930
  @param digest: Expected digest
2931
  @rtype: bool
2932
  @return: Whether HMAC-SHA1 digest matches
2933

2934
  """
2935
  return digest.lower() == Sha1Hmac(key, text, salt=salt).lower()
2936

    
2937

    
2938
def SafeEncode(text):
2939
  """Return a 'safe' version of a source string.
2940

2941
  This function mangles the input string and returns a version that
2942
  should be safe to display/encode as ASCII. To this end, we first
2943
  convert it to ASCII using the 'backslashreplace' encoding which
2944
  should get rid of any non-ASCII chars, and then we process it
2945
  through a loop copied from the string repr sources in the python; we
2946
  don't use string_escape anymore since that escape single quotes and
2947
  backslashes too, and that is too much; and that escaping is not
2948
  stable, i.e. string_escape(string_escape(x)) != string_escape(x).
2949

2950
  @type text: str or unicode
2951
  @param text: input data
2952
  @rtype: str
2953
  @return: a safe version of text
2954

2955
  """
2956
  if isinstance(text, unicode):
2957
    # only if unicode; if str already, we handle it below
2958
    text = text.encode('ascii', 'backslashreplace')
2959
  resu = ""
2960
  for char in text:
2961
    c = ord(char)
2962
    if char  == '\t':
2963
      resu += r'\t'
2964
    elif char == '\n':
2965
      resu += r'\n'
2966
    elif char == '\r':
2967
      resu += r'\'r'
2968
    elif c < 32 or c >= 127: # non-printable
2969
      resu += "\\x%02x" % (c & 0xff)
2970
    else:
2971
      resu += char
2972
  return resu
2973

    
2974

    
2975
def UnescapeAndSplit(text, sep=","):
2976
  """Split and unescape a string based on a given separator.
2977

2978
  This function splits a string based on a separator where the
2979
  separator itself can be escape in order to be an element of the
2980
  elements. The escaping rules are (assuming coma being the
2981
  separator):
2982
    - a plain , separates the elements
2983
    - a sequence \\\\, (double backslash plus comma) is handled as a
2984
      backslash plus a separator comma
2985
    - a sequence \, (backslash plus comma) is handled as a
2986
      non-separator comma
2987

2988
  @type text: string
2989
  @param text: the string to split
2990
  @type sep: string
2991
  @param text: the separator
2992
  @rtype: string
2993
  @return: a list of strings
2994

2995
  """
2996
  # we split the list by sep (with no escaping at this stage)
2997
  slist = text.split(sep)
2998
  # next, we revisit the elements and if any of them ended with an odd
2999
  # number of backslashes, then we join it with the next
3000
  rlist = []
3001
  while slist:
3002
    e1 = slist.pop(0)
3003
    if e1.endswith("\\"):
3004
      num_b = len(e1) - len(e1.rstrip("\\"))
3005
      if num_b % 2 == 1:
3006
        e2 = slist.pop(0)
3007
        # here the backslashes remain (all), and will be reduced in
3008
        # the next step
3009
        rlist.append(e1 + sep + e2)
3010
        continue
3011
    rlist.append(e1)
3012
  # finally, replace backslash-something with something
3013
  rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
3014
  return rlist
3015

    
3016

    
3017
def CommaJoin(names):
3018
  """Nicely join a set of identifiers.
3019

3020
  @param names: set, list or tuple
3021
  @return: a string with the formatted results
3022

3023
  """
3024
  return ", ".join([str(val) for val in names])
3025

    
3026

    
3027
def FindMatch(data, name):
3028
  """Tries to find an item in a dictionary matching a name.
3029

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

3034
  @type data: dict
3035
  @param data: Dictionary containing data
3036
  @type name: string
3037
  @param name: Name to look for
3038
  @rtype: tuple; (value in dictionary, matched groups as list)
3039

3040
  """
3041
  if name in data:
3042
    return (data[name], [])
3043

    
3044
  for key, value in data.items():
3045
    # Regex objects
3046
    if hasattr(key, "match"):
3047
      m = key.match(name)
3048
      if m:
3049
        return (value, list(m.groups()))
3050

    
3051
  return None
3052

    
3053

    
3054
def BytesToMebibyte(value):
3055
  """Converts bytes to mebibytes.
3056

3057
  @type value: int
3058
  @param value: Value in bytes
3059
  @rtype: int
3060
  @return: Value in mebibytes
3061

3062
  """
3063
  return int(round(value / (1024.0 * 1024.0), 0))
3064

    
3065

    
3066
def CalculateDirectorySize(path):
3067
  """Calculates the size of a directory recursively.
3068

3069
  @type path: string
3070
  @param path: Path to directory
3071
  @rtype: int
3072
  @return: Size in mebibytes
3073

3074
  """
3075
  size = 0
3076

    
3077
  for (curpath, _, files) in os.walk(path):
3078
    for filename in files:
3079
      st = os.lstat(PathJoin(curpath, filename))
3080
      size += st.st_size
3081

    
3082
  return BytesToMebibyte(size)
3083

    
3084

    
3085
def GetMounts(filename=constants.PROC_MOUNTS):
3086
  """Returns the list of mounted filesystems.
3087

3088
  This function is Linux-specific.
3089

3090
  @param filename: path of mounts file (/proc/mounts by default)
3091
  @rtype: list of tuples
3092
  @return: list of mount entries (device, mountpoint, fstype, options)
3093

3094
  """
3095
  # TODO(iustin): investigate non-Linux options (e.g. via mount output)
3096
  data = []
3097
  mountlines = ReadFile(filename).splitlines()
3098
  for line in mountlines:
3099
    device, mountpoint, fstype, options, _ = line.split(None, 4)
3100
    data.append((device, mountpoint, fstype, options))
3101

    
3102
  return data
3103

    
3104

    
3105
def GetFilesystemStats(path):
3106
  """Returns the total and free space on a filesystem.
3107

3108
  @type path: string
3109
  @param path: Path on filesystem to be examined
3110
  @rtype: int
3111
  @return: tuple of (Total space, Free space) in mebibytes
3112

3113
  """
3114
  st = os.statvfs(path)
3115

    
3116
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
3117
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
3118
  return (tsize, fsize)
3119

    
3120

    
3121
def RunInSeparateProcess(fn, *args):
3122
  """Runs a function in a separate process.
3123

3124
  Note: Only boolean return values are supported.
3125

3126
  @type fn: callable
3127
  @param fn: Function to be called
3128
  @rtype: bool
3129
  @return: Function's result
3130

3131
  """
3132
  pid = os.fork()
3133
  if pid == 0:
3134
    # Child process
3135
    try:
3136
      # In case the function uses temporary files
3137
      ResetTempfileModule()
3138

    
3139
      # Call function
3140
      result = int(bool(fn(*args)))
3141
      assert result in (0, 1)
3142
    except: # pylint: disable-msg=W0702
3143
      logging.exception("Error while calling function in separate process")
3144
      # 0 and 1 are reserved for the return value
3145
      result = 33
3146

    
3147
    os._exit(result) # pylint: disable-msg=W0212
3148

    
3149
  # Parent process
3150

    
3151
  # Avoid zombies and check exit code
3152
  (_, status) = os.waitpid(pid, 0)
3153

    
3154
  if os.WIFSIGNALED(status):
3155
    exitcode = None
3156
    signum = os.WTERMSIG(status)
3157
  else:
3158
    exitcode = os.WEXITSTATUS(status)
3159
    signum = None
3160

    
3161
  if not (exitcode in (0, 1) and signum is None):
3162
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
3163
                              (exitcode, signum))
3164

    
3165
  return bool(exitcode)
3166

    
3167

    
3168
def IgnoreProcessNotFound(fn, *args, **kwargs):
3169
  """Ignores ESRCH when calling a process-related function.
3170

3171
  ESRCH is raised when a process is not found.
3172

3173
  @rtype: bool
3174
  @return: Whether process was found
3175

3176
  """
3177
  try:
3178
    fn(*args, **kwargs)
3179
  except EnvironmentError, err:
3180
    # Ignore ESRCH
3181
    if err.errno == errno.ESRCH:
3182
      return False
3183
    raise
3184

    
3185
  return True
3186

    
3187

    
3188
def IgnoreSignals(fn, *args, **kwargs):
3189
  """Tries to call a function ignoring failures due to EINTR.
3190

3191
  """
3192
  try:
3193
    return fn(*args, **kwargs)
3194
  except EnvironmentError, err:
3195
    if err.errno == errno.EINTR:
3196
      return None
3197
    else:
3198
      raise
3199
  except (select.error, socket.error), err:
3200
    # In python 2.6 and above select.error is an IOError, so it's handled
3201
    # above, in 2.5 and below it's not, and it's handled here.
3202
    if err.args and err.args[0] == errno.EINTR:
3203
      return None
3204
    else:
3205
      raise
3206

    
3207

    
3208
def LockFile(fd):
3209
  """Locks a file using POSIX locks.
3210

3211
  @type fd: int
3212
  @param fd: the file descriptor we need to lock
3213

3214
  """
3215
  try:
3216
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
3217
  except IOError, err:
3218
    if err.errno == errno.EAGAIN:
3219
      raise errors.LockError("File already locked")
3220
    raise
3221

    
3222

    
3223
def FormatTime(val):
3224
  """Formats a time value.
3225

3226
  @type val: float or None
3227
  @param val: the timestamp as returned by time.time()
3228
  @return: a string value or N/A if we don't have a valid timestamp
3229

3230
  """
3231
  if val is None or not isinstance(val, (int, float)):
3232
    return "N/A"
3233
  # these two codes works on Linux, but they are not guaranteed on all
3234
  # platforms
3235
  return time.strftime("%F %T", time.localtime(val))
3236

    
3237

    
3238
def FormatSeconds(secs):
3239
  """Formats seconds for easier reading.
3240

3241
  @type secs: number
3242
  @param secs: Number of seconds
3243
  @rtype: string
3244
  @return: Formatted seconds (e.g. "2d 9h 19m 49s")
3245

3246
  """
3247
  parts = []
3248

    
3249
  secs = round(secs, 0)
3250

    
3251
  if secs > 0:
3252
    # Negative values would be a bit tricky
3253
    for unit, one in [("d", 24 * 60 * 60), ("h", 60 * 60), ("m", 60)]:
3254
      (complete, secs) = divmod(secs, one)
3255
      if complete or parts:
3256
        parts.append("%d%s" % (complete, unit))
3257

    
3258
  parts.append("%ds" % secs)
3259

    
3260
  return " ".join(parts)
3261

    
3262

    
3263
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
3264
  """Reads the watcher pause file.
3265

3266
  @type filename: string
3267
  @param filename: Path to watcher pause file
3268
  @type now: None, float or int
3269
  @param now: Current time as Unix timestamp
3270
  @type remove_after: int
3271
  @param remove_after: Remove watcher pause file after specified amount of
3272
    seconds past the pause end time
3273

3274
  """
3275
  if now is None:
3276
    now = time.time()
3277

    
3278
  try:
3279
    value = ReadFile(filename)
3280
  except IOError, err:
3281
    if err.errno != errno.ENOENT:
3282
      raise
3283
    value = None
3284

    
3285
  if value is not None:
3286
    try:
3287
      value = int(value)
3288
    except ValueError:
3289
      logging.warning(("Watcher pause file (%s) contains invalid value,"
3290
                       " removing it"), filename)
3291
      RemoveFile(filename)
3292
      value = None
3293

    
3294
    if value is not None:
3295
      # Remove file if it's outdated
3296
      if now > (value + remove_after):
3297
        RemoveFile(filename)
3298
        value = None
3299

    
3300
      elif now > value:
3301
        value = None
3302

    
3303
  return value
3304

    
3305

    
3306
class RetryTimeout(Exception):
3307
  """Retry loop timed out.
3308

3309
  Any arguments which was passed by the retried function to RetryAgain will be
3310
  preserved in RetryTimeout, if it is raised. If such argument was an exception
3311
  the RaiseInner helper method will reraise it.
3312

3313
  """
3314
  def RaiseInner(self):
3315
    if self.args and isinstance(self.args[0], Exception):
3316
      raise self.args[0]
3317
    else:
3318
      raise RetryTimeout(*self.args)
3319

    
3320

    
3321
class RetryAgain(Exception):
3322
  """Retry again.
3323

3324
  Any arguments passed to RetryAgain will be preserved, if a timeout occurs, as
3325
  arguments to RetryTimeout. If an exception is passed, the RaiseInner() method
3326
  of the RetryTimeout() method can be used to reraise it.
3327

3328
  """
3329

    
3330

    
3331
class _RetryDelayCalculator(object):
3332
  """Calculator for increasing delays.
3333

3334
  """
3335
  __slots__ = [
3336
    "_factor",
3337
    "_limit",
3338
    "_next",
3339
    "_start",
3340
    ]
3341

    
3342
  def __init__(self, start, factor, limit):
3343
    """Initializes this class.
3344

3345
    @type start: float
3346
    @param start: Initial delay
3347
    @type factor: float
3348
    @param factor: Factor for delay increase
3349
    @type limit: float or None
3350
    @param limit: Upper limit for delay or None for no limit
3351

3352
    """
3353
    assert start > 0.0
3354
    assert factor >= 1.0
3355
    assert limit is None or limit >= 0.0
3356

    
3357
    self._start = start
3358
    self._factor = factor
3359
    self._limit = limit
3360

    
3361
    self._next = start
3362

    
3363
  def __call__(self):
3364
    """Returns current delay and calculates the next one.
3365

3366
    """
3367
    current = self._next
3368

    
3369
    # Update for next run
3370
    if self._limit is None or self._next < self._limit:
3371
      self._next = min(self._limit, self._next * self._factor)
3372

    
3373
    return current
3374

    
3375

    
3376
#: Special delay to specify whole remaining timeout
3377
RETRY_REMAINING_TIME = object()
3378

    
3379

    
3380
def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
3381
          _time_fn=time.time):
3382
  """Call a function repeatedly until it succeeds.
3383

3384
  The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
3385
  anymore. Between calls a delay, specified by C{delay}, is inserted. After a
3386
  total of C{timeout} seconds, this function throws L{RetryTimeout}.
3387

3388
  C{delay} can be one of the following:
3389
    - callable returning the delay length as a float
3390
    - Tuple of (start, factor, limit)
3391
    - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
3392
      useful when overriding L{wait_fn} to wait for an external event)
3393
    - A static delay as a number (int or float)
3394

3395
  @type fn: callable
3396
  @param fn: Function to be called
3397
  @param delay: Either a callable (returning the delay), a tuple of (start,
3398
                factor, limit) (see L{_RetryDelayCalculator}),
3399
                L{RETRY_REMAINING_TIME} or a number (int or float)
3400
  @type timeout: float
3401
  @param timeout: Total timeout
3402
  @type wait_fn: callable
3403
  @param wait_fn: Waiting function
3404
  @return: Return value of function
3405

3406
  """
3407
  assert callable(fn)
3408
  assert callable(wait_fn)
3409
  assert callable(_time_fn)
3410

    
3411
  if args is None:
3412
    args = []
3413

    
3414
  end_time = _time_fn() + timeout
3415

    
3416
  if callable(delay):
3417
    # External function to calculate delay
3418
    calc_delay = delay
3419

    
3420
  elif isinstance(delay, (tuple, list)):
3421
    # Increasing delay with optional upper boundary
3422
    (start, factor, limit) = delay
3423
    calc_delay = _RetryDelayCalculator(start, factor, limit)
3424

    
3425
  elif delay is RETRY_REMAINING_TIME:
3426
    # Always use the remaining time
3427
    calc_delay = None
3428

    
3429
  else:
3430
    # Static delay
3431
    calc_delay = lambda: delay
3432

    
3433
  assert calc_delay is None or callable(calc_delay)
3434

    
3435
  while True:
3436
    retry_args = []
3437
    try:
3438
      # pylint: disable-msg=W0142
3439
      return fn(*args)
3440
    except RetryAgain, err:
3441
      retry_args = err.args
3442
    except RetryTimeout:
3443
      raise errors.ProgrammerError("Nested retry loop detected that didn't"
3444
                                   " handle RetryTimeout")
3445

    
3446
    remaining_time = end_time - _time_fn()
3447

    
3448
    if remaining_time < 0.0:
3449
      # pylint: disable-msg=W0142
3450
      raise RetryTimeout(*retry_args)
3451

    
3452
    assert remaining_time >= 0.0
3453

    
3454
    if calc_delay is None:
3455
      wait_fn(remaining_time)
3456
    else:
3457
      current_delay = calc_delay()
3458
      if current_delay > 0.0:
3459
        wait_fn(current_delay)
3460

    
3461

    
3462
def GetClosedTempfile(*args, **kwargs):
3463
  """Creates a temporary file and returns its path.
3464

3465
  """
3466
  (fd, path) = tempfile.mkstemp(*args, **kwargs)
3467
  _CloseFDNoErr(fd)
3468
  return path
3469

    
3470

    
3471
def GenerateSelfSignedX509Cert(common_name, validity):
3472
  """Generates a self-signed X509 certificate.
3473

3474
  @type common_name: string
3475
  @param common_name: commonName value
3476
  @type validity: int
3477
  @param validity: Validity for certificate in seconds
3478

3479
  """
3480
  # Create private and public key
3481
  key = OpenSSL.crypto.PKey()
3482
  key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
3483

    
3484
  # Create self-signed certificate
3485
  cert = OpenSSL.crypto.X509()
3486
  if common_name:
3487
    cert.get_subject().CN = common_name
3488
  cert.set_serial_number(1)
3489
  cert.gmtime_adj_notBefore(0)
3490
  cert.gmtime_adj_notAfter(validity)
3491
  cert.set_issuer(cert.get_subject())
3492
  cert.set_pubkey(key)
3493
  cert.sign(key, constants.X509_CERT_SIGN_DIGEST)
3494

    
3495
  key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
3496
  cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
3497

    
3498
  return (key_pem, cert_pem)
3499

    
3500

    
3501
def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
3502
                              validity=constants.X509_CERT_DEFAULT_VALIDITY):
3503
  """Legacy function to generate self-signed X509 certificate.
3504

3505
  @type filename: str
3506
  @param filename: path to write certificate to
3507
  @type common_name: string
3508
  @param common_name: commonName value
3509
  @type validity: int
3510
  @param validity: validity of certificate in number of days
3511

3512
  """
3513
  # TODO: Investigate using the cluster name instead of X505_CERT_CN for
3514
  # common_name, as cluster-renames are very seldom, and it'd be nice if RAPI
3515
  # and node daemon certificates have the proper Subject/Issuer.
3516
  (key_pem, cert_pem) = GenerateSelfSignedX509Cert(common_name,
3517
                                                   validity * 24 * 60 * 60)
3518

    
3519
  WriteFile(filename, mode=0400, data=key_pem + cert_pem)
3520

    
3521

    
3522
class FileLock(object):
3523
  """Utility class for file locks.
3524

3525
  """
3526
  def __init__(self, fd, filename):
3527
    """Constructor for FileLock.
3528

3529
    @type fd: file
3530
    @param fd: File object
3531
    @type filename: str
3532
    @param filename: Path of the file opened at I{fd}
3533

3534
    """
3535
    self.fd = fd
3536
    self.filename = filename
3537

    
3538
  @classmethod
3539
  def Open(cls, filename):
3540
    """Creates and opens a file to be used as a file-based lock.
3541

3542
    @type filename: string
3543
    @param filename: path to the file to be locked
3544

3545
    """
3546
    # Using "os.open" is necessary to allow both opening existing file
3547
    # read/write and creating if not existing. Vanilla "open" will truncate an
3548
    # existing file -or- allow creating if not existing.
3549
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
3550
               filename)
3551

    
3552
  def __del__(self):
3553
    self.Close()
3554

    
3555
  def Close(self):
3556
    """Close the file and release the lock.
3557

3558
    """
3559
    if hasattr(self, "fd") and self.fd:
3560
      self.fd.close()
3561
      self.fd = None
3562

    
3563
  def _flock(self, flag, blocking, timeout, errmsg):
3564
    """Wrapper for fcntl.flock.
3565

3566
    @type flag: int
3567
    @param flag: operation flag
3568
    @type blocking: bool
3569
    @param blocking: whether the operation should be done in blocking mode.
3570
    @type timeout: None or float
3571
    @param timeout: for how long the operation should be retried (implies
3572
                    non-blocking mode).
3573
    @type errmsg: string
3574
    @param errmsg: error message in case operation fails.
3575

3576
    """
3577
    assert self.fd, "Lock was closed"
3578
    assert timeout is None or timeout >= 0, \
3579
      "If specified, timeout must be positive"
3580
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
3581

    
3582
    # When a timeout is used, LOCK_NB must always be set
3583
    if not (timeout is None and blocking):
3584
      flag |= fcntl.LOCK_NB
3585

    
3586
    if timeout is None:
3587
      self._Lock(self.fd, flag, timeout)
3588
    else:
3589
      try:
3590
        Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
3591
              args=(self.fd, flag, timeout))
3592
      except RetryTimeout:
3593
        raise errors.LockError(errmsg)
3594

    
3595
  @staticmethod
3596
  def _Lock(fd, flag, timeout):
3597
    try:
3598
      fcntl.flock(fd, flag)
3599
    except IOError, err:
3600
      if timeout is not None and err.errno == errno.EAGAIN:
3601
        raise RetryAgain()
3602

    
3603
      logging.exception("fcntl.flock failed")
3604
      raise
3605

    
3606
  def Exclusive(self, blocking=False, timeout=None):
3607
    """Locks the file in exclusive mode.
3608

3609
    @type blocking: boolean
3610
    @param blocking: whether to block and wait until we
3611
        can lock the file or return immediately
3612
    @type timeout: int or None
3613
    @param timeout: if not None, the duration to wait for the lock
3614
        (in blocking mode)
3615

3616
    """
3617
    self._flock(fcntl.LOCK_EX, blocking, timeout,
3618
                "Failed to lock %s in exclusive mode" % self.filename)
3619

    
3620
  def Shared(self, blocking=False, timeout=None):
3621
    """Locks the file in shared mode.
3622

3623
    @type blocking: boolean
3624
    @param blocking: whether to block and wait until we
3625
        can lock the file or return immediately
3626
    @type timeout: int or None
3627
    @param timeout: if not None, the duration to wait for the lock
3628
        (in blocking mode)
3629

3630
    """
3631
    self._flock(fcntl.LOCK_SH, blocking, timeout,
3632
                "Failed to lock %s in shared mode" % self.filename)
3633

    
3634
  def Unlock(self, blocking=True, timeout=None):
3635
    """Unlocks the file.
3636

3637
    According to C{flock(2)}, unlocking can also be a nonblocking
3638
    operation::
3639

3640
      To make a non-blocking request, include LOCK_NB with any of the above
3641
      operations.
3642

3643
    @type blocking: boolean
3644
    @param blocking: whether to block and wait until we
3645
        can lock the file or return immediately
3646
    @type timeout: int or None
3647
    @param timeout: if not None, the duration to wait for the lock
3648
        (in blocking mode)
3649

3650
    """
3651
    self._flock(fcntl.LOCK_UN, blocking, timeout,
3652
                "Failed to unlock %s" % self.filename)
3653

    
3654

    
3655
class LineSplitter:
3656
  """Splits data chunks into lines separated by newline.
3657

3658
  Instances provide a file-like interface.
3659

3660
  """
3661
  def __init__(self, line_fn, *args):
3662
    """Initializes this class.
3663

3664
    @type line_fn: callable
3665
    @param line_fn: Function called for each line, first parameter is line
3666
    @param args: Extra arguments for L{line_fn}
3667

3668
    """
3669
    assert callable(line_fn)
3670

    
3671
    if args:
3672
      # Python 2.4 doesn't have functools.partial yet
3673
      self._line_fn = \
3674
        lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
3675
    else:
3676
      self._line_fn = line_fn
3677

    
3678
    self._lines = collections.deque()
3679
    self._buffer = ""
3680

    
3681
  def write(self, data):
3682
    parts = (self._buffer + data).split("\n")
3683
    self._buffer = parts.pop()
3684
    self._lines.extend(parts)
3685

    
3686
  def flush(self):
3687
    while self._lines:
3688
      self._line_fn(self._lines.popleft().rstrip("\r\n"))
3689

    
3690
  def close(self):
3691
    self.flush()
3692
    if self._buffer:
3693
      self._line_fn(self._buffer)
3694

    
3695

    
3696
def SignalHandled(signums):
3697
  """Signal Handled decoration.
3698

3699
  This special decorator installs a signal handler and then calls the target
3700
  function. The function must accept a 'signal_handlers' keyword argument,
3701
  which will contain a dict indexed by signal number, with SignalHandler
3702
  objects as values.
3703

3704
  The decorator can be safely stacked with iself, to handle multiple signals
3705
  with different handlers.
3706

3707
  @type signums: list
3708
  @param signums: signals to intercept
3709

3710
  """
3711
  def wrap(fn):
3712
    def sig_function(*args, **kwargs):
3713
      assert 'signal_handlers' not in kwargs or \
3714
             kwargs['signal_handlers'] is None or \
3715
             isinstance(kwargs['signal_handlers'], dict), \
3716
             "Wrong signal_handlers parameter in original function call"
3717
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
3718
        signal_handlers = kwargs['signal_handlers']
3719
      else:
3720
        signal_handlers = {}
3721
        kwargs['signal_handlers'] = signal_handlers
3722
      sighandler = SignalHandler(signums)
3723
      try:
3724
        for sig in signums:
3725
          signal_handlers[sig] = sighandler
3726
        return fn(*args, **kwargs)
3727
      finally:
3728
        sighandler.Reset()
3729
    return sig_function
3730
  return wrap
3731

    
3732

    
3733
class SignalWakeupFd(object):
3734
  try:
3735
    # This is only supported in Python 2.5 and above (some distributions
3736
    # backported it to Python 2.4)
3737
    _set_wakeup_fd_fn = signal.set_wakeup_fd
3738
  except AttributeError:
3739
    # Not supported
3740
    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
3741
      return -1
3742
  else:
3743
    def _SetWakeupFd(self, fd):
3744
      return self._set_wakeup_fd_fn(fd)
3745

    
3746
  def __init__(self):
3747
    """Initializes this class.
3748

3749
    """
3750
    (read_fd, write_fd) = os.pipe()
3751

    
3752
    # Once these succeeded, the file descriptors will be closed automatically.
3753
    # Buffer size 0 is important, otherwise .read() with a specified length
3754
    # might buffer data and the file descriptors won't be marked readable.
3755
    self._read_fh = os.fdopen(read_fd, "r", 0)
3756
    self._write_fh = os.fdopen(write_fd, "w", 0)
3757

    
3758
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
3759

    
3760
    # Utility functions
3761
    self.fileno = self._read_fh.fileno
3762
    self.read = self._read_fh.read
3763

    
3764
  def Reset(self):
3765
    """Restores the previous wakeup file descriptor.
3766

3767
    """
3768
    if hasattr(self, "_previous") and self._previous is not None:
3769
      self._SetWakeupFd(self._previous)
3770
      self._previous = None
3771

    
3772
  def Notify(self):
3773
    """Notifies the wakeup file descriptor.
3774

3775
    """
3776
    self._write_fh.write("\0")
3777

    
3778
  def __del__(self):
3779
    """Called before object deletion.
3780

3781
    """
3782
    self.Reset()
3783

    
3784

    
3785
class SignalHandler(object):
3786
  """Generic signal handler class.
3787

3788
  It automatically restores the original handler when deconstructed or
3789
  when L{Reset} is called. You can either pass your own handler
3790
  function in or query the L{called} attribute to detect whether the
3791
  signal was sent.
3792

3793
  @type signum: list
3794
  @ivar signum: the signals we handle
3795
  @type called: boolean
3796
  @ivar called: tracks whether any of the signals have been raised
3797

3798
  """
3799
  def __init__(self, signum, handler_fn=None, wakeup=None):
3800
    """Constructs a new SignalHandler instance.
3801

3802
    @type signum: int or list of ints
3803
    @param signum: Single signal number or set of signal numbers
3804
    @type handler_fn: callable
3805
    @param handler_fn: Signal handling function
3806

3807
    """
3808
    assert handler_fn is None or callable(handler_fn)
3809

    
3810
    self.signum = set(signum)
3811
    self.called = False
3812

    
3813
    self._handler_fn = handler_fn
3814
    self._wakeup = wakeup
3815

    
3816
    self._previous = {}
3817
    try:
3818
      for signum in self.signum:
3819
        # Setup handler
3820
        prev_handler = signal.signal(signum, self._HandleSignal)
3821
        try:
3822
          self._previous[signum] = prev_handler
3823
        except:
3824
          # Restore previous handler
3825
          signal.signal(signum, prev_handler)
3826
          raise
3827
    except:
3828
      # Reset all handlers
3829
      self.Reset()
3830
      # Here we have a race condition: a handler may have already been called,
3831
      # but there's not much we can do about it at this point.
3832
      raise
3833

    
3834
  def __del__(self):
3835
    self.Reset()
3836

    
3837
  def Reset(self):
3838
    """Restore previous handler.
3839

3840
    This will reset all the signals to their previous handlers.
3841

3842
    """
3843
    for signum, prev_handler in self._previous.items():
3844
      signal.signal(signum, prev_handler)
3845
      # If successful, remove from dict
3846
      del self._previous[signum]
3847

    
3848
  def Clear(self):
3849
    """Unsets the L{called} flag.
3850

3851
    This function can be used in case a signal may arrive several times.
3852

3853
    """
3854
    self.called = False
3855

    
3856
  def _HandleSignal(self, signum, frame):
3857
    """Actual signal handling function.
3858

3859
    """
3860
    # This is not nice and not absolutely atomic, but it appears to be the only
3861
    # solution in Python -- there are no atomic types.
3862
    self.called = True
3863

    
3864
    if self._wakeup:
3865
      # Notify whoever is interested in signals
3866
      self._wakeup.Notify()
3867

    
3868
    if self._handler_fn:
3869
      self._handler_fn(signum, frame)
3870

    
3871

    
3872
class FieldSet(object):
3873
  """A simple field set.
3874

3875
  Among the features are:
3876
    - checking if a string is among a list of static string or regex objects
3877
    - checking if a whole list of string matches
3878
    - returning the matching groups from a regex match
3879

3880
  Internally, all fields are held as regular expression objects.
3881

3882
  """
3883
  def __init__(self, *items):
3884
    self.items = [re.compile("^%s$" % value) for value in items]
3885

    
3886
  def Extend(self, other_set):
3887
    """Extend the field set with the items from another one"""
3888
    self.items.extend(other_set.items)
3889

    
3890
  def Matches(self, field):
3891
    """Checks if a field matches the current set
3892

3893
    @type field: str
3894
    @param field: the string to match
3895
    @return: either None or a regular expression match object
3896

3897
    """
3898
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
3899
      return m
3900
    return None
3901

    
3902
  def NonMatching(self, items):
3903
    """Returns the list of fields not matching the current set
3904

3905
    @type items: list
3906
    @param items: the list of fields to check
3907
    @rtype: list
3908
    @return: list of non-matching fields
3909

3910
    """
3911
    return [val for val in items if not self.Matches(val)]
3912

    
3913

    
3914
class RunningTimeout(object):
3915
  """Class to calculate remaining timeout when doing several operations.
3916

3917
  """
3918
  __slots__ = [
3919
    "_allow_negative",
3920
    "_start_time",
3921
    "_time_fn",
3922
    "_timeout",
3923
    ]
3924

    
3925
  def __init__(self, timeout, allow_negative, _time_fn=time.time):
3926
    """Initializes this class.
3927

3928
    @type timeout: float
3929
    @param timeout: Timeout duration
3930
    @type allow_negative: bool
3931
    @param allow_negative: Whether to return values below zero
3932
    @param _time_fn: Time function for unittests
3933

3934
    """
3935
    object.__init__(self)
3936

    
3937
    if timeout is not None and timeout < 0.0:
3938
      raise ValueError("Timeout must not be negative")
3939

    
3940
    self._timeout = timeout
3941
    self._allow_negative = allow_negative
3942
    self._time_fn = _time_fn
3943

    
3944
    self._start_time = None
3945

    
3946
  def Remaining(self):
3947
    """Returns the remaining timeout.
3948

3949
    """
3950
    if self._timeout is None:
3951
      return None
3952

    
3953
    # Get start time on first calculation
3954
    if self._start_time is None:
3955
      self._start_time = self._time_fn()
3956

    
3957
    # Calculate remaining time
3958
    remaining_timeout = self._start_time + self._timeout - self._time_fn()
3959

    
3960
    if not self._allow_negative:
3961
      # Ensure timeout is always >= 0
3962
      return max(0.0, remaining_timeout)
3963

    
3964
    return remaining_timeout