Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 20601361

History | View | Annotate | Download (83.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 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 time
32
import subprocess
33
import re
34
import socket
35
import tempfile
36
import shutil
37
import errno
38
import pwd
39
import itertools
40
import select
41
import fcntl
42
import resource
43
import logging
44
import logging.handlers
45
import signal
46
import datetime
47
import calendar
48
import collections
49
import struct
50
import IN
51

    
52
from cStringIO import StringIO
53

    
54
try:
55
  from hashlib import sha1
56
except ImportError:
57
  import sha
58
  sha1 = sha.new
59

    
60
try:
61
  import ctypes
62
except ImportError:
63
  ctypes = None
64

    
65
from ganeti import errors
66
from ganeti import constants
67

    
68

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

    
72
debug_locks = False
73

    
74
#: when set to True, L{RunCmd} is disabled
75
no_fork = False
76

    
77
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
78

    
79
# Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
80
# struct ucred { pid_t pid; uid_t uid; gid_t gid; };
81
#
82
# The GNU C Library defines gid_t and uid_t to be "unsigned int" and
83
# pid_t to "int".
84
#
85
# IEEE Std 1003.1-2008:
86
# "nlink_t, uid_t, gid_t, and id_t shall be integer types"
87
# "blksize_t, pid_t, and ssize_t shall be signed integer types"
88
_STRUCT_UCRED = "iII"
89
_STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
90

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

    
95

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

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

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

    
118

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

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

    
134
    if self.failed:
135
      logging.debug("Command '%s' failed (%s); output: %s",
136
                    self.cmd, self.fail_reason, self.output)
137

    
138
  def _GetOutput(self):
139
    """Returns the combined stdout and stderr for easier usage.
140

141
    """
142
    return self.stdout + self.stderr
143

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

    
146

    
147
def RunCmd(cmd, env=None, output=None, cwd='/', reset_env=False):
148
  """Execute a (shell) command.
149

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

153
  @type cmd: string or list
154
  @param cmd: Command to run
155
  @type env: dict
156
  @param env: Additional environment
157
  @type output: str
158
  @param output: if desired, the output of the command can be
159
      saved in a file instead of the RunResult instance; this
160
      parameter denotes the file name (if not None)
161
  @type cwd: string
162
  @param cwd: if specified, will be used as the working
163
      directory for the command; the default will be /
164
  @type reset_env: boolean
165
  @param reset_env: whether to reset or keep the default os environment
166
  @rtype: L{RunResult}
167
  @return: RunResult instance
168
  @raise errors.ProgrammerError: if we call this when forks are disabled
169

170
  """
171
  if no_fork:
172
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
173

    
174
  if isinstance(cmd, list):
175
    cmd = [str(val) for val in cmd]
176
    strcmd = " ".join(cmd)
177
    shell = False
178
  else:
179
    strcmd = cmd
180
    shell = True
181
  logging.debug("RunCmd '%s'", strcmd)
182

    
183
  if not reset_env:
184
    cmd_env = os.environ.copy()
185
    cmd_env["LC_ALL"] = "C"
186
  else:
187
    cmd_env = {}
188

    
189
  if env is not None:
190
    cmd_env.update(env)
191

    
192
  try:
193
    if output is None:
194
      out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
195
    else:
196
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
197
      out = err = ""
198
  except OSError, err:
199
    if err.errno == errno.ENOENT:
200
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
201
                               (strcmd, err))
202
    else:
203
      raise
204

    
205
  if status >= 0:
206
    exitcode = status
207
    signal_ = None
208
  else:
209
    exitcode = None
210
    signal_ = -status
211

    
212
  return RunResult(exitcode, signal_, out, err, strcmd)
213

    
214

    
215
def _RunCmdPipe(cmd, env, via_shell, cwd):
216
  """Run a command and return its output.
217

218
  @type  cmd: string or list
219
  @param cmd: Command to run
220
  @type env: dict
221
  @param env: The environment to use
222
  @type via_shell: bool
223
  @param via_shell: if we should run via the shell
224
  @type cwd: string
225
  @param cwd: the working directory for the program
226
  @rtype: tuple
227
  @return: (out, err, status)
228

229
  """
230
  poller = select.poll()
231
  child = subprocess.Popen(cmd, shell=via_shell,
232
                           stderr=subprocess.PIPE,
233
                           stdout=subprocess.PIPE,
234
                           stdin=subprocess.PIPE,
235
                           close_fds=True, env=env,
236
                           cwd=cwd)
237

    
238
  child.stdin.close()
239
  poller.register(child.stdout, select.POLLIN)
240
  poller.register(child.stderr, select.POLLIN)
241
  out = StringIO()
242
  err = StringIO()
243
  fdmap = {
244
    child.stdout.fileno(): (out, child.stdout),
245
    child.stderr.fileno(): (err, child.stderr),
246
    }
247
  for fd in fdmap:
248
    status = fcntl.fcntl(fd, fcntl.F_GETFL)
249
    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
250

    
251
  while fdmap:
252
    try:
253
      pollresult = poller.poll()
254
    except EnvironmentError, eerr:
255
      if eerr.errno == errno.EINTR:
256
        continue
257
      raise
258
    except select.error, serr:
259
      if serr[0] == errno.EINTR:
260
        continue
261
      raise
262

    
263
    for fd, event in pollresult:
264
      if event & select.POLLIN or event & select.POLLPRI:
265
        data = fdmap[fd][1].read()
266
        # no data from read signifies EOF (the same as POLLHUP)
267
        if not data:
268
          poller.unregister(fd)
269
          del fdmap[fd]
270
          continue
271
        fdmap[fd][0].write(data)
272
      if (event & select.POLLNVAL or event & select.POLLHUP or
273
          event & select.POLLERR):
274
        poller.unregister(fd)
275
        del fdmap[fd]
276

    
277
  out = out.getvalue()
278
  err = err.getvalue()
279

    
280
  status = child.wait()
281
  return out, err, status
282

    
283

    
284
def _RunCmdFile(cmd, env, via_shell, output, cwd):
285
  """Run a command and save its output to a file.
286

287
  @type  cmd: string or list
288
  @param cmd: Command to run
289
  @type env: dict
290
  @param env: The environment to use
291
  @type via_shell: bool
292
  @param via_shell: if we should run via the shell
293
  @type output: str
294
  @param output: the filename in which to save the output
295
  @type cwd: string
296
  @param cwd: the working directory for the program
297
  @rtype: int
298
  @return: the exit status
299

300
  """
301
  fh = open(output, "a")
302
  try:
303
    child = subprocess.Popen(cmd, shell=via_shell,
304
                             stderr=subprocess.STDOUT,
305
                             stdout=fh,
306
                             stdin=subprocess.PIPE,
307
                             close_fds=True, env=env,
308
                             cwd=cwd)
309

    
310
    child.stdin.close()
311
    status = child.wait()
312
  finally:
313
    fh.close()
314
  return status
315

    
316

    
317
def RunParts(dir_name, env=None, reset_env=False):
318
  """Run Scripts or programs in a directory
319

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

329
  """
330
  rr = []
331

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

    
338
  for relname in sorted(dir_contents):
339
    fname = PathJoin(dir_name, relname)
340
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
341
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
342
      rr.append((relname, constants.RUNPARTS_SKIP, None))
343
    else:
344
      try:
345
        result = RunCmd([fname], env=env, reset_env=reset_env)
346
      except Exception, err: # pylint: disable-msg=W0703
347
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
348
      else:
349
        rr.append((relname, constants.RUNPARTS_RUN, result))
350

    
351
  return rr
352

    
353

    
354
def GetSocketCredentials(sock):
355
  """Returns the credentials of the foreign process connected to a socket.
356

357
  @param sock: Unix socket
358
  @rtype: tuple; (number, number, number)
359
  @return: The PID, UID and GID of the connected foreign process.
360

361
  """
362
  peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
363
                             _STRUCT_UCRED_SIZE)
364
  return struct.unpack(_STRUCT_UCRED, peercred)
365

    
366

    
367
def RemoveFile(filename):
368
  """Remove a file ignoring some errors.
369

370
  Remove a file, ignoring non-existing ones or directories. Other
371
  errors are passed.
372

373
  @type filename: str
374
  @param filename: the file to be removed
375

376
  """
377
  try:
378
    os.unlink(filename)
379
  except OSError, err:
380
    if err.errno not in (errno.ENOENT, errno.EISDIR):
381
      raise
382

    
383

    
384
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
385
  """Renames a file.
386

387
  @type old: string
388
  @param old: Original path
389
  @type new: string
390
  @param new: New path
391
  @type mkdir: bool
392
  @param mkdir: Whether to create target directory if it doesn't exist
393
  @type mkdir_mode: int
394
  @param mkdir_mode: Mode for newly created directories
395

396
  """
397
  try:
398
    return os.rename(old, new)
399
  except OSError, err:
400
    # In at least one use case of this function, the job queue, directory
401
    # creation is very rare. Checking for the directory before renaming is not
402
    # as efficient.
403
    if mkdir and err.errno == errno.ENOENT:
404
      # Create directory and try again
405
      Makedirs(os.path.dirname(new), mode=mkdir_mode)
406

    
407
      return os.rename(old, new)
408

    
409
    raise
410

    
411

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

415
  This is a wrapper around C{os.makedirs} adding error handling not implemented
416
  before Python 2.5.
417

418
  """
419
  try:
420
    os.makedirs(path, mode)
421
  except OSError, err:
422
    # Ignore EEXIST. This is only handled in os.makedirs as included in
423
    # Python 2.5 and above.
424
    if err.errno != errno.EEXIST or not os.path.exists(path):
425
      raise
426

    
427

    
428
def ResetTempfileModule():
429
  """Resets the random name generator of the tempfile module.
430

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

437
  """
438
  # pylint: disable-msg=W0212
439
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
440
    tempfile._once_lock.acquire()
441
    try:
442
      # Reset random name generator
443
      tempfile._name_sequence = None
444
    finally:
445
      tempfile._once_lock.release()
446
  else:
447
    logging.critical("The tempfile module misses at least one of the"
448
                     " '_once_lock' and '_name_sequence' attributes")
449

    
450

    
451
def _FingerprintFile(filename):
452
  """Compute the fingerprint of a file.
453

454
  If the file does not exist, a None will be returned
455
  instead.
456

457
  @type filename: str
458
  @param filename: the filename to checksum
459
  @rtype: str
460
  @return: the hex digest of the sha checksum of the contents
461
      of the file
462

463
  """
464
  if not (os.path.exists(filename) and os.path.isfile(filename)):
465
    return None
466

    
467
  f = open(filename)
468

    
469
  fp = sha1()
470
  while True:
471
    data = f.read(4096)
472
    if not data:
473
      break
474

    
475
    fp.update(data)
476

    
477
  return fp.hexdigest()
478

    
479

    
480
def FingerprintFiles(files):
481
  """Compute fingerprints for a list of files.
482

483
  @type files: list
484
  @param files: the list of filename to fingerprint
485
  @rtype: dict
486
  @return: a dictionary filename: fingerprint, holding only
487
      existing files
488

489
  """
490
  ret = {}
491

    
492
  for filename in files:
493
    cksum = _FingerprintFile(filename)
494
    if cksum:
495
      ret[filename] = cksum
496

    
497
  return ret
498

    
499

    
500
def ForceDictType(target, key_types, allowed_values=None):
501
  """Force the values of a dict to have certain types.
502

503
  @type target: dict
504
  @param target: the dict to update
505
  @type key_types: dict
506
  @param key_types: dict mapping target dict keys to types
507
                    in constants.ENFORCEABLE_TYPES
508
  @type allowed_values: list
509
  @keyword allowed_values: list of specially allowed values
510

511
  """
512
  if allowed_values is None:
513
    allowed_values = []
514

    
515
  if not isinstance(target, dict):
516
    msg = "Expected dictionary, got '%s'" % target
517
    raise errors.TypeEnforcementError(msg)
518

    
519
  for key in target:
520
    if key not in key_types:
521
      msg = "Unknown key '%s'" % key
522
      raise errors.TypeEnforcementError(msg)
523

    
524
    if target[key] in allowed_values:
525
      continue
526

    
527
    ktype = key_types[key]
528
    if ktype not in constants.ENFORCEABLE_TYPES:
529
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
530
      raise errors.ProgrammerError(msg)
531

    
532
    if ktype == constants.VTYPE_STRING:
533
      if not isinstance(target[key], basestring):
534
        if isinstance(target[key], bool) and not target[key]:
535
          target[key] = ''
536
        else:
537
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
538
          raise errors.TypeEnforcementError(msg)
539
    elif ktype == constants.VTYPE_BOOL:
540
      if isinstance(target[key], basestring) and target[key]:
541
        if target[key].lower() == constants.VALUE_FALSE:
542
          target[key] = False
543
        elif target[key].lower() == constants.VALUE_TRUE:
544
          target[key] = True
545
        else:
546
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
547
          raise errors.TypeEnforcementError(msg)
548
      elif target[key]:
549
        target[key] = True
550
      else:
551
        target[key] = False
552
    elif ktype == constants.VTYPE_SIZE:
553
      try:
554
        target[key] = ParseUnit(target[key])
555
      except errors.UnitParseError, err:
556
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
557
              (key, target[key], err)
558
        raise errors.TypeEnforcementError(msg)
559
    elif ktype == constants.VTYPE_INT:
560
      try:
561
        target[key] = int(target[key])
562
      except (ValueError, TypeError):
563
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
564
        raise errors.TypeEnforcementError(msg)
565

    
566

    
567
def IsProcessAlive(pid):
568
  """Check if a given pid exists on the system.
569

570
  @note: zombie status is not handled, so zombie processes
571
      will be returned as alive
572
  @type pid: int
573
  @param pid: the process ID to check
574
  @rtype: boolean
575
  @return: True if the process exists
576

577
  """
578
  def _TryStat(name):
579
    try:
580
      os.stat(name)
581
      return True
582
    except EnvironmentError, err:
583
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
584
        return False
585
      elif err.errno == errno.EINVAL:
586
        raise RetryAgain(err)
587
      raise
588

    
589
  assert isinstance(pid, int), "pid must be an integer"
590
  if pid <= 0:
591
    return False
592

    
593
  proc_entry = "/proc/%d/status" % pid
594
  # /proc in a multiprocessor environment can have strange behaviors.
595
  # Retry the os.stat a few times until we get a good result.
596
  try:
597
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5, args=[proc_entry])
598
  except RetryTimeout, err:
599
    err.RaiseInner()
600

    
601

    
602
def ReadPidFile(pidfile):
603
  """Read a pid from a file.
604

605
  @type  pidfile: string
606
  @param pidfile: path to the file containing the pid
607
  @rtype: int
608
  @return: The process id, if the file exists and contains a valid PID,
609
           otherwise 0
610

611
  """
612
  try:
613
    raw_data = ReadOneLineFile(pidfile)
614
  except EnvironmentError, err:
615
    if err.errno != errno.ENOENT:
616
      logging.exception("Can't read pid file")
617
    return 0
618

    
619
  try:
620
    pid = int(raw_data)
621
  except (TypeError, ValueError), err:
622
    logging.info("Can't parse pid file contents", exc_info=True)
623
    return 0
624

    
625
  return pid
626

    
627

    
628
def MatchNameComponent(key, name_list, case_sensitive=True):
629
  """Try to match a name against a list.
630

631
  This function will try to match a name like test1 against a list
632
  like C{['test1.example.com', 'test2.example.com', ...]}. Against
633
  this list, I{'test1'} as well as I{'test1.example'} will match, but
634
  not I{'test1.ex'}. A multiple match will be considered as no match
635
  at all (e.g. I{'test1'} against C{['test1.example.com',
636
  'test1.example.org']}), except when the key fully matches an entry
637
  (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
638

639
  @type key: str
640
  @param key: the name to be searched
641
  @type name_list: list
642
  @param name_list: the list of strings against which to search the key
643
  @type case_sensitive: boolean
644
  @param case_sensitive: whether to provide a case-sensitive match
645

646
  @rtype: None or str
647
  @return: None if there is no match I{or} if there are multiple matches,
648
      otherwise the element from the list which matches
649

650
  """
651
  if key in name_list:
652
    return key
653

    
654
  re_flags = 0
655
  if not case_sensitive:
656
    re_flags |= re.IGNORECASE
657
    key = key.upper()
658
  mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
659
  names_filtered = []
660
  string_matches = []
661
  for name in name_list:
662
    if mo.match(name) is not None:
663
      names_filtered.append(name)
664
      if not case_sensitive and key == name.upper():
665
        string_matches.append(name)
666

    
667
  if len(string_matches) == 1:
668
    return string_matches[0]
669
  if len(names_filtered) == 1:
670
    return names_filtered[0]
671
  return None
672

    
673

    
674
class HostInfo:
675
  """Class implementing resolver and hostname functionality
676

677
  """
678
  _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
679

    
680
  def __init__(self, name=None):
681
    """Initialize the host name object.
682

683
    If the name argument is not passed, it will use this system's
684
    name.
685

686
    """
687
    if name is None:
688
      name = self.SysName()
689

    
690
    self.query = name
691
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
692
    self.ip = self.ipaddrs[0]
693

    
694
  def ShortName(self):
695
    """Returns the hostname without domain.
696

697
    """
698
    return self.name.split('.')[0]
699

    
700
  @staticmethod
701
  def SysName():
702
    """Return the current system's name.
703

704
    This is simply a wrapper over C{socket.gethostname()}.
705

706
    """
707
    return socket.gethostname()
708

    
709
  @staticmethod
710
  def LookupHostname(hostname):
711
    """Look up hostname
712

713
    @type hostname: str
714
    @param hostname: hostname to look up
715

716
    @rtype: tuple
717
    @return: a tuple (name, aliases, ipaddrs) as returned by
718
        C{socket.gethostbyname_ex}
719
    @raise errors.ResolverError: in case of errors in resolving
720

721
    """
722
    try:
723
      result = socket.gethostbyname_ex(hostname)
724
    except socket.gaierror, err:
725
      # hostname not found in DNS
726
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
727

    
728
    return result
729

    
730
  @classmethod
731
  def NormalizeName(cls, hostname):
732
    """Validate and normalize the given hostname.
733

734
    @attention: the validation is a bit more relaxed than the standards
735
        require; most importantly, we allow underscores in names
736
    @raise errors.OpPrereqError: when the name is not valid
737

738
    """
739
    hostname = hostname.lower()
740
    if (not cls._VALID_NAME_RE.match(hostname) or
741
        # double-dots, meaning empty label
742
        ".." in hostname or
743
        # empty initial label
744
        hostname.startswith(".")):
745
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
746
                                 errors.ECODE_INVAL)
747
    if hostname.endswith("."):
748
      hostname = hostname.rstrip(".")
749
    return hostname
750

    
751

    
752
def GetHostInfo(name=None):
753
  """Lookup host name and raise an OpPrereqError for failures"""
754

    
755
  try:
756
    return HostInfo(name)
757
  except errors.ResolverError, err:
758
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
759
                               (err[0], err[2]), errors.ECODE_RESOLVER)
760

    
761

    
762
def ListVolumeGroups():
763
  """List volume groups and their size
764

765
  @rtype: dict
766
  @return:
767
       Dictionary with keys volume name and values
768
       the size of the volume
769

770
  """
771
  command = "vgs --noheadings --units m --nosuffix -o name,size"
772
  result = RunCmd(command)
773
  retval = {}
774
  if result.failed:
775
    return retval
776

    
777
  for line in result.stdout.splitlines():
778
    try:
779
      name, size = line.split()
780
      size = int(float(size))
781
    except (IndexError, ValueError), err:
782
      logging.error("Invalid output from vgs (%s): %s", err, line)
783
      continue
784

    
785
    retval[name] = size
786

    
787
  return retval
788

    
789

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

793
  @type bridge: str
794
  @param bridge: the bridge name to check
795
  @rtype: boolean
796
  @return: True if it does
797

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

    
801

    
802
def NiceSort(name_list):
803
  """Sort a list of strings based on digit and non-digit groupings.
804

805
  Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
806
  will sort the list in the logical order C{['a1', 'a2', 'a10',
807
  'a11']}.
808

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

813
  @type name_list: list
814
  @param name_list: the names to be sorted
815
  @rtype: list
816
  @return: a copy of the name list sorted with our algorithm
817

818
  """
819
  _SORTER_BASE = "(\D+|\d+)"
820
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
821
                                                  _SORTER_BASE, _SORTER_BASE,
822
                                                  _SORTER_BASE, _SORTER_BASE,
823
                                                  _SORTER_BASE, _SORTER_BASE)
824
  _SORTER_RE = re.compile(_SORTER_FULL)
825
  _SORTER_NODIGIT = re.compile("^\D*$")
826
  def _TryInt(val):
827
    """Attempts to convert a variable to integer."""
828
    if val is None or _SORTER_NODIGIT.match(val):
829
      return val
830
    rval = int(val)
831
    return rval
832

    
833
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
834
             for name in name_list]
835
  to_sort.sort()
836
  return [tup[1] for tup in to_sort]
837

    
838

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

842
  This function tries to apply function I{fn} to I{val}. If no
843
  C{ValueError} or C{TypeError} exceptions are raised, it will return
844
  the result, else it will return the original value. Any other
845
  exceptions are propagated to the caller.
846

847
  @type fn: callable
848
  @param fn: function to apply to the value
849
  @param val: the value to be converted
850
  @return: The converted value if the conversion was successful,
851
      otherwise the original value.
852

853
  """
854
  try:
855
    nv = fn(val)
856
  except (ValueError, TypeError):
857
    nv = val
858
  return nv
859

    
860

    
861
def IsValidIP(ip):
862
  """Verifies the syntax of an IPv4 address.
863

864
  This function checks if the IPv4 address passes is valid or not based
865
  on syntax (not IP range, class calculations, etc.).
866

867
  @type ip: str
868
  @param ip: the address to be checked
869
  @rtype: a regular expression match object
870
  @return: a regular expression match object, or None if the
871
      address is not valid
872

873
  """
874
  unit = "(0|[1-9]\d{0,2})"
875
  #TODO: convert and return only boolean
876
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
877

    
878

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

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

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

889
  @type word: str
890
  @param word: the word to check
891
  @rtype: boolean
892
  @return: True if the word is 'safe'
893

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

    
897

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

901
  This function will check all arguments in the args list so that they
902
  are valid shell parameters (i.e. they don't contain shell
903
  metacharacters). If everything is ok, it will return the result of
904
  template % args.
905

906
  @type template: str
907
  @param template: the string holding the template for the
908
      string formatting
909
  @rtype: str
910
  @return: the expanded command line
911

912
  """
913
  for word in args:
914
    if not IsValidShellParam(word):
915
      raise errors.ProgrammerError("Shell argument '%s' contains"
916
                                   " invalid characters" % word)
917
  return template % args
918

    
919

    
920
def FormatUnit(value, units):
921
  """Formats an incoming number of MiB with the appropriate unit.
922

923
  @type value: int
924
  @param value: integer representing the value in MiB (1048576)
925
  @type units: char
926
  @param units: the type of formatting we should do:
927
      - 'h' for automatic scaling
928
      - 'm' for MiBs
929
      - 'g' for GiBs
930
      - 't' for TiBs
931
  @rtype: str
932
  @return: the formatted value (with suffix)
933

934
  """
935
  if units not in ('m', 'g', 't', 'h'):
936
    raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
937

    
938
  suffix = ''
939

    
940
  if units == 'm' or (units == 'h' and value < 1024):
941
    if units == 'h':
942
      suffix = 'M'
943
    return "%d%s" % (round(value, 0), suffix)
944

    
945
  elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
946
    if units == 'h':
947
      suffix = 'G'
948
    return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
949

    
950
  else:
951
    if units == 'h':
952
      suffix = 'T'
953
    return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
954

    
955

    
956
def ParseUnit(input_string):
957
  """Tries to extract number and scale from the given string.
958

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

963
  """
964
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
965
  if not m:
966
    raise errors.UnitParseError("Invalid format")
967

    
968
  value = float(m.groups()[0])
969

    
970
  unit = m.groups()[1]
971
  if unit:
972
    lcunit = unit.lower()
973
  else:
974
    lcunit = 'm'
975

    
976
  if lcunit in ('m', 'mb', 'mib'):
977
    # Value already in MiB
978
    pass
979

    
980
  elif lcunit in ('g', 'gb', 'gib'):
981
    value *= 1024
982

    
983
  elif lcunit in ('t', 'tb', 'tib'):
984
    value *= 1024 * 1024
985

    
986
  else:
987
    raise errors.UnitParseError("Unknown unit: %s" % unit)
988

    
989
  # Make sure we round up
990
  if int(value) < value:
991
    value += 1
992

    
993
  # Round up to the next multiple of 4
994
  value = int(value)
995
  if value % 4:
996
    value += 4 - value % 4
997

    
998
  return value
999

    
1000

    
1001
def AddAuthorizedKey(file_name, key):
1002
  """Adds an SSH public key to an authorized_keys file.
1003

1004
  @type file_name: str
1005
  @param file_name: path to authorized_keys file
1006
  @type key: str
1007
  @param key: string containing key
1008

1009
  """
1010
  key_fields = key.split()
1011

    
1012
  f = open(file_name, 'a+')
1013
  try:
1014
    nl = True
1015
    for line in f:
1016
      # Ignore whitespace changes
1017
      if line.split() == key_fields:
1018
        break
1019
      nl = line.endswith('\n')
1020
    else:
1021
      if not nl:
1022
        f.write("\n")
1023
      f.write(key.rstrip('\r\n'))
1024
      f.write("\n")
1025
      f.flush()
1026
  finally:
1027
    f.close()
1028

    
1029

    
1030
def RemoveAuthorizedKey(file_name, key):
1031
  """Removes an SSH public key from an authorized_keys file.
1032

1033
  @type file_name: str
1034
  @param file_name: path to authorized_keys file
1035
  @type key: str
1036
  @param key: string containing key
1037

1038
  """
1039
  key_fields = key.split()
1040

    
1041
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1042
  try:
1043
    out = os.fdopen(fd, 'w')
1044
    try:
1045
      f = open(file_name, 'r')
1046
      try:
1047
        for line in f:
1048
          # Ignore whitespace changes while comparing lines
1049
          if line.split() != key_fields:
1050
            out.write(line)
1051

    
1052
        out.flush()
1053
        os.rename(tmpname, file_name)
1054
      finally:
1055
        f.close()
1056
    finally:
1057
      out.close()
1058
  except:
1059
    RemoveFile(tmpname)
1060
    raise
1061

    
1062

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

1066
  @type file_name: str
1067
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1068
  @type ip: str
1069
  @param ip: the IP address
1070
  @type hostname: str
1071
  @param hostname: the hostname to be added
1072
  @type aliases: list
1073
  @param aliases: the list of aliases to add for the hostname
1074

1075
  """
1076
  # FIXME: use WriteFile + fn rather than duplicating its efforts
1077
  # Ensure aliases are unique
1078
  aliases = UniqueSequence([hostname] + aliases)[1:]
1079

    
1080
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1081
  try:
1082
    out = os.fdopen(fd, 'w')
1083
    try:
1084
      f = open(file_name, 'r')
1085
      try:
1086
        for line in f:
1087
          fields = line.split()
1088
          if fields and not fields[0].startswith('#') and ip == fields[0]:
1089
            continue
1090
          out.write(line)
1091

    
1092
        out.write("%s\t%s" % (ip, hostname))
1093
        if aliases:
1094
          out.write(" %s" % ' '.join(aliases))
1095
        out.write('\n')
1096

    
1097
        out.flush()
1098
        os.fsync(out)
1099
        os.chmod(tmpname, 0644)
1100
        os.rename(tmpname, file_name)
1101
      finally:
1102
        f.close()
1103
    finally:
1104
      out.close()
1105
  except:
1106
    RemoveFile(tmpname)
1107
    raise
1108

    
1109

    
1110
def AddHostToEtcHosts(hostname):
1111
  """Wrapper around SetEtcHostsEntry.
1112

1113
  @type hostname: str
1114
  @param hostname: a hostname that will be resolved and added to
1115
      L{constants.ETC_HOSTS}
1116

1117
  """
1118
  hi = HostInfo(name=hostname)
1119
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1120

    
1121

    
1122
def RemoveEtcHostsEntry(file_name, hostname):
1123
  """Removes a hostname from /etc/hosts.
1124

1125
  IP addresses without names are removed from the file.
1126

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

1132
  """
1133
  # FIXME: use WriteFile + fn rather than duplicating its efforts
1134
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1135
  try:
1136
    out = os.fdopen(fd, 'w')
1137
    try:
1138
      f = open(file_name, 'r')
1139
      try:
1140
        for line in f:
1141
          fields = line.split()
1142
          if len(fields) > 1 and not fields[0].startswith('#'):
1143
            names = fields[1:]
1144
            if hostname in names:
1145
              while hostname in names:
1146
                names.remove(hostname)
1147
              if names:
1148
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
1149
              continue
1150

    
1151
          out.write(line)
1152

    
1153
        out.flush()
1154
        os.fsync(out)
1155
        os.chmod(tmpname, 0644)
1156
        os.rename(tmpname, file_name)
1157
      finally:
1158
        f.close()
1159
    finally:
1160
      out.close()
1161
  except:
1162
    RemoveFile(tmpname)
1163
    raise
1164

    
1165

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

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

1174
  """
1175
  hi = HostInfo(name=hostname)
1176
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1177
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1178

    
1179

    
1180
def TimestampForFilename():
1181
  """Returns the current time formatted for filenames.
1182

1183
  The format doesn't contain colons as some shells and applications them as
1184
  separators.
1185

1186
  """
1187
  return time.strftime("%Y-%m-%d_%H_%M_%S")
1188

    
1189

    
1190
def CreateBackup(file_name):
1191
  """Creates a backup of a file.
1192

1193
  @type file_name: str
1194
  @param file_name: file to be backed up
1195
  @rtype: str
1196
  @return: the path to the newly created backup
1197
  @raise errors.ProgrammerError: for invalid file names
1198

1199
  """
1200
  if not os.path.isfile(file_name):
1201
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1202
                                file_name)
1203

    
1204
  prefix = ("%s.backup-%s." %
1205
            (os.path.basename(file_name), TimestampForFilename()))
1206
  dir_name = os.path.dirname(file_name)
1207

    
1208
  fsrc = open(file_name, 'rb')
1209
  try:
1210
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1211
    fdst = os.fdopen(fd, 'wb')
1212
    try:
1213
      logging.debug("Backing up %s at %s", file_name, backup_name)
1214
      shutil.copyfileobj(fsrc, fdst)
1215
    finally:
1216
      fdst.close()
1217
  finally:
1218
    fsrc.close()
1219

    
1220
  return backup_name
1221

    
1222

    
1223
def ShellQuote(value):
1224
  """Quotes shell argument according to POSIX.
1225

1226
  @type value: str
1227
  @param value: the argument to be quoted
1228
  @rtype: str
1229
  @return: the quoted value
1230

1231
  """
1232
  if _re_shell_unquoted.match(value):
1233
    return value
1234
  else:
1235
    return "'%s'" % value.replace("'", "'\\''")
1236

    
1237

    
1238
def ShellQuoteArgs(args):
1239
  """Quotes a list of shell arguments.
1240

1241
  @type args: list
1242
  @param args: list of arguments to be quoted
1243
  @rtype: str
1244
  @return: the quoted arguments concatenated with spaces
1245

1246
  """
1247
  return ' '.join([ShellQuote(i) for i in args])
1248

    
1249

    
1250
def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1251
  """Simple ping implementation using TCP connect(2).
1252

1253
  Check if the given IP is reachable by doing attempting a TCP connect
1254
  to it.
1255

1256
  @type target: str
1257
  @param target: the IP or hostname to ping
1258
  @type port: int
1259
  @param port: the port to connect to
1260
  @type timeout: int
1261
  @param timeout: the timeout on the connection attempt
1262
  @type live_port_needed: boolean
1263
  @param live_port_needed: whether a closed port will cause the
1264
      function to return failure, as if there was a timeout
1265
  @type source: str or None
1266
  @param source: if specified, will cause the connect to be made
1267
      from this specific source address; failures to bind other
1268
      than C{EADDRNOTAVAIL} will be ignored
1269

1270
  """
1271
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1272

    
1273
  success = False
1274

    
1275
  if source is not None:
1276
    try:
1277
      sock.bind((source, 0))
1278
    except socket.error, (errcode, _):
1279
      if errcode == errno.EADDRNOTAVAIL:
1280
        success = False
1281

    
1282
  sock.settimeout(timeout)
1283

    
1284
  try:
1285
    sock.connect((target, port))
1286
    sock.close()
1287
    success = True
1288
  except socket.timeout:
1289
    success = False
1290
  except socket.error, (errcode, _):
1291
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1292

    
1293
  return success
1294

    
1295

    
1296
def OwnIpAddress(address):
1297
  """Check if the current host has the the given IP address.
1298

1299
  Currently this is done by TCP-pinging the address from the loopback
1300
  address.
1301

1302
  @type address: string
1303
  @param address: the address to check
1304
  @rtype: bool
1305
  @return: True if we own the address
1306

1307
  """
1308
  return TcpPing(address, constants.DEFAULT_NODED_PORT,
1309
                 source=constants.LOCALHOST_IP_ADDRESS)
1310

    
1311

    
1312
def ListVisibleFiles(path):
1313
  """Returns a list of visible files in a directory.
1314

1315
  @type path: str
1316
  @param path: the directory to enumerate
1317
  @rtype: list
1318
  @return: the list of all files not starting with a dot
1319
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
1320

1321
  """
1322
  if not IsNormAbsPath(path):
1323
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1324
                                 " absolute/normalized: '%s'" % path)
1325
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1326
  files.sort()
1327
  return files
1328

    
1329

    
1330
def GetHomeDir(user, default=None):
1331
  """Try to get the homedir of the given user.
1332

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

1337
  """
1338
  try:
1339
    if isinstance(user, basestring):
1340
      result = pwd.getpwnam(user)
1341
    elif isinstance(user, (int, long)):
1342
      result = pwd.getpwuid(user)
1343
    else:
1344
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1345
                                   type(user))
1346
  except KeyError:
1347
    return default
1348
  return result.pw_dir
1349

    
1350

    
1351
def NewUUID():
1352
  """Returns a random UUID.
1353

1354
  @note: This is a Linux-specific method as it uses the /proc
1355
      filesystem.
1356
  @rtype: str
1357

1358
  """
1359
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1360

    
1361

    
1362
def GenerateSecret(numbytes=20):
1363
  """Generates a random secret.
1364

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

1368
  @param numbytes: the number of bytes which will be represented by the returned
1369
      string (defaulting to 20, the length of a SHA1 hash)
1370
  @rtype: str
1371
  @return: an hex representation of the pseudo-random sequence
1372

1373
  """
1374
  return os.urandom(numbytes).encode('hex')
1375

    
1376

    
1377
def EnsureDirs(dirs):
1378
  """Make required directories, if they don't exist.
1379

1380
  @param dirs: list of tuples (dir_name, dir_mode)
1381
  @type dirs: list of (string, integer)
1382

1383
  """
1384
  for dir_name, dir_mode in dirs:
1385
    try:
1386
      os.mkdir(dir_name, dir_mode)
1387
    except EnvironmentError, err:
1388
      if err.errno != errno.EEXIST:
1389
        raise errors.GenericError("Cannot create needed directory"
1390
                                  " '%s': %s" % (dir_name, err))
1391
    if not os.path.isdir(dir_name):
1392
      raise errors.GenericError("%s is not a directory" % dir_name)
1393

    
1394

    
1395
def ReadFile(file_name, size=-1):
1396
  """Reads a file.
1397

1398
  @type size: int
1399
  @param size: Read at most size bytes (if negative, entire file)
1400
  @rtype: str
1401
  @return: the (possibly partial) content of the file
1402

1403
  """
1404
  f = open(file_name, "r")
1405
  try:
1406
    return f.read(size)
1407
  finally:
1408
    f.close()
1409

    
1410

    
1411
def WriteFile(file_name, fn=None, data=None,
1412
              mode=None, uid=-1, gid=-1,
1413
              atime=None, mtime=None, close=True,
1414
              dry_run=False, backup=False,
1415
              prewrite=None, postwrite=None):
1416
  """(Over)write a file atomically.
1417

1418
  The file_name and either fn (a function taking one argument, the
1419
  file descriptor, and which should write the data to it) or data (the
1420
  contents of the file) must be passed. The other arguments are
1421
  optional and allow setting the file mode, owner and group, and the
1422
  mtime/atime of the file.
1423

1424
  If the function doesn't raise an exception, it has succeeded and the
1425
  target file has the new contents. If the function has raised an
1426
  exception, an existing target file should be unmodified and the
1427
  temporary file should be removed.
1428

1429
  @type file_name: str
1430
  @param file_name: the target filename
1431
  @type fn: callable
1432
  @param fn: content writing function, called with
1433
      file descriptor as parameter
1434
  @type data: str
1435
  @param data: contents of the file
1436
  @type mode: int
1437
  @param mode: file mode
1438
  @type uid: int
1439
  @param uid: the owner of the file
1440
  @type gid: int
1441
  @param gid: the group of the file
1442
  @type atime: int
1443
  @param atime: a custom access time to be set on the file
1444
  @type mtime: int
1445
  @param mtime: a custom modification time to be set on the file
1446
  @type close: boolean
1447
  @param close: whether to close file after writing it
1448
  @type prewrite: callable
1449
  @param prewrite: function to be called before writing content
1450
  @type postwrite: callable
1451
  @param postwrite: function to be called after writing content
1452

1453
  @rtype: None or int
1454
  @return: None if the 'close' parameter evaluates to True,
1455
      otherwise the file descriptor
1456

1457
  @raise errors.ProgrammerError: if any of the arguments are not valid
1458

1459
  """
1460
  if not os.path.isabs(file_name):
1461
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1462
                                 " absolute: '%s'" % file_name)
1463

    
1464
  if [fn, data].count(None) != 1:
1465
    raise errors.ProgrammerError("fn or data required")
1466

    
1467
  if [atime, mtime].count(None) == 1:
1468
    raise errors.ProgrammerError("Both atime and mtime must be either"
1469
                                 " set or None")
1470

    
1471
  if backup and not dry_run and os.path.isfile(file_name):
1472
    CreateBackup(file_name)
1473

    
1474
  dir_name, base_name = os.path.split(file_name)
1475
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1476
  do_remove = True
1477
  # here we need to make sure we remove the temp file, if any error
1478
  # leaves it in place
1479
  try:
1480
    if uid != -1 or gid != -1:
1481
      os.chown(new_name, uid, gid)
1482
    if mode:
1483
      os.chmod(new_name, mode)
1484
    if callable(prewrite):
1485
      prewrite(fd)
1486
    if data is not None:
1487
      os.write(fd, data)
1488
    else:
1489
      fn(fd)
1490
    if callable(postwrite):
1491
      postwrite(fd)
1492
    os.fsync(fd)
1493
    if atime is not None and mtime is not None:
1494
      os.utime(new_name, (atime, mtime))
1495
    if not dry_run:
1496
      os.rename(new_name, file_name)
1497
      do_remove = False
1498
  finally:
1499
    if close:
1500
      os.close(fd)
1501
      result = None
1502
    else:
1503
      result = fd
1504
    if do_remove:
1505
      RemoveFile(new_name)
1506

    
1507
  return result
1508

    
1509

    
1510
def ReadOneLineFile(file_name, strict=False):
1511
  """Return the first non-empty line from a file.
1512

1513
  @type strict: boolean
1514
  @param strict: if True, abort if the file has more than one
1515
      non-empty line
1516

1517
  """
1518
  file_lines = ReadFile(file_name).splitlines()
1519
  full_lines = filter(bool, file_lines)
1520
  if not file_lines or not full_lines:
1521
    raise errors.GenericError("No data in one-liner file %s" % file_name)
1522
  elif strict and len(full_lines) > 1:
1523
    raise errors.GenericError("Too many lines in one-liner file %s" %
1524
                              file_name)
1525
  return full_lines[0]
1526

    
1527

    
1528
def FirstFree(seq, base=0):
1529
  """Returns the first non-existing integer from seq.
1530

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

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

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

1540
  @type seq: sequence
1541
  @param seq: the sequence to be analyzed.
1542
  @type base: int
1543
  @param base: use this value as the base index of the sequence
1544
  @rtype: int
1545
  @return: the first non-used index in the sequence
1546

1547
  """
1548
  for idx, elem in enumerate(seq):
1549
    assert elem >= base, "Passed element is higher than base offset"
1550
    if elem > idx + base:
1551
      # idx is not used
1552
      return idx + base
1553
  return None
1554

    
1555

    
1556
def SingleWaitForFdCondition(fdobj, event, timeout):
1557
  """Waits for a condition to occur on the socket.
1558

1559
  Immediately returns at the first interruption.
1560

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

1570
  """
1571
  check = (event | select.POLLPRI |
1572
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
1573

    
1574
  if timeout is not None:
1575
    # Poller object expects milliseconds
1576
    timeout *= 1000
1577

    
1578
  poller = select.poll()
1579
  poller.register(fdobj, event)
1580
  try:
1581
    # TODO: If the main thread receives a signal and we have no timeout, we
1582
    # could wait forever. This should check a global "quit" flag or something
1583
    # every so often.
1584
    io_events = poller.poll(timeout)
1585
  except select.error, err:
1586
    if err[0] != errno.EINTR:
1587
      raise
1588
    io_events = []
1589
  if io_events and io_events[0][1] & check:
1590
    return io_events[0][1]
1591
  else:
1592
    return None
1593

    
1594

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

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

1602
  """
1603

    
1604
  def __init__(self, timeout):
1605
    self.timeout = timeout
1606

    
1607
  def Poll(self, fdobj, event):
1608
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1609
    if result is None:
1610
      raise RetryAgain()
1611
    else:
1612
      return result
1613

    
1614
  def UpdateTimeout(self, timeout):
1615
    self.timeout = timeout
1616

    
1617

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

1621
  Retries until the timeout is expired, even if interrupted.
1622

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

1632
  """
1633
  if timeout is not None:
1634
    retrywaiter = FdConditionWaiterHelper(timeout)
1635
    try:
1636
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1637
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1638
    except RetryTimeout:
1639
      result = None
1640
  else:
1641
    result = None
1642
    while result is None:
1643
      result = SingleWaitForFdCondition(fdobj, event, timeout)
1644
  return result
1645

    
1646

    
1647
def UniqueSequence(seq):
1648
  """Returns a list with unique elements.
1649

1650
  Element order is preserved.
1651

1652
  @type seq: sequence
1653
  @param seq: the sequence with the source elements
1654
  @rtype: list
1655
  @return: list of unique elements from seq
1656

1657
  """
1658
  seen = set()
1659
  return [i for i in seq if i not in seen and not seen.add(i)]
1660

    
1661

    
1662
def NormalizeAndValidateMac(mac):
1663
  """Normalizes and check if a MAC address is valid.
1664

1665
  Checks whether the supplied MAC address is formally correct, only
1666
  accepts colon separated format. Normalize it to all lower.
1667

1668
  @type mac: str
1669
  @param mac: the MAC to be validated
1670
  @rtype: str
1671
  @return: returns the normalized and validated MAC.
1672

1673
  @raise errors.OpPrereqError: If the MAC isn't valid
1674

1675
  """
1676
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1677
  if not mac_check.match(mac):
1678
    raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1679
                               mac, errors.ECODE_INVAL)
1680

    
1681
  return mac.lower()
1682

    
1683

    
1684
def TestDelay(duration):
1685
  """Sleep for a fixed amount of time.
1686

1687
  @type duration: float
1688
  @param duration: the sleep duration
1689
  @rtype: boolean
1690
  @return: False for negative value, True otherwise
1691

1692
  """
1693
  if duration < 0:
1694
    return False, "Invalid sleep duration"
1695
  time.sleep(duration)
1696
  return True, None
1697

    
1698

    
1699
def _CloseFDNoErr(fd, retries=5):
1700
  """Close a file descriptor ignoring errors.
1701

1702
  @type fd: int
1703
  @param fd: the file descriptor
1704
  @type retries: int
1705
  @param retries: how many retries to make, in case we get any
1706
      other error than EBADF
1707

1708
  """
1709
  try:
1710
    os.close(fd)
1711
  except OSError, err:
1712
    if err.errno != errno.EBADF:
1713
      if retries > 0:
1714
        _CloseFDNoErr(fd, retries - 1)
1715
    # else either it's closed already or we're out of retries, so we
1716
    # ignore this and go on
1717

    
1718

    
1719
def CloseFDs(noclose_fds=None):
1720
  """Close file descriptors.
1721

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

1725
  @type noclose_fds: list or None
1726
  @param noclose_fds: if given, it denotes a list of file descriptor
1727
      that should not be closed
1728

1729
  """
1730
  # Default maximum for the number of available file descriptors.
1731
  if 'SC_OPEN_MAX' in os.sysconf_names:
1732
    try:
1733
      MAXFD = os.sysconf('SC_OPEN_MAX')
1734
      if MAXFD < 0:
1735
        MAXFD = 1024
1736
    except OSError:
1737
      MAXFD = 1024
1738
  else:
1739
    MAXFD = 1024
1740
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1741
  if (maxfd == resource.RLIM_INFINITY):
1742
    maxfd = MAXFD
1743

    
1744
  # Iterate through and close all file descriptors (except the standard ones)
1745
  for fd in range(3, maxfd):
1746
    if noclose_fds and fd in noclose_fds:
1747
      continue
1748
    _CloseFDNoErr(fd)
1749

    
1750

    
1751
def Mlockall():
1752
  """Lock current process' virtual address space into RAM.
1753

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

1757
  """
1758
  if ctypes is None:
1759
    logging.warning("Cannot set memory lock, ctypes module not found")
1760
    return
1761

    
1762
  libc = ctypes.cdll.LoadLibrary("libc.so.6")
1763
  if libc is None:
1764
    logging.error("Cannot set memory lock, ctypes cannot load libc")
1765
    return
1766

    
1767
  # Some older version of the ctypes module don't have built-in functionality
1768
  # to access the errno global variable, where function error codes are stored.
1769
  # By declaring this variable as a pointer to an integer we can then access
1770
  # its value correctly, should the mlockall call fail, in order to see what
1771
  # the actual error code was.
1772
  # pylint: disable-msg=W0212
1773
  libc.__errno_location.restype = ctypes.POINTER(ctypes.c_int)
1774

    
1775
  if libc.mlockall(_MCL_CURRENT | _MCL_FUTURE):
1776
    # pylint: disable-msg=W0212
1777
    logging.error("Cannot set memory lock: %s",
1778
                  os.strerror(libc.__errno_location().contents.value))
1779
    return
1780

    
1781
  logging.debug("Memory lock set")
1782

    
1783

    
1784
def Daemonize(logfile):
1785
  """Daemonize the current process.
1786

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

1790
  @type logfile: str
1791
  @param logfile: the logfile to which we should redirect stdout/stderr
1792
  @rtype: int
1793
  @return: the value zero
1794

1795
  """
1796
  # pylint: disable-msg=W0212
1797
  # yes, we really want os._exit
1798
  UMASK = 077
1799
  WORKDIR = "/"
1800

    
1801
  # this might fail
1802
  pid = os.fork()
1803
  if (pid == 0):  # The first child.
1804
    os.setsid()
1805
    # this might fail
1806
    pid = os.fork() # Fork a second child.
1807
    if (pid == 0):  # The second child.
1808
      os.chdir(WORKDIR)
1809
      os.umask(UMASK)
1810
    else:
1811
      # exit() or _exit()?  See below.
1812
      os._exit(0) # Exit parent (the first child) of the second child.
1813
  else:
1814
    os._exit(0) # Exit parent of the first child.
1815

    
1816
  for fd in range(3):
1817
    _CloseFDNoErr(fd)
1818
  i = os.open("/dev/null", os.O_RDONLY) # stdin
1819
  assert i == 0, "Can't close/reopen stdin"
1820
  i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1821
  assert i == 1, "Can't close/reopen stdout"
1822
  # Duplicate standard output to standard error.
1823
  os.dup2(1, 2)
1824
  return 0
1825

    
1826

    
1827
def DaemonPidFileName(name):
1828
  """Compute a ganeti pid file absolute path
1829

1830
  @type name: str
1831
  @param name: the daemon name
1832
  @rtype: str
1833
  @return: the full path to the pidfile corresponding to the given
1834
      daemon name
1835

1836
  """
1837
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
1838

    
1839

    
1840
def EnsureDaemon(name):
1841
  """Check for and start daemon if not alive.
1842

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

    
1850
  return True
1851

    
1852

    
1853
def WritePidFile(name):
1854
  """Write the current process pidfile.
1855

1856
  The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1857

1858
  @type name: str
1859
  @param name: the daemon name to use
1860
  @raise errors.GenericError: if the pid file already exists and
1861
      points to a live process
1862

1863
  """
1864
  pid = os.getpid()
1865
  pidfilename = DaemonPidFileName(name)
1866
  if IsProcessAlive(ReadPidFile(pidfilename)):
1867
    raise errors.GenericError("%s contains a live process" % pidfilename)
1868

    
1869
  WriteFile(pidfilename, data="%d\n" % pid)
1870

    
1871

    
1872
def RemovePidFile(name):
1873
  """Remove the current process pidfile.
1874

1875
  Any errors are ignored.
1876

1877
  @type name: str
1878
  @param name: the daemon name used to derive the pidfile name
1879

1880
  """
1881
  pidfilename = DaemonPidFileName(name)
1882
  # TODO: we could check here that the file contains our pid
1883
  try:
1884
    RemoveFile(pidfilename)
1885
  except: # pylint: disable-msg=W0702
1886
    pass
1887

    
1888

    
1889
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1890
                waitpid=False):
1891
  """Kill a process given by its pid.
1892

1893
  @type pid: int
1894
  @param pid: The PID to terminate.
1895
  @type signal_: int
1896
  @param signal_: The signal to send, by default SIGTERM
1897
  @type timeout: int
1898
  @param timeout: The timeout after which, if the process is still alive,
1899
                  a SIGKILL will be sent. If not positive, no such checking
1900
                  will be done
1901
  @type waitpid: boolean
1902
  @param waitpid: If true, we should waitpid on this process after
1903
      sending signals, since it's our own child and otherwise it
1904
      would remain as zombie
1905

1906
  """
1907
  def _helper(pid, signal_, wait):
1908
    """Simple helper to encapsulate the kill/waitpid sequence"""
1909
    os.kill(pid, signal_)
1910
    if wait:
1911
      try:
1912
        os.waitpid(pid, os.WNOHANG)
1913
      except OSError:
1914
        pass
1915

    
1916
  if pid <= 0:
1917
    # kill with pid=0 == suicide
1918
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1919

    
1920
  if not IsProcessAlive(pid):
1921
    return
1922

    
1923
  _helper(pid, signal_, waitpid)
1924

    
1925
  if timeout <= 0:
1926
    return
1927

    
1928
  def _CheckProcess():
1929
    if not IsProcessAlive(pid):
1930
      return
1931

    
1932
    try:
1933
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1934
    except OSError:
1935
      raise RetryAgain()
1936

    
1937
    if result_pid > 0:
1938
      return
1939

    
1940
    raise RetryAgain()
1941

    
1942
  try:
1943
    # Wait up to $timeout seconds
1944
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1945
  except RetryTimeout:
1946
    pass
1947

    
1948
  if IsProcessAlive(pid):
1949
    # Kill process if it's still alive
1950
    _helper(pid, signal.SIGKILL, waitpid)
1951

    
1952

    
1953
def FindFile(name, search_path, test=os.path.exists):
1954
  """Look for a filesystem object in a given path.
1955

1956
  This is an abstract method to search for filesystem object (files,
1957
  dirs) under a given search path.
1958

1959
  @type name: str
1960
  @param name: the name to look for
1961
  @type search_path: str
1962
  @param search_path: location to start at
1963
  @type test: callable
1964
  @param test: a function taking one argument that should return True
1965
      if the a given object is valid; the default value is
1966
      os.path.exists, causing only existing files to be returned
1967
  @rtype: str or None
1968
  @return: full path to the object if found, None otherwise
1969

1970
  """
1971
  # validate the filename mask
1972
  if constants.EXT_PLUGIN_MASK.match(name) is None:
1973
    logging.critical("Invalid value passed for external script name: '%s'",
1974
                     name)
1975
    return None
1976

    
1977
  for dir_name in search_path:
1978
    # FIXME: investigate switch to PathJoin
1979
    item_name = os.path.sep.join([dir_name, name])
1980
    # check the user test and that we're indeed resolving to the given
1981
    # basename
1982
    if test(item_name) and os.path.basename(item_name) == name:
1983
      return item_name
1984
  return None
1985

    
1986

    
1987
def CheckVolumeGroupSize(vglist, vgname, minsize):
1988
  """Checks if the volume group list is valid.
1989

1990
  The function will check if a given volume group is in the list of
1991
  volume groups and has a minimum size.
1992

1993
  @type vglist: dict
1994
  @param vglist: dictionary of volume group names and their size
1995
  @type vgname: str
1996
  @param vgname: the volume group we should check
1997
  @type minsize: int
1998
  @param minsize: the minimum size we accept
1999
  @rtype: None or str
2000
  @return: None for success, otherwise the error message
2001

2002
  """
2003
  vgsize = vglist.get(vgname, None)
2004
  if vgsize is None:
2005
    return "volume group '%s' missing" % vgname
2006
  elif vgsize < minsize:
2007
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
2008
            (vgname, minsize, vgsize))
2009
  return None
2010

    
2011

    
2012
def SplitTime(value):
2013
  """Splits time as floating point number into a tuple.
2014

2015
  @param value: Time in seconds
2016
  @type value: int or float
2017
  @return: Tuple containing (seconds, microseconds)
2018

2019
  """
2020
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
2021

    
2022
  assert 0 <= seconds, \
2023
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2024
  assert 0 <= microseconds <= 999999, \
2025
    "Microseconds must be 0-999999, but are %s" % microseconds
2026

    
2027
  return (int(seconds), int(microseconds))
2028

    
2029

    
2030
def MergeTime(timetuple):
2031
  """Merges a tuple into time as a floating point number.
2032

2033
  @param timetuple: Time as tuple, (seconds, microseconds)
2034
  @type timetuple: tuple
2035
  @return: Time as a floating point number expressed in seconds
2036

2037
  """
2038
  (seconds, microseconds) = timetuple
2039

    
2040
  assert 0 <= seconds, \
2041
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2042
  assert 0 <= microseconds <= 999999, \
2043
    "Microseconds must be 0-999999, but are %s" % microseconds
2044

    
2045
  return float(seconds) + (float(microseconds) * 0.000001)
2046

    
2047

    
2048
def GetDaemonPort(daemon_name):
2049
  """Get the daemon port for this cluster.
2050

2051
  Note that this routine does not read a ganeti-specific file, but
2052
  instead uses C{socket.getservbyname} to allow pre-customization of
2053
  this parameter outside of Ganeti.
2054

2055
  @type daemon_name: string
2056
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
2057
  @rtype: int
2058

2059
  """
2060
  if daemon_name not in constants.DAEMONS_PORTS:
2061
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
2062

    
2063
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
2064
  try:
2065
    port = socket.getservbyname(daemon_name, proto)
2066
  except socket.error:
2067
    port = default_port
2068

    
2069
  return port
2070

    
2071

    
2072
class LogFileHandler(logging.FileHandler):
2073
  """Log handler that doesn't fallback to stderr.
2074

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

2079
  """
2080
  def __init__(self, filename, mode="a", encoding=None):
2081
    """Open the specified file and use it as the stream for logging.
2082

2083
    Also open /dev/console to report errors while logging.
2084

2085
    """
2086
    logging.FileHandler.__init__(self, filename, mode, encoding)
2087
    self.console = open(constants.DEV_CONSOLE, "a")
2088

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

2092
    Try to handle errors with FileHandler method, if it fails write to
2093
    /dev/console.
2094

2095
    """
2096
    try:
2097
      logging.FileHandler.handleError(self, record)
2098
    except Exception: # pylint: disable-msg=W0703
2099
      try:
2100
        self.console.write("Cannot log message:\n%s\n" % self.format(record))
2101
      except Exception: # pylint: disable-msg=W0703
2102
        # Log handler tried everything it could, now just give up
2103
        pass
2104

    
2105

    
2106
def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
2107
                 multithreaded=False, syslog=constants.SYSLOG_USAGE,
2108
                 console_logging=False):
2109
  """Configures the logging module.
2110

2111
  @type logfile: str
2112
  @param logfile: the filename to which we should log
2113
  @type debug: integer
2114
  @param debug: if greater than zero, enable debug messages, otherwise
2115
      only those at C{INFO} and above level
2116
  @type stderr_logging: boolean
2117
  @param stderr_logging: whether we should also log to the standard error
2118
  @type program: str
2119
  @param program: the name under which we should log messages
2120
  @type multithreaded: boolean
2121
  @param multithreaded: if True, will add the thread name to the log file
2122
  @type syslog: string
2123
  @param syslog: one of 'no', 'yes', 'only':
2124
      - if no, syslog is not used
2125
      - if yes, syslog is used (in addition to file-logging)
2126
      - if only, only syslog is used
2127
  @type console_logging: boolean
2128
  @param console_logging: if True, will use a FileHandler which falls back to
2129
      the system console if logging fails
2130
  @raise EnvironmentError: if we can't open the log file and
2131
      syslog/stderr logging is disabled
2132

2133
  """
2134
  fmt = "%(asctime)s: " + program + " pid=%(process)d"
2135
  sft = program + "[%(process)d]:"
2136
  if multithreaded:
2137
    fmt += "/%(threadName)s"
2138
    sft += " (%(threadName)s)"
2139
  if debug:
2140
    fmt += " %(module)s:%(lineno)s"
2141
    # no debug info for syslog loggers
2142
  fmt += " %(levelname)s %(message)s"
2143
  # yes, we do want the textual level, as remote syslog will probably
2144
  # lose the error level, and it's easier to grep for it
2145
  sft += " %(levelname)s %(message)s"
2146
  formatter = logging.Formatter(fmt)
2147
  sys_fmt = logging.Formatter(sft)
2148

    
2149
  root_logger = logging.getLogger("")
2150
  root_logger.setLevel(logging.NOTSET)
2151

    
2152
  # Remove all previously setup handlers
2153
  for handler in root_logger.handlers:
2154
    handler.close()
2155
    root_logger.removeHandler(handler)
2156

    
2157
  if stderr_logging:
2158
    stderr_handler = logging.StreamHandler()
2159
    stderr_handler.setFormatter(formatter)
2160
    if debug:
2161
      stderr_handler.setLevel(logging.NOTSET)
2162
    else:
2163
      stderr_handler.setLevel(logging.CRITICAL)
2164
    root_logger.addHandler(stderr_handler)
2165

    
2166
  if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2167
    facility = logging.handlers.SysLogHandler.LOG_DAEMON
2168
    syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2169
                                                    facility)
2170
    syslog_handler.setFormatter(sys_fmt)
2171
    # Never enable debug over syslog
2172
    syslog_handler.setLevel(logging.INFO)
2173
    root_logger.addHandler(syslog_handler)
2174

    
2175
  if syslog != constants.SYSLOG_ONLY:
2176
    # this can fail, if the logging directories are not setup or we have
2177
    # a permisssion problem; in this case, it's best to log but ignore
2178
    # the error if stderr_logging is True, and if false we re-raise the
2179
    # exception since otherwise we could run but without any logs at all
2180
    try:
2181
      if console_logging:
2182
        logfile_handler = LogFileHandler(logfile)
2183
      else:
2184
        logfile_handler = logging.FileHandler(logfile)
2185
      logfile_handler.setFormatter(formatter)
2186
      if debug:
2187
        logfile_handler.setLevel(logging.DEBUG)
2188
      else:
2189
        logfile_handler.setLevel(logging.INFO)
2190
      root_logger.addHandler(logfile_handler)
2191
    except EnvironmentError:
2192
      if stderr_logging or syslog == constants.SYSLOG_YES:
2193
        logging.exception("Failed to enable logging to file '%s'", logfile)
2194
      else:
2195
        # we need to re-raise the exception
2196
        raise
2197

    
2198

    
2199
def IsNormAbsPath(path):
2200
  """Check whether a path is absolute and also normalized
2201

2202
  This avoids things like /dir/../../other/path to be valid.
2203

2204
  """
2205
  return os.path.normpath(path) == path and os.path.isabs(path)
2206

    
2207

    
2208
def PathJoin(*args):
2209
  """Safe-join a list of path components.
2210

2211
  Requirements:
2212
      - the first argument must be an absolute path
2213
      - no component in the path must have backtracking (e.g. /../),
2214
        since we check for normalization at the end
2215

2216
  @param args: the path components to be joined
2217
  @raise ValueError: for invalid paths
2218

2219
  """
2220
  # ensure we're having at least one path passed in
2221
  assert args
2222
  # ensure the first component is an absolute and normalized path name
2223
  root = args[0]
2224
  if not IsNormAbsPath(root):
2225
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2226
  result = os.path.join(*args)
2227
  # ensure that the whole path is normalized
2228
  if not IsNormAbsPath(result):
2229
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2230
  # check that we're still under the original prefix
2231
  prefix = os.path.commonprefix([root, result])
2232
  if prefix != root:
2233
    raise ValueError("Error: path joining resulted in different prefix"
2234
                     " (%s != %s)" % (prefix, root))
2235
  return result
2236

    
2237

    
2238
def TailFile(fname, lines=20):
2239
  """Return the last lines from a file.
2240

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

2245
  @param fname: the file name
2246
  @type lines: int
2247
  @param lines: the (maximum) number of lines to return
2248

2249
  """
2250
  fd = open(fname, "r")
2251
  try:
2252
    fd.seek(0, 2)
2253
    pos = fd.tell()
2254
    pos = max(0, pos-4096)
2255
    fd.seek(pos, 0)
2256
    raw_data = fd.read()
2257
  finally:
2258
    fd.close()
2259

    
2260
  rows = raw_data.splitlines()
2261
  return rows[-lines:]
2262

    
2263

    
2264
def _ParseAsn1Generalizedtime(value):
2265
  """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2266

2267
  @type value: string
2268
  @param value: ASN1 GENERALIZEDTIME timestamp
2269

2270
  """
2271
  m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2272
  if m:
2273
    # We have an offset
2274
    asn1time = m.group(1)
2275
    hours = int(m.group(2))
2276
    minutes = int(m.group(3))
2277
    utcoffset = (60 * hours) + minutes
2278
  else:
2279
    if not value.endswith("Z"):
2280
      raise ValueError("Missing timezone")
2281
    asn1time = value[:-1]
2282
    utcoffset = 0
2283

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

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

    
2288
  return calendar.timegm(tt.utctimetuple())
2289

    
2290

    
2291
def GetX509CertValidity(cert):
2292
  """Returns the validity period of the certificate.
2293

2294
  @type cert: OpenSSL.crypto.X509
2295
  @param cert: X509 certificate object
2296

2297
  """
2298
  # The get_notBefore and get_notAfter functions are only supported in
2299
  # pyOpenSSL 0.7 and above.
2300
  try:
2301
    get_notbefore_fn = cert.get_notBefore
2302
  except AttributeError:
2303
    not_before = None
2304
  else:
2305
    not_before_asn1 = get_notbefore_fn()
2306

    
2307
    if not_before_asn1 is None:
2308
      not_before = None
2309
    else:
2310
      not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2311

    
2312
  try:
2313
    get_notafter_fn = cert.get_notAfter
2314
  except AttributeError:
2315
    not_after = None
2316
  else:
2317
    not_after_asn1 = get_notafter_fn()
2318

    
2319
    if not_after_asn1 is None:
2320
      not_after = None
2321
    else:
2322
      not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2323

    
2324
  return (not_before, not_after)
2325

    
2326

    
2327
def SafeEncode(text):
2328
  """Return a 'safe' version of a source string.
2329

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

2339
  @type text: str or unicode
2340
  @param text: input data
2341
  @rtype: str
2342
  @return: a safe version of text
2343

2344
  """
2345
  if isinstance(text, unicode):
2346
    # only if unicode; if str already, we handle it below
2347
    text = text.encode('ascii', 'backslashreplace')
2348
  resu = ""
2349
  for char in text:
2350
    c = ord(char)
2351
    if char  == '\t':
2352
      resu += r'\t'
2353
    elif char == '\n':
2354
      resu += r'\n'
2355
    elif char == '\r':
2356
      resu += r'\'r'
2357
    elif c < 32 or c >= 127: # non-printable
2358
      resu += "\\x%02x" % (c & 0xff)
2359
    else:
2360
      resu += char
2361
  return resu
2362

    
2363

    
2364
def UnescapeAndSplit(text, sep=","):
2365
  """Split and unescape a string based on a given separator.
2366

2367
  This function splits a string based on a separator where the
2368
  separator itself can be escape in order to be an element of the
2369
  elements. The escaping rules are (assuming coma being the
2370
  separator):
2371
    - a plain , separates the elements
2372
    - a sequence \\\\, (double backslash plus comma) is handled as a
2373
      backslash plus a separator comma
2374
    - a sequence \, (backslash plus comma) is handled as a
2375
      non-separator comma
2376

2377
  @type text: string
2378
  @param text: the string to split
2379
  @type sep: string
2380
  @param text: the separator
2381
  @rtype: string
2382
  @return: a list of strings
2383

2384
  """
2385
  # we split the list by sep (with no escaping at this stage)
2386
  slist = text.split(sep)
2387
  # next, we revisit the elements and if any of them ended with an odd
2388
  # number of backslashes, then we join it with the next
2389
  rlist = []
2390
  while slist:
2391
    e1 = slist.pop(0)
2392
    if e1.endswith("\\"):
2393
      num_b = len(e1) - len(e1.rstrip("\\"))
2394
      if num_b % 2 == 1:
2395
        e2 = slist.pop(0)
2396
        # here the backslashes remain (all), and will be reduced in
2397
        # the next step
2398
        rlist.append(e1 + sep + e2)
2399
        continue
2400
    rlist.append(e1)
2401
  # finally, replace backslash-something with something
2402
  rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2403
  return rlist
2404

    
2405

    
2406
def CommaJoin(names):
2407
  """Nicely join a set of identifiers.
2408

2409
  @param names: set, list or tuple
2410
  @return: a string with the formatted results
2411

2412
  """
2413
  return ", ".join([str(val) for val in names])
2414

    
2415

    
2416
def BytesToMebibyte(value):
2417
  """Converts bytes to mebibytes.
2418

2419
  @type value: int
2420
  @param value: Value in bytes
2421
  @rtype: int
2422
  @return: Value in mebibytes
2423

2424
  """
2425
  return int(round(value / (1024.0 * 1024.0), 0))
2426

    
2427

    
2428
def CalculateDirectorySize(path):
2429
  """Calculates the size of a directory recursively.
2430

2431
  @type path: string
2432
  @param path: Path to directory
2433
  @rtype: int
2434
  @return: Size in mebibytes
2435

2436
  """
2437
  size = 0
2438

    
2439
  for (curpath, _, files) in os.walk(path):
2440
    for filename in files:
2441
      st = os.lstat(PathJoin(curpath, filename))
2442
      size += st.st_size
2443

    
2444
  return BytesToMebibyte(size)
2445

    
2446

    
2447
def GetFilesystemStats(path):
2448
  """Returns the total and free space on a filesystem.
2449

2450
  @type path: string
2451
  @param path: Path on filesystem to be examined
2452
  @rtype: int
2453
  @return: tuple of (Total space, Free space) in mebibytes
2454

2455
  """
2456
  st = os.statvfs(path)
2457

    
2458
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2459
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2460
  return (tsize, fsize)
2461

    
2462

    
2463
def RunInSeparateProcess(fn, *args):
2464
  """Runs a function in a separate process.
2465

2466
  Note: Only boolean return values are supported.
2467

2468
  @type fn: callable
2469
  @param fn: Function to be called
2470
  @rtype: bool
2471
  @return: Function's result
2472

2473
  """
2474
  pid = os.fork()
2475
  if pid == 0:
2476
    # Child process
2477
    try:
2478
      # In case the function uses temporary files
2479
      ResetTempfileModule()
2480

    
2481
      # Call function
2482
      result = int(bool(fn(*args)))
2483
      assert result in (0, 1)
2484
    except: # pylint: disable-msg=W0702
2485
      logging.exception("Error while calling function in separate process")
2486
      # 0 and 1 are reserved for the return value
2487
      result = 33
2488

    
2489
    os._exit(result) # pylint: disable-msg=W0212
2490

    
2491
  # Parent process
2492

    
2493
  # Avoid zombies and check exit code
2494
  (_, status) = os.waitpid(pid, 0)
2495

    
2496
  if os.WIFSIGNALED(status):
2497
    exitcode = None
2498
    signum = os.WTERMSIG(status)
2499
  else:
2500
    exitcode = os.WEXITSTATUS(status)
2501
    signum = None
2502

    
2503
  if not (exitcode in (0, 1) and signum is None):
2504
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2505
                              (exitcode, signum))
2506

    
2507
  return bool(exitcode)
2508

    
2509

    
2510
def LockedMethod(fn):
2511
  """Synchronized object access decorator.
2512

2513
  This decorator is intended to protect access to an object using the
2514
  object's own lock which is hardcoded to '_lock'.
2515

2516
  """
2517
  def _LockDebug(*args, **kwargs):
2518
    if debug_locks:
2519
      logging.debug(*args, **kwargs)
2520

    
2521
  def wrapper(self, *args, **kwargs):
2522
    # pylint: disable-msg=W0212
2523
    assert hasattr(self, '_lock')
2524
    lock = self._lock
2525
    _LockDebug("Waiting for %s", lock)
2526
    lock.acquire()
2527
    try:
2528
      _LockDebug("Acquired %s", lock)
2529
      result = fn(self, *args, **kwargs)
2530
    finally:
2531
      _LockDebug("Releasing %s", lock)
2532
      lock.release()
2533
      _LockDebug("Released %s", lock)
2534
    return result
2535
  return wrapper
2536

    
2537

    
2538
def LockFile(fd):
2539
  """Locks a file using POSIX locks.
2540

2541
  @type fd: int
2542
  @param fd: the file descriptor we need to lock
2543

2544
  """
2545
  try:
2546
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2547
  except IOError, err:
2548
    if err.errno == errno.EAGAIN:
2549
      raise errors.LockError("File already locked")
2550
    raise
2551

    
2552

    
2553
def FormatTime(val):
2554
  """Formats a time value.
2555

2556
  @type val: float or None
2557
  @param val: the timestamp as returned by time.time()
2558
  @return: a string value or N/A if we don't have a valid timestamp
2559

2560
  """
2561
  if val is None or not isinstance(val, (int, float)):
2562
    return "N/A"
2563
  # these two codes works on Linux, but they are not guaranteed on all
2564
  # platforms
2565
  return time.strftime("%F %T", time.localtime(val))
2566

    
2567

    
2568
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2569
  """Reads the watcher pause file.
2570

2571
  @type filename: string
2572
  @param filename: Path to watcher pause file
2573
  @type now: None, float or int
2574
  @param now: Current time as Unix timestamp
2575
  @type remove_after: int
2576
  @param remove_after: Remove watcher pause file after specified amount of
2577
    seconds past the pause end time
2578

2579
  """
2580
  if now is None:
2581
    now = time.time()
2582

    
2583
  try:
2584
    value = ReadFile(filename)
2585
  except IOError, err:
2586
    if err.errno != errno.ENOENT:
2587
      raise
2588
    value = None
2589

    
2590
  if value is not None:
2591
    try:
2592
      value = int(value)
2593
    except ValueError:
2594
      logging.warning(("Watcher pause file (%s) contains invalid value,"
2595
                       " removing it"), filename)
2596
      RemoveFile(filename)
2597
      value = None
2598

    
2599
    if value is not None:
2600
      # Remove file if it's outdated
2601
      if now > (value + remove_after):
2602
        RemoveFile(filename)
2603
        value = None
2604

    
2605
      elif now > value:
2606
        value = None
2607

    
2608
  return value
2609

    
2610

    
2611
class RetryTimeout(Exception):
2612
  """Retry loop timed out.
2613

2614
  Any arguments which was passed by the retried function to RetryAgain will be
2615
  preserved in RetryTimeout, if it is raised. If such argument was an exception
2616
  the RaiseInner helper method will reraise it.
2617

2618
  """
2619
  def RaiseInner(self):
2620
    if self.args and isinstance(self.args[0], Exception):
2621
      raise self.args[0]
2622
    else:
2623
      raise RetryTimeout(*self.args)
2624

    
2625

    
2626
class RetryAgain(Exception):
2627
  """Retry again.
2628

2629
  Any arguments passed to RetryAgain will be preserved, if a timeout occurs, as
2630
  arguments to RetryTimeout. If an exception is passed, the RaiseInner() method
2631
  of the RetryTimeout() method can be used to reraise it.
2632

2633
  """
2634

    
2635

    
2636
class _RetryDelayCalculator(object):
2637
  """Calculator for increasing delays.
2638

2639
  """
2640
  __slots__ = [
2641
    "_factor",
2642
    "_limit",
2643
    "_next",
2644
    "_start",
2645
    ]
2646

    
2647
  def __init__(self, start, factor, limit):
2648
    """Initializes this class.
2649

2650
    @type start: float
2651
    @param start: Initial delay
2652
    @type factor: float
2653
    @param factor: Factor for delay increase
2654
    @type limit: float or None
2655
    @param limit: Upper limit for delay or None for no limit
2656

2657
    """
2658
    assert start > 0.0
2659
    assert factor >= 1.0
2660
    assert limit is None or limit >= 0.0
2661

    
2662
    self._start = start
2663
    self._factor = factor
2664
    self._limit = limit
2665

    
2666
    self._next = start
2667

    
2668
  def __call__(self):
2669
    """Returns current delay and calculates the next one.
2670

2671
    """
2672
    current = self._next
2673

    
2674
    # Update for next run
2675
    if self._limit is None or self._next < self._limit:
2676
      self._next = min(self._limit, self._next * self._factor)
2677

    
2678
    return current
2679

    
2680

    
2681
#: Special delay to specify whole remaining timeout
2682
RETRY_REMAINING_TIME = object()
2683

    
2684

    
2685
def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2686
          _time_fn=time.time):
2687
  """Call a function repeatedly until it succeeds.
2688

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

2693
  C{delay} can be one of the following:
2694
    - callable returning the delay length as a float
2695
    - Tuple of (start, factor, limit)
2696
    - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2697
      useful when overriding L{wait_fn} to wait for an external event)
2698
    - A static delay as a number (int or float)
2699

2700
  @type fn: callable
2701
  @param fn: Function to be called
2702
  @param delay: Either a callable (returning the delay), a tuple of (start,
2703
                factor, limit) (see L{_RetryDelayCalculator}),
2704
                L{RETRY_REMAINING_TIME} or a number (int or float)
2705
  @type timeout: float
2706
  @param timeout: Total timeout
2707
  @type wait_fn: callable
2708
  @param wait_fn: Waiting function
2709
  @return: Return value of function
2710

2711
  """
2712
  assert callable(fn)
2713
  assert callable(wait_fn)
2714
  assert callable(_time_fn)
2715

    
2716
  if args is None:
2717
    args = []
2718

    
2719
  end_time = _time_fn() + timeout
2720

    
2721
  if callable(delay):
2722
    # External function to calculate delay
2723
    calc_delay = delay
2724

    
2725
  elif isinstance(delay, (tuple, list)):
2726
    # Increasing delay with optional upper boundary
2727
    (start, factor, limit) = delay
2728
    calc_delay = _RetryDelayCalculator(start, factor, limit)
2729

    
2730
  elif delay is RETRY_REMAINING_TIME:
2731
    # Always use the remaining time
2732
    calc_delay = None
2733

    
2734
  else:
2735
    # Static delay
2736
    calc_delay = lambda: delay
2737

    
2738
  assert calc_delay is None or callable(calc_delay)
2739

    
2740
  while True:
2741
    retry_args = []
2742
    try:
2743
      # pylint: disable-msg=W0142
2744
      return fn(*args)
2745
    except RetryAgain, err:
2746
      retry_args = err.args
2747
    except RetryTimeout:
2748
      raise errors.ProgrammerError("Nested retry loop detected that didn't"
2749
                                   " handle RetryTimeout")
2750

    
2751
    remaining_time = end_time - _time_fn()
2752

    
2753
    if remaining_time < 0.0:
2754
      # pylint: disable-msg=W0142
2755
      raise RetryTimeout(*retry_args)
2756

    
2757
    assert remaining_time >= 0.0
2758

    
2759
    if calc_delay is None:
2760
      wait_fn(remaining_time)
2761
    else:
2762
      current_delay = calc_delay()
2763
      if current_delay > 0.0:
2764
        wait_fn(current_delay)
2765

    
2766

    
2767
class FileLock(object):
2768
  """Utility class for file locks.
2769

2770
  """
2771
  def __init__(self, fd, filename):
2772
    """Constructor for FileLock.
2773

2774
    @type fd: file
2775
    @param fd: File object
2776
    @type filename: str
2777
    @param filename: Path of the file opened at I{fd}
2778

2779
    """
2780
    self.fd = fd
2781
    self.filename = filename
2782

    
2783
  @classmethod
2784
  def Open(cls, filename):
2785
    """Creates and opens a file to be used as a file-based lock.
2786

2787
    @type filename: string
2788
    @param filename: path to the file to be locked
2789

2790
    """
2791
    # Using "os.open" is necessary to allow both opening existing file
2792
    # read/write and creating if not existing. Vanilla "open" will truncate an
2793
    # existing file -or- allow creating if not existing.
2794
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2795
               filename)
2796

    
2797
  def __del__(self):
2798
    self.Close()
2799

    
2800
  def Close(self):
2801
    """Close the file and release the lock.
2802

2803
    """
2804
    if hasattr(self, "fd") and self.fd:
2805
      self.fd.close()
2806
      self.fd = None
2807

    
2808
  def _flock(self, flag, blocking, timeout, errmsg):
2809
    """Wrapper for fcntl.flock.
2810

2811
    @type flag: int
2812
    @param flag: operation flag
2813
    @type blocking: bool
2814
    @param blocking: whether the operation should be done in blocking mode.
2815
    @type timeout: None or float
2816
    @param timeout: for how long the operation should be retried (implies
2817
                    non-blocking mode).
2818
    @type errmsg: string
2819
    @param errmsg: error message in case operation fails.
2820

2821
    """
2822
    assert self.fd, "Lock was closed"
2823
    assert timeout is None or timeout >= 0, \
2824
      "If specified, timeout must be positive"
2825
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2826

    
2827
    # When a timeout is used, LOCK_NB must always be set
2828
    if not (timeout is None and blocking):
2829
      flag |= fcntl.LOCK_NB
2830

    
2831
    if timeout is None:
2832
      self._Lock(self.fd, flag, timeout)
2833
    else:
2834
      try:
2835
        Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2836
              args=(self.fd, flag, timeout))
2837
      except RetryTimeout:
2838
        raise errors.LockError(errmsg)
2839

    
2840
  @staticmethod
2841
  def _Lock(fd, flag, timeout):
2842
    try:
2843
      fcntl.flock(fd, flag)
2844
    except IOError, err:
2845
      if timeout is not None and err.errno == errno.EAGAIN:
2846
        raise RetryAgain()
2847

    
2848
      logging.exception("fcntl.flock failed")
2849
      raise
2850

    
2851
  def Exclusive(self, blocking=False, timeout=None):
2852
    """Locks the file in exclusive mode.
2853

2854
    @type blocking: boolean
2855
    @param blocking: whether to block and wait until we
2856
        can lock the file or return immediately
2857
    @type timeout: int or None
2858
    @param timeout: if not None, the duration to wait for the lock
2859
        (in blocking mode)
2860

2861
    """
2862
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2863
                "Failed to lock %s in exclusive mode" % self.filename)
2864

    
2865
  def Shared(self, blocking=False, timeout=None):
2866
    """Locks the file in shared mode.
2867

2868
    @type blocking: boolean
2869
    @param blocking: whether to block and wait until we
2870
        can lock the file or return immediately
2871
    @type timeout: int or None
2872
    @param timeout: if not None, the duration to wait for the lock
2873
        (in blocking mode)
2874

2875
    """
2876
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2877
                "Failed to lock %s in shared mode" % self.filename)
2878

    
2879
  def Unlock(self, blocking=True, timeout=None):
2880
    """Unlocks the file.
2881

2882
    According to C{flock(2)}, unlocking can also be a nonblocking
2883
    operation::
2884

2885
      To make a non-blocking request, include LOCK_NB with any of the above
2886
      operations.
2887

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

2895
    """
2896
    self._flock(fcntl.LOCK_UN, blocking, timeout,
2897
                "Failed to unlock %s" % self.filename)
2898

    
2899

    
2900
class LineSplitter:
2901
  """Splits data chunks into lines separated by newline.
2902

2903
  Instances provide a file-like interface.
2904

2905
  """
2906
  def __init__(self, line_fn, *args):
2907
    """Initializes this class.
2908

2909
    @type line_fn: callable
2910
    @param line_fn: Function called for each line, first parameter is line
2911
    @param args: Extra arguments for L{line_fn}
2912

2913
    """
2914
    assert callable(line_fn)
2915

    
2916
    if args:
2917
      # Python 2.4 doesn't have functools.partial yet
2918
      self._line_fn = \
2919
        lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
2920
    else:
2921
      self._line_fn = line_fn
2922

    
2923
    self._lines = collections.deque()
2924
    self._buffer = ""
2925

    
2926
  def write(self, data):
2927
    parts = (self._buffer + data).split("\n")
2928
    self._buffer = parts.pop()
2929
    self._lines.extend(parts)
2930

    
2931
  def flush(self):
2932
    while self._lines:
2933
      self._line_fn(self._lines.popleft().rstrip("\r\n"))
2934

    
2935
  def close(self):
2936
    self.flush()
2937
    if self._buffer:
2938
      self._line_fn(self._buffer)
2939

    
2940

    
2941
def SignalHandled(signums):
2942
  """Signal Handled decoration.
2943

2944
  This special decorator installs a signal handler and then calls the target
2945
  function. The function must accept a 'signal_handlers' keyword argument,
2946
  which will contain a dict indexed by signal number, with SignalHandler
2947
  objects as values.
2948

2949
  The decorator can be safely stacked with iself, to handle multiple signals
2950
  with different handlers.
2951

2952
  @type signums: list
2953
  @param signums: signals to intercept
2954

2955
  """
2956
  def wrap(fn):
2957
    def sig_function(*args, **kwargs):
2958
      assert 'signal_handlers' not in kwargs or \
2959
             kwargs['signal_handlers'] is None or \
2960
             isinstance(kwargs['signal_handlers'], dict), \
2961
             "Wrong signal_handlers parameter in original function call"
2962
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2963
        signal_handlers = kwargs['signal_handlers']
2964
      else:
2965
        signal_handlers = {}
2966
        kwargs['signal_handlers'] = signal_handlers
2967
      sighandler = SignalHandler(signums)
2968
      try:
2969
        for sig in signums:
2970
          signal_handlers[sig] = sighandler
2971
        return fn(*args, **kwargs)
2972
      finally:
2973
        sighandler.Reset()
2974
    return sig_function
2975
  return wrap
2976

    
2977

    
2978
class SignalHandler(object):
2979
  """Generic signal handler class.
2980

2981
  It automatically restores the original handler when deconstructed or
2982
  when L{Reset} is called. You can either pass your own handler
2983
  function in or query the L{called} attribute to detect whether the
2984
  signal was sent.
2985

2986
  @type signum: list
2987
  @ivar signum: the signals we handle
2988
  @type called: boolean
2989
  @ivar called: tracks whether any of the signals have been raised
2990

2991
  """
2992
  def __init__(self, signum):
2993
    """Constructs a new SignalHandler instance.
2994

2995
    @type signum: int or list of ints
2996
    @param signum: Single signal number or set of signal numbers
2997

2998
    """
2999
    self.signum = set(signum)
3000
    self.called = False
3001

    
3002
    self._previous = {}
3003
    try:
3004
      for signum in self.signum:
3005
        # Setup handler
3006
        prev_handler = signal.signal(signum, self._HandleSignal)
3007
        try:
3008
          self._previous[signum] = prev_handler
3009
        except:
3010
          # Restore previous handler
3011
          signal.signal(signum, prev_handler)
3012
          raise
3013
    except:
3014
      # Reset all handlers
3015
      self.Reset()
3016
      # Here we have a race condition: a handler may have already been called,
3017
      # but there's not much we can do about it at this point.
3018
      raise
3019

    
3020
  def __del__(self):
3021
    self.Reset()
3022

    
3023
  def Reset(self):
3024
    """Restore previous handler.
3025

3026
    This will reset all the signals to their previous handlers.
3027

3028
    """
3029
    for signum, prev_handler in self._previous.items():
3030
      signal.signal(signum, prev_handler)
3031
      # If successful, remove from dict
3032
      del self._previous[signum]
3033

    
3034
  def Clear(self):
3035
    """Unsets the L{called} flag.
3036

3037
    This function can be used in case a signal may arrive several times.
3038

3039
    """
3040
    self.called = False
3041

    
3042
  # we don't care about arguments, but we leave them named for the future
3043
  def _HandleSignal(self, signum, frame): # pylint: disable-msg=W0613
3044
    """Actual signal handling function.
3045

3046
    """
3047
    # This is not nice and not absolutely atomic, but it appears to be the only
3048
    # solution in Python -- there are no atomic types.
3049
    self.called = True
3050

    
3051

    
3052
class FieldSet(object):
3053
  """A simple field set.
3054

3055
  Among the features are:
3056
    - checking if a string is among a list of static string or regex objects
3057
    - checking if a whole list of string matches
3058
    - returning the matching groups from a regex match
3059

3060
  Internally, all fields are held as regular expression objects.
3061

3062
  """
3063
  def __init__(self, *items):
3064
    self.items = [re.compile("^%s$" % value) for value in items]
3065

    
3066
  def Extend(self, other_set):
3067
    """Extend the field set with the items from another one"""
3068
    self.items.extend(other_set.items)
3069

    
3070
  def Matches(self, field):
3071
    """Checks if a field matches the current set
3072

3073
    @type field: str
3074
    @param field: the string to match
3075
    @return: either None or a regular expression match object
3076

3077
    """
3078
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
3079
      return m
3080
    return None
3081

    
3082
  def NonMatching(self, items):
3083
    """Returns the list of fields not matching the current set
3084

3085
    @type items: list
3086
    @param items: the list of fields to check
3087
    @rtype: list
3088
    @return: list of non-matching fields
3089

3090
    """
3091
    return [val for val in items if not self.Matches(val)]