Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ d4fa5c23

History | View | Annotate | Download (26.8 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 small utilities
23

24
"""
25

    
26

    
27
import sys
28
import os
29
import sha
30
import time
31
import subprocess
32
import re
33
import socket
34
import tempfile
35
import shutil
36
import errno
37
import pwd
38
import itertools
39
import select
40
import fcntl
41
import resource
42

    
43
from cStringIO import StringIO
44

    
45
from ganeti import logger
46
from ganeti import errors
47
from ganeti import constants
48

    
49

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

    
53
debug = False
54
no_fork = False
55

    
56

    
57
class RunResult(object):
58
  """Simple class for holding the result of running external programs.
59

60
  Instance variables:
61
    exit_code: the exit code of the program, or None (if the program
62
               didn't exit())
63
    signal: numeric signal that caused the program to finish, or None
64
            (if the program wasn't terminated by a signal)
65
    stdout: the standard output of the program
66
    stderr: the standard error of the program
67
    failed: a Boolean value which is True in case the program was
68
            terminated by a signal or exited with a non-zero exit code
69
    fail_reason: a string detailing the termination reason
70

71
  """
72
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
73
               "failed", "fail_reason", "cmd"]
74

    
75

    
76
  def __init__(self, exit_code, signal, stdout, stderr, cmd):
77
    self.cmd = cmd
78
    self.exit_code = exit_code
79
    self.signal = signal
80
    self.stdout = stdout
81
    self.stderr = stderr
82
    self.failed = (signal is not None or exit_code != 0)
83

    
84
    if self.signal is not None:
85
      self.fail_reason = "terminated by signal %s" % self.signal
86
    elif self.exit_code is not None:
87
      self.fail_reason = "exited with exit code %s" % self.exit_code
88
    else:
89
      self.fail_reason = "unable to determine termination reason"
90

    
91
    if debug and self.failed:
92
      logger.Debug("Command '%s' failed (%s); output: %s" %
93
                   (self.cmd, self.fail_reason, self.output))
94

    
95
  def _GetOutput(self):
96
    """Returns the combined stdout and stderr for easier usage.
97

98
    """
99
    return self.stdout + self.stderr
100

    
101
  output = property(_GetOutput, None, None, "Return full output")
102

    
103

    
104
def RunCmd(cmd):
105
  """Execute a (shell) command.
106

107
  The command should not read from its standard input, as it will be
108
  closed.
109

110
  Args:
111
    cmd: command to run. (str)
112

113
  Returns: `RunResult` instance
114

115
  """
116
  if no_fork:
117
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
118

    
119
  if isinstance(cmd, list):
120
    cmd = [str(val) for val in cmd]
121
    strcmd = " ".join(cmd)
122
    shell = False
123
  else:
124
    strcmd = cmd
125
    shell = True
126
  env = os.environ.copy()
127
  env["LC_ALL"] = "C"
128
  poller = select.poll()
129
  child = subprocess.Popen(cmd, shell=shell,
130
                           stderr=subprocess.PIPE,
131
                           stdout=subprocess.PIPE,
132
                           stdin=subprocess.PIPE,
133
                           close_fds=True, env=env)
134

    
135
  child.stdin.close()
136
  poller.register(child.stdout, select.POLLIN)
137
  poller.register(child.stderr, select.POLLIN)
138
  out = StringIO()
139
  err = StringIO()
140
  fdmap = {
141
    child.stdout.fileno(): (out, child.stdout),
142
    child.stderr.fileno(): (err, child.stderr),
143
    }
144
  for fd in fdmap:
145
    status = fcntl.fcntl(fd, fcntl.F_GETFL)
146
    fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
147

    
148
  while fdmap:
149
    for fd, event in poller.poll():
150
      if event & select.POLLIN or event & select.POLLPRI:
151
        data = fdmap[fd][1].read()
152
        # no data from read signifies EOF (the same as POLLHUP)
153
        if not data:
154
          poller.unregister(fd)
155
          del fdmap[fd]
156
          continue
157
        fdmap[fd][0].write(data)
158
      if (event & select.POLLNVAL or event & select.POLLHUP or
159
          event & select.POLLERR):
160
        poller.unregister(fd)
161
        del fdmap[fd]
162

    
163
  out = out.getvalue()
164
  err = err.getvalue()
165

    
166
  status = child.wait()
167
  if status >= 0:
168
    exitcode = status
169
    signal = None
170
  else:
171
    exitcode = None
172
    signal = -status
173

    
174
  return RunResult(exitcode, signal, out, err, strcmd)
175

    
176

    
177
def RemoveFile(filename):
178
  """Remove a file ignoring some errors.
179

180
  Remove a file, ignoring non-existing ones or directories. Other
181
  errors are passed.
182

183
  """
184
  try:
185
    os.unlink(filename)
186
  except OSError, err:
187
    if err.errno not in (errno.ENOENT, errno.EISDIR):
188
      raise
189

    
190

    
191
def _FingerprintFile(filename):
192
  """Compute the fingerprint of a file.
193

194
  If the file does not exist, a None will be returned
195
  instead.
196

197
  Args:
198
    filename - Filename (str)
199

200
  """
201
  if not (os.path.exists(filename) and os.path.isfile(filename)):
202
    return None
203

    
204
  f = open(filename)
205

    
206
  fp = sha.sha()
207
  while True:
208
    data = f.read(4096)
209
    if not data:
210
      break
211

    
212
    fp.update(data)
213

    
214
  return fp.hexdigest()
215

    
216

    
217
def FingerprintFiles(files):
218
  """Compute fingerprints for a list of files.
219

220
  Args:
221
    files - array of filenames.  ( [str, ...] )
222

223
  Return value:
224
    dictionary of filename: fingerprint for the files that exist
225

226
  """
227
  ret = {}
228

    
229
  for filename in files:
230
    cksum = _FingerprintFile(filename)
231
    if cksum:
232
      ret[filename] = cksum
233

    
234
  return ret
235

    
236

    
237
def CheckDict(target, template, logname=None):
238
  """Ensure a dictionary has a required set of keys.
239

240
  For the given dictionaries `target` and `template`, ensure target
241
  has all the keys from template. Missing keys are added with values
242
  from template.
243

244
  Args:
245
    target   - the dictionary to check
246
    template - template dictionary
247
    logname  - a caller-chosen string to identify the debug log
248
               entry; if None, no logging will be done
249

250
  Returns value:
251
    None
252

253
  """
254
  missing = []
255
  for k in template:
256
    if k not in target:
257
      missing.append(k)
258
      target[k] = template[k]
259

    
260
  if missing and logname:
261
    logger.Debug('%s missing keys %s' %
262
                 (logname, ', '.join(missing)))
263

    
264

    
265
def IsProcessAlive(pid):
266
  """Check if a given pid exists on the system.
267

268
  Returns: true or false, depending on if the pid exists or not
269

270
  Remarks: zombie processes treated as not alive
271

272
  """
273
  try:
274
    f = open("/proc/%d/status" % pid)
275
  except IOError, err:
276
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
277
      return False
278

    
279
  alive = True
280
  try:
281
    data = f.readlines()
282
    if len(data) > 1:
283
      state = data[1].split()
284
      if len(state) > 1 and state[1] == "Z":
285
        alive = False
286
  finally:
287
    f.close()
288

    
289
  return alive
290

    
291

    
292
def MatchNameComponent(key, name_list):
293
  """Try to match a name against a list.
294

295
  This function will try to match a name like test1 against a list
296
  like ['test1.example.com', 'test2.example.com', ...]. Against this
297
  list, 'test1' as well as 'test1.example' will match, but not
298
  'test1.ex'. A multiple match will be considered as no match at all
299
  (e.g. 'test1' against ['test1.example.com', 'test1.example.org']).
300

301
  Args:
302
    key: the name to be searched
303
    name_list: the list of strings against which to search the key
304

305
  Returns:
306
    None if there is no match *or* if there are multiple matches
307
    otherwise the element from the list which matches
308

309
  """
310
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
311
  names_filtered = [name for name in name_list if mo.match(name) is not None]
312
  if len(names_filtered) != 1:
313
    return None
314
  return names_filtered[0]
315

    
316

    
317
class HostInfo:
318
  """Class implementing resolver and hostname functionality
319

320
  """
321
  def __init__(self, name=None):
322
    """Initialize the host name object.
323

324
    If the name argument is not passed, it will use this system's
325
    name.
326

327
    """
328
    if name is None:
329
      name = self.SysName()
330

    
331
    self.query = name
332
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
333
    self.ip = self.ipaddrs[0]
334

    
335
  def ShortName(self):
336
    """Returns the hostname without domain.
337

338
    """
339
    return self.name.split('.')[0]
340

    
341
  @staticmethod
342
  def SysName():
343
    """Return the current system's name.
344

345
    This is simply a wrapper over socket.gethostname()
346

347
    """
348
    return socket.gethostname()
349

    
350
  @staticmethod
351
  def LookupHostname(hostname):
352
    """Look up hostname
353

354
    Args:
355
      hostname: hostname to look up
356

357
    Returns:
358
      a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
359
      in case of errors in resolving, we raise a ResolverError
360

361
    """
362
    try:
363
      result = socket.gethostbyname_ex(hostname)
364
    except socket.gaierror, err:
365
      # hostname not found in DNS
366
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
367

    
368
    return result
369

    
370

    
371
def ListVolumeGroups():
372
  """List volume groups and their size
373

374
  Returns:
375
     Dictionary with keys volume name and values the size of the volume
376

377
  """
378
  command = "vgs --noheadings --units m --nosuffix -o name,size"
379
  result = RunCmd(command)
380
  retval = {}
381
  if result.failed:
382
    return retval
383

    
384
  for line in result.stdout.splitlines():
385
    try:
386
      name, size = line.split()
387
      size = int(float(size))
388
    except (IndexError, ValueError), err:
389
      logger.Error("Invalid output from vgs (%s): %s" % (err, line))
390
      continue
391

    
392
    retval[name] = size
393

    
394
  return retval
395

    
396

    
397
def BridgeExists(bridge):
398
  """Check whether the given bridge exists in the system
399

400
  Returns:
401
     True if it does, false otherwise.
402

403
  """
404
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
405

    
406

    
407
def NiceSort(name_list):
408
  """Sort a list of strings based on digit and non-digit groupings.
409

410
  Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
411
  sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
412

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

417
  Return value
418
    - a copy of the list sorted according to our algorithm
419

420
  """
421
  _SORTER_BASE = "(\D+|\d+)"
422
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
423
                                                  _SORTER_BASE, _SORTER_BASE,
424
                                                  _SORTER_BASE, _SORTER_BASE,
425
                                                  _SORTER_BASE, _SORTER_BASE)
426
  _SORTER_RE = re.compile(_SORTER_FULL)
427
  _SORTER_NODIGIT = re.compile("^\D*$")
428
  def _TryInt(val):
429
    """Attempts to convert a variable to integer."""
430
    if val is None or _SORTER_NODIGIT.match(val):
431
      return val
432
    rval = int(val)
433
    return rval
434

    
435
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
436
             for name in name_list]
437
  to_sort.sort()
438
  return [tup[1] for tup in to_sort]
439

    
440

    
441
def TryConvert(fn, val):
442
  """Try to convert a value ignoring errors.
443

444
  This function tries to apply function `fn` to `val`. If no
445
  ValueError or TypeError exceptions are raised, it will return the
446
  result, else it will return the original value. Any other exceptions
447
  are propagated to the caller.
448

449
  """
450
  try:
451
    nv = fn(val)
452
  except (ValueError, TypeError), err:
453
    nv = val
454
  return nv
455

    
456

    
457
def IsValidIP(ip):
458
  """Verifies the syntax of an IP address.
459

460
  This function checks if the ip address passes is valid or not based
461
  on syntax (not ip range, class calculations or anything).
462

463
  """
464
  unit = "(0|[1-9]\d{0,2})"
465
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
466

    
467

    
468
def IsValidShellParam(word):
469
  """Verifies is the given word is safe from the shell's p.o.v.
470

471
  This means that we can pass this to a command via the shell and be
472
  sure that it doesn't alter the command line and is passed as such to
473
  the actual command.
474

475
  Note that we are overly restrictive here, in order to be on the safe
476
  side.
477

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

    
481

    
482
def BuildShellCmd(template, *args):
483
  """Build a safe shell command line from the given arguments.
484

485
  This function will check all arguments in the args list so that they
486
  are valid shell parameters (i.e. they don't contain shell
487
  metacharaters). If everything is ok, it will return the result of
488
  template % args.
489

490
  """
491
  for word in args:
492
    if not IsValidShellParam(word):
493
      raise errors.ProgrammerError("Shell argument '%s' contains"
494
                                   " invalid characters" % word)
495
  return template % args
496

    
497

    
498
def FormatUnit(value):
499
  """Formats an incoming number of MiB with the appropriate unit.
500

501
  Value needs to be passed as a numeric type. Return value is always a string.
502

503
  """
504
  if value < 1024:
505
    return "%dM" % round(value, 0)
506

    
507
  elif value < (1024 * 1024):
508
    return "%0.1fG" % round(float(value) / 1024, 1)
509

    
510
  else:
511
    return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
512

    
513

    
514
def ParseUnit(input_string):
515
  """Tries to extract number and scale from the given string.
516

517
  Input must be in the format NUMBER+ [DOT NUMBER+] SPACE* [UNIT]. If no unit
518
  is specified, it defaults to MiB. Return value is always an int in MiB.
519

520
  """
521
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
522
  if not m:
523
    raise errors.UnitParseError("Invalid format")
524

    
525
  value = float(m.groups()[0])
526

    
527
  unit = m.groups()[1]
528
  if unit:
529
    lcunit = unit.lower()
530
  else:
531
    lcunit = 'm'
532

    
533
  if lcunit in ('m', 'mb', 'mib'):
534
    # Value already in MiB
535
    pass
536

    
537
  elif lcunit in ('g', 'gb', 'gib'):
538
    value *= 1024
539

    
540
  elif lcunit in ('t', 'tb', 'tib'):
541
    value *= 1024 * 1024
542

    
543
  else:
544
    raise errors.UnitParseError("Unknown unit: %s" % unit)
545

    
546
  # Make sure we round up
547
  if int(value) < value:
548
    value += 1
549

    
550
  # Round up to the next multiple of 4
551
  value = int(value)
552
  if value % 4:
553
    value += 4 - value % 4
554

    
555
  return value
556

    
557

    
558
def AddAuthorizedKey(file_name, key):
559
  """Adds an SSH public key to an authorized_keys file.
560

561
  Args:
562
    file_name: Path to authorized_keys file
563
    key: String containing key
564
  """
565
  key_fields = key.split()
566

    
567
  f = open(file_name, 'a+')
568
  try:
569
    nl = True
570
    for line in f:
571
      # Ignore whitespace changes
572
      if line.split() == key_fields:
573
        break
574
      nl = line.endswith('\n')
575
    else:
576
      if not nl:
577
        f.write("\n")
578
      f.write(key.rstrip('\r\n'))
579
      f.write("\n")
580
      f.flush()
581
  finally:
582
    f.close()
583

    
584

    
585
def RemoveAuthorizedKey(file_name, key):
586
  """Removes an SSH public key from an authorized_keys file.
587

588
  Args:
589
    file_name: Path to authorized_keys file
590
    key: String containing key
591
  """
592
  key_fields = key.split()
593

    
594
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
595
  try:
596
    out = os.fdopen(fd, 'w')
597
    try:
598
      f = open(file_name, 'r')
599
      try:
600
        for line in f:
601
          # Ignore whitespace changes while comparing lines
602
          if line.split() != key_fields:
603
            out.write(line)
604

    
605
        out.flush()
606
        os.rename(tmpname, file_name)
607
      finally:
608
        f.close()
609
    finally:
610
      out.close()
611
  except:
612
    RemoveFile(tmpname)
613
    raise
614

    
615

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

619
  """
620
  # Ensure aliases are unique
621
  aliases = UniqueSequence([hostname] + aliases)[1:]
622

    
623
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
624
  try:
625
    out = os.fdopen(fd, 'w')
626
    try:
627
      f = open(file_name, 'r')
628
      try:
629
        written = False
630
        for line in f:
631
          fields = line.split()
632
          if fields and not fields[0].startswith('#') and ip == fields[0]:
633
            continue
634
          out.write(line)
635

    
636
        out.write("%s\t%s" % (ip, hostname))
637
        if aliases:
638
          out.write(" %s" % ' '.join(aliases))
639
        out.write('\n')
640

    
641
        out.flush()
642
        os.fsync(out)
643
        os.rename(tmpname, file_name)
644
      finally:
645
        f.close()
646
    finally:
647
      out.close()
648
  except:
649
    RemoveFile(tmpname)
650
    raise
651

    
652

    
653
def AddHostToEtcHosts(hostname):
654
  """Wrapper around SetEtcHostsEntry.
655

656
  """
657
  hi = HostInfo(name=hostname)
658
  SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
659

    
660

    
661
def RemoveEtcHostsEntry(file_name, hostname):
662
  """Removes a hostname from /etc/hosts.
663

664
  IP addresses without names are removed from the file.
665
  """
666
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
667
  try:
668
    out = os.fdopen(fd, 'w')
669
    try:
670
      f = open(file_name, 'r')
671
      try:
672
        for line in f:
673
          fields = line.split()
674
          if len(fields) > 1 and not fields[0].startswith('#'):
675
            names = fields[1:]
676
            if hostname in names:
677
              while hostname in names:
678
                names.remove(hostname)
679
              if names:
680
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
681
              continue
682

    
683
          out.write(line)
684

    
685
        out.flush()
686
        os.fsync(out)
687
        os.rename(tmpname, file_name)
688
      finally:
689
        f.close()
690
    finally:
691
      out.close()
692
  except:
693
    RemoveFile(tmpname)
694
    raise
695

    
696

    
697
def RemoveHostFromEtcHosts(hostname):
698
  """Wrapper around RemoveEtcHostsEntry.
699

700
  """
701
  hi = HostInfo(name=hostname)
702
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
703
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
704

    
705

    
706
def CreateBackup(file_name):
707
  """Creates a backup of a file.
708

709
  Returns: the path to the newly created backup file.
710

711
  """
712
  if not os.path.isfile(file_name):
713
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
714
                                file_name)
715

    
716
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
717
  dir_name = os.path.dirname(file_name)
718

    
719
  fsrc = open(file_name, 'rb')
720
  try:
721
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
722
    fdst = os.fdopen(fd, 'wb')
723
    try:
724
      shutil.copyfileobj(fsrc, fdst)
725
    finally:
726
      fdst.close()
727
  finally:
728
    fsrc.close()
729

    
730
  return backup_name
731

    
732

    
733
def ShellQuote(value):
734
  """Quotes shell argument according to POSIX.
735

736
  """
737
  if _re_shell_unquoted.match(value):
738
    return value
739
  else:
740
    return "'%s'" % value.replace("'", "'\\''")
741

    
742

    
743
def ShellQuoteArgs(args):
744
  """Quotes all given shell arguments and concatenates using spaces.
745

746
  """
747
  return ' '.join([ShellQuote(i) for i in args])
748

    
749

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

753
  Try to do a TCP connect(2) from an optional source IP to the
754
  specified target IP and the specified target port. If the optional
755
  parameter live_port_needed is set to true, requires the remote end
756
  to accept the connection. The timeout is specified in seconds and
757
  defaults to 10 seconds. If the source optional argument is not
758
  passed, the source address selection is left to the kernel,
759
  otherwise we try to connect using the passed address (failures to
760
  bind other than EADDRNOTAVAIL will be ignored).
761

762
  """
763
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
764

    
765
  sucess = False
766

    
767
  if source is not None:
768
    try:
769
      sock.bind((source, 0))
770
    except socket.error, (errcode, errstring):
771
      if errcode == errno.EADDRNOTAVAIL:
772
        success = False
773

    
774
  sock.settimeout(timeout)
775

    
776
  try:
777
    sock.connect((target, port))
778
    sock.close()
779
    success = True
780
  except socket.timeout:
781
    success = False
782
  except socket.error, (errcode, errstring):
783
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
784

    
785
  return success
786

    
787

    
788
def ListVisibleFiles(path):
789
  """Returns a list of all visible files in a directory.
790

791
  """
792
  files = [i for i in os.listdir(path) if not i.startswith(".")]
793
  files.sort()
794
  return files
795

    
796

    
797
def GetHomeDir(user, default=None):
798
  """Try to get the homedir of the given user.
799

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

804
  """
805
  try:
806
    if isinstance(user, basestring):
807
      result = pwd.getpwnam(user)
808
    elif isinstance(user, (int, long)):
809
      result = pwd.getpwuid(user)
810
    else:
811
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
812
                                   type(user))
813
  except KeyError:
814
    return default
815
  return result.pw_dir
816

    
817

    
818
def NewUUID():
819
  """Returns a random UUID.
820

821
  """
822
  f = open("/proc/sys/kernel/random/uuid", "r")
823
  try:
824
    return f.read(128).rstrip("\n")
825
  finally:
826
    f.close()
827

    
828

    
829
def WriteFile(file_name, fn=None, data=None,
830
              mode=None, uid=-1, gid=-1,
831
              atime=None, mtime=None, close=True,
832
              dry_run=False, backup=False,
833
              prewrite=None, postwrite=None):
834
  """(Over)write a file atomically.
835

836
  The file_name and either fn (a function taking one argument, the
837
  file descriptor, and which should write the data to it) or data (the
838
  contents of the file) must be passed. The other arguments are
839
  optional and allow setting the file mode, owner and group, and the
840
  mtime/atime of the file.
841

842
  If the function doesn't raise an exception, it has succeeded and the
843
  target file has the new contents. If the file has raised an
844
  exception, an existing target file should be unmodified and the
845
  temporary file should be removed.
846

847
  Args:
848
    file_name: New filename
849
    fn: Content writing function, called with file descriptor as parameter
850
    data: Content as string
851
    mode: File mode
852
    uid: Owner
853
    gid: Group
854
    atime: Access time
855
    mtime: Modification time
856
    close: Whether to close file after writing it
857
    prewrite: Function object called before writing content
858
    postwrite: Function object called after writing content
859

860
  Returns:
861
    None if "close" parameter evaluates to True, otherwise file descriptor.
862

863
  """
864
  if not os.path.isabs(file_name):
865
    raise errors.ProgrammerError("Path passed to WriteFile is not"
866
                                 " absolute: '%s'" % file_name)
867

    
868
  if [fn, data].count(None) != 1:
869
    raise errors.ProgrammerError("fn or data required")
870

    
871
  if [atime, mtime].count(None) == 1:
872
    raise errors.ProgrammerError("Both atime and mtime must be either"
873
                                 " set or None")
874

    
875
  if backup and not dry_run and os.path.isfile(file_name):
876
    CreateBackup(file_name)
877

    
878
  dir_name, base_name = os.path.split(file_name)
879
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
880
  # here we need to make sure we remove the temp file, if any error
881
  # leaves it in place
882
  try:
883
    if uid != -1 or gid != -1:
884
      os.chown(new_name, uid, gid)
885
    if mode:
886
      os.chmod(new_name, mode)
887
    if callable(prewrite):
888
      prewrite(fd)
889
    if data is not None:
890
      os.write(fd, data)
891
    else:
892
      fn(fd)
893
    if callable(postwrite):
894
      postwrite(fd)
895
    os.fsync(fd)
896
    if atime is not None and mtime is not None:
897
      os.utime(new_name, (atime, mtime))
898
    if not dry_run:
899
      os.rename(new_name, file_name)
900
  finally:
901
    if close:
902
      os.close(fd)
903
      result = None
904
    else:
905
      result = fd
906
    RemoveFile(new_name)
907

    
908
  return result
909

    
910

    
911
def FirstFree(seq, base=0):
912
  """Returns the first non-existing integer from seq.
913

914
  The seq argument should be a sorted list of positive integers. The
915
  first time the index of an element is smaller than the element
916
  value, the index will be returned.
917

918
  The base argument is used to start at a different offset,
919
  i.e. [3, 4, 6] with offset=3 will return 5.
920

921
  Example: [0, 1, 3] will return 2.
922

923
  """
924
  for idx, elem in enumerate(seq):
925
    assert elem >= base, "Passed element is higher than base offset"
926
    if elem > idx + base:
927
      # idx is not used
928
      return idx + base
929
  return None
930

    
931

    
932
def all(seq, pred=bool):
933
  "Returns True if pred(x) is True for every element in the iterable"
934
  for elem in itertools.ifilterfalse(pred, seq):
935
    return False
936
  return True
937

    
938

    
939
def any(seq, pred=bool):
940
  "Returns True if pred(x) is True for at least one element in the iterable"
941
  for elem in itertools.ifilter(pred, seq):
942
    return True
943
  return False
944

    
945

    
946
def UniqueSequence(seq):
947
  """Returns a list with unique elements.
948

949
  Element order is preserved.
950
  """
951
  seen = set()
952
  return [i for i in seq if i not in seen and not seen.add(i)]
953

    
954

    
955
def IsValidMac(mac):
956
  """Predicate to check if a MAC address is valid.
957

958
  Checks wether the supplied MAC address is formally correct, only
959
  accepts colon separated format.
960
  """
961
  mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
962
  return mac_check.match(mac) is not None
963

    
964

    
965
def TestDelay(duration):
966
  """Sleep for a fixed amount of time.
967

968
  """
969
  if duration < 0:
970
    return False
971
  time.sleep(duration)
972
  return True
973

    
974

    
975
def Daemonize(logfile, noclose_fds=None):
976
  """Daemonize the current process.
977

978
  This detaches the current process from the controlling terminal and
979
  runs it in the background as a daemon.
980

981
  """
982
  UMASK = 077
983
  WORKDIR = "/"
984
  # Default maximum for the number of available file descriptors.
985
  if 'SC_OPEN_MAX' in os.sysconf_names:
986
    try:
987
      MAXFD = os.sysconf('SC_OPEN_MAX')
988
      if MAXFD < 0:
989
        MAXFD = 1024
990
    except OSError:
991
      MAXFD = 1024
992
  else:
993
    MAXFD = 1024
994

    
995
  # this might fail
996
  pid = os.fork()
997
  if (pid == 0):  # The first child.
998
    os.setsid()
999
    # this might fail
1000
    pid = os.fork() # Fork a second child.
1001
    if (pid == 0):  # The second child.
1002
      os.chdir(WORKDIR)
1003
      os.umask(UMASK)
1004
    else:
1005
      # exit() or _exit()?  See below.
1006
      os._exit(0) # Exit parent (the first child) of the second child.
1007
  else:
1008
    os._exit(0) # Exit parent of the first child.
1009
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1010
  if (maxfd == resource.RLIM_INFINITY):
1011
    maxfd = MAXFD
1012

    
1013
  # Iterate through and close all file descriptors.
1014
  for fd in range(0, maxfd):
1015
    if noclose_fds and fd in noclose_fds:
1016
      continue
1017
    try:
1018
      os.close(fd)
1019
    except OSError: # ERROR, fd wasn't open to begin with (ignored)
1020
      pass
1021
  os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1022
  # Duplicate standard input to standard output and standard error.
1023
  os.dup2(0, 1)     # standard output (1)
1024
  os.dup2(0, 2)     # standard error (2)
1025
  return 0
1026

    
1027

    
1028
def FindFile(name, search_path, test=os.path.exists):
1029
  """Look for a filesystem object in a given path.
1030

1031
  This is an abstract method to search for filesystem object (files,
1032
  dirs) under a given search path.
1033

1034
  Args:
1035
    - name: the name to look for
1036
    - search_path: list of directory names
1037
    - test: the test which the full path must satisfy
1038
      (defaults to os.path.exists)
1039

1040
  Returns:
1041
    - full path to the item if found
1042
    - None otherwise
1043

1044
  """
1045
  for dir_name in search_path:
1046
    item_name = os.path.sep.join([dir_name, name])
1047
    if test(item_name):
1048
      return item_name
1049
  return None
1050

    
1051

    
1052
def CheckVolumeGroupSize(vglist, vgname, minsize):
1053
  """Checks if the volume group list is valid.
1054

1055
  A non-None return value means there's an error, and the return value
1056
  is the error message.
1057

1058
  """
1059
  vgsize = vglist.get(vgname, None)
1060
  if vgsize is None:
1061
    return "volume group '%s' missing" % vgname
1062
  elif vgsize < minsize:
1063
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1064
            (vgname, minsize, vgsize))
1065
  return None