Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 59072e7e

History | View | Annotate | Download (20.6 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
import sys
27
import os
28
import sha
29
import time
30
import subprocess
31
import re
32
import socket
33
import tempfile
34
import shutil
35
import errno
36
import pwd
37

    
38
from ganeti import logger
39
from ganeti import errors
40

    
41

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

    
45
class RunResult(object):
46
  """Simple class for holding the result of running external programs.
47

48
  Instance variables:
49
    exit_code: the exit code of the program, or None (if the program
50
               didn't exit())
51
    signal: numeric signal that caused the program to finish, or None
52
            (if the program wasn't terminated by a signal)
53
    stdout: the standard output of the program
54
    stderr: the standard error of the program
55
    failed: a Boolean value which is True in case the program was
56
            terminated by a signal or exited with a non-zero exit code
57
    fail_reason: a string detailing the termination reason
58

59
  """
60
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
61
               "failed", "fail_reason", "cmd"]
62

    
63

    
64
  def __init__(self, exit_code, signal, stdout, stderr, cmd):
65
    self.cmd = cmd
66
    self.exit_code = exit_code
67
    self.signal = signal
68
    self.stdout = stdout
69
    self.stderr = stderr
70
    self.failed = (signal is not None or exit_code != 0)
71

    
72
    if self.signal is not None:
73
      self.fail_reason = "terminated by signal %s" % self.signal
74
    elif self.exit_code is not None:
75
      self.fail_reason = "exited with exit code %s" % self.exit_code
76
    else:
77
      self.fail_reason = "unable to determine termination reason"
78

    
79
  def _GetOutput(self):
80
    """Returns the combined stdout and stderr for easier usage.
81

82
    """
83
    return self.stdout + self.stderr
84

    
85
  output = property(_GetOutput, None, None, "Return full output")
86

    
87

    
88
def _GetLockFile(subsystem):
89
  """Compute the file name for a given lock name."""
90
  return "/var/lock/ganeti_lock_%s" % subsystem
91

    
92

    
93
def Lock(name, max_retries=None, debug=False):
94
  """Lock a given subsystem.
95

96
  In case the lock is already held by an alive process, the function
97
  will sleep indefintely and poll with a one second interval.
98

99
  When the optional integer argument 'max_retries' is passed with a
100
  non-zero value, the function will sleep only for this number of
101
  times, and then it will will raise a LockError if the lock can't be
102
  acquired. Passing in a negative number will cause only one try to
103
  get the lock. Passing a positive number will make the function retry
104
  for approximately that number of seconds.
105

106
  """
107
  lockfile = _GetLockFile(name)
108

    
109
  if name in _locksheld:
110
    raise errors.LockError('Lock "%s" already held!' % (name,))
111

    
112
  errcount = 0
113

    
114
  retries = 0
115
  while True:
116
    try:
117
      fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_SYNC)
118
      break
119
    except OSError, creat_err:
120
      if creat_err.errno != errno.EEXIST:
121
        raise errors.LockError("Can't create the lock file. Error '%s'." %
122
                               str(creat_err))
123

    
124
      try:
125
        pf = open(lockfile, 'r')
126
      except IOError, open_err:
127
        errcount += 1
128
        if errcount >= 5:
129
          raise errors.LockError("Lock file exists but cannot be opened."
130
                                 " Error: '%s'." % str(open_err))
131
        time.sleep(1)
132
        continue
133

    
134
      try:
135
        pid = int(pf.read())
136
      except ValueError:
137
        raise errors.LockError("Invalid pid string in %s" %
138
                               (lockfile,))
139

    
140
      if not IsProcessAlive(pid):
141
        raise errors.LockError("Stale lockfile %s for pid %d?" %
142
                               (lockfile, pid))
143

    
144
      if max_retries and max_retries <= retries:
145
        raise errors.LockError("Can't acquire lock during the specified"
146
                               " time, aborting.")
147
      if retries == 5 and (debug or sys.stdin.isatty()):
148
        logger.ToStderr("Waiting for '%s' lock from pid %d..." % (name, pid))
149

    
150
      time.sleep(1)
151
      retries += 1
152
      continue
153

    
154
  os.write(fd, '%d\n' % (os.getpid(),))
155
  os.close(fd)
156

    
157
  _locksheld.append(name)
158

    
159

    
160
def Unlock(name):
161
  """Unlock a given subsystem.
162

163
  """
164
  lockfile = _GetLockFile(name)
165

    
166
  try:
167
    fd = os.open(lockfile, os.O_RDONLY)
168
  except OSError:
169
    raise errors.LockError('Lock "%s" not held.' % (name,))
170

    
171
  f = os.fdopen(fd, 'r')
172
  pid_str = f.read()
173

    
174
  try:
175
    pid = int(pid_str)
176
  except ValueError:
177
    raise errors.LockError('Unable to determine PID of locking process.')
178

    
179
  if pid != os.getpid():
180
    raise errors.LockError('Lock not held by me (%d != %d)' %
181
                           (os.getpid(), pid,))
182

    
183
  os.unlink(lockfile)
184
  _locksheld.remove(name)
185

    
186

    
187
def LockCleanup():
188
  """Remove all locks.
189

190
  """
191
  for lock in _locksheld:
192
    Unlock(lock)
193

    
194

    
195
def RunCmd(cmd):
196
  """Execute a (shell) command.
197

198
  The command should not read from its standard input, as it will be
199
  closed.
200

201
  Args:
202
    cmd: command to run. (str)
203

204
  Returns: `RunResult` instance
205

206
  """
207
  if isinstance(cmd, list):
208
    cmd = [str(val) for val in cmd]
209
    strcmd = " ".join(cmd)
210
    shell = False
211
  else:
212
    strcmd = cmd
213
    shell = True
214
  env = os.environ.copy()
215
  env["LC_ALL"] = "C"
216
  child = subprocess.Popen(cmd, shell=shell,
217
                           stderr=subprocess.PIPE,
218
                           stdout=subprocess.PIPE,
219
                           stdin=subprocess.PIPE,
220
                           close_fds=True, env=env)
221

    
222
  child.stdin.close()
223
  out = child.stdout.read()
224
  err = child.stderr.read()
225

    
226
  status = child.wait()
227
  if status >= 0:
228
    exitcode = status
229
    signal = None
230
  else:
231
    exitcode = None
232
    signal = -status
233

    
234
  return RunResult(exitcode, signal, out, err, strcmd)
235

    
236

    
237
def RunCmdUnlocked(cmd):
238
  """Execute a shell command without the 'cmd' lock.
239

240
  This variant of `RunCmd()` drops the 'cmd' lock before running the
241
  command and re-aquires it afterwards, thus it can be used to call
242
  other ganeti commands.
243

244
  The argument and return values are the same as for the `RunCmd()`
245
  function.
246

247
  Args:
248
    cmd - command to run. (str)
249

250
  Returns:
251
    `RunResult`
252

253
  """
254
  Unlock('cmd')
255
  ret = RunCmd(cmd)
256
  Lock('cmd')
257

    
258
  return ret
259

    
260

    
261
def RemoveFile(filename):
262
  """Remove a file ignoring some errors.
263

264
  Remove a file, ignoring non-existing ones or directories. Other
265
  errors are passed.
266

267
  """
268
  try:
269
    os.unlink(filename)
270
  except OSError, err:
271
    if err.errno not in (errno.ENOENT, errno.EISDIR):
272
      raise
273

    
274

    
275
def _FingerprintFile(filename):
276
  """Compute the fingerprint of a file.
277

278
  If the file does not exist, a None will be returned
279
  instead.
280

281
  Args:
282
    filename - Filename (str)
283

284
  """
285
  if not (os.path.exists(filename) and os.path.isfile(filename)):
286
    return None
287

    
288
  f = open(filename)
289

    
290
  fp = sha.sha()
291
  while True:
292
    data = f.read(4096)
293
    if not data:
294
      break
295

    
296
    fp.update(data)
297

    
298
  return fp.hexdigest()
299

    
300

    
301
def FingerprintFiles(files):
302
  """Compute fingerprints for a list of files.
303

304
  Args:
305
    files - array of filenames.  ( [str, ...] )
306

307
  Return value:
308
    dictionary of filename: fingerprint for the files that exist
309

310
  """
311
  ret = {}
312

    
313
  for filename in files:
314
    cksum = _FingerprintFile(filename)
315
    if cksum:
316
      ret[filename] = cksum
317

    
318
  return ret
319

    
320

    
321
def CheckDict(target, template, logname=None):
322
  """Ensure a dictionary has a required set of keys.
323

324
  For the given dictionaries `target` and `template`, ensure target
325
  has all the keys from template. Missing keys are added with values
326
  from template.
327

328
  Args:
329
    target   - the dictionary to check
330
    template - template dictionary
331
    logname  - a caller-chosen string to identify the debug log
332
               entry; if None, no logging will be done
333

334
  Returns value:
335
    None
336

337
  """
338
  missing = []
339
  for k in template:
340
    if k not in target:
341
      missing.append(k)
342
      target[k] = template[k]
343

    
344
  if missing and logname:
345
    logger.Debug('%s missing keys %s' %
346
                 (logname, ', '.join(missing)))
347

    
348

    
349
def IsProcessAlive(pid):
350
  """Check if a given pid exists on the system.
351

352
  Returns: true or false, depending on if the pid exists or not
353

354
  Remarks: zombie processes treated as not alive
355

356
  """
357
  try:
358
    f = open("/proc/%d/status" % pid)
359
  except IOError, err:
360
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
361
      return False
362

    
363
  alive = True
364
  try:
365
    data = f.readlines()
366
    if len(data) > 1:
367
      state = data[1].split()
368
      if len(state) > 1 and state[1] == "Z":
369
        alive = False
370
  finally:
371
    f.close()
372

    
373
  return alive
374

    
375

    
376
def MatchNameComponent(key, name_list):
377
  """Try to match a name against a list.
378

379
  This function will try to match a name like test1 against a list
380
  like ['test1.example.com', 'test2.example.com', ...]. Against this
381
  list, 'test1' as well as 'test1.example' will match, but not
382
  'test1.ex'. A multiple match will be considered as no match at all
383
  (e.g. 'test1' against ['test1.example.com', 'test1.example.org']).
384

385
  Args:
386
    key: the name to be searched
387
    name_list: the list of strings against which to search the key
388

389
  Returns:
390
    None if there is no match *or* if there are multiple matches
391
    otherwise the element from the list which matches
392

393
  """
394
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
395
  names_filtered = [name for name in name_list if mo.match(name) is not None]
396
  if len(names_filtered) != 1:
397
    return None
398
  return names_filtered[0]
399

    
400

    
401
class HostInfo:
402
  """Class implementing resolver and hostname functionality
403

404
  """
405
  def __init__(self, name=None):
406
    """Initialize the host name object.
407

408
    If the name argument is not passed, it will use this system's
409
    name.
410

411
    """
412
    if name is None:
413
      name = self.SysName()
414

    
415
    self.query = name
416
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
417
    self.ip = self.ipaddrs[0]
418

    
419
  @staticmethod
420
  def SysName():
421
    """Return the current system's name.
422

423
    This is simply a wrapper over socket.gethostname()
424

425
    """
426
    return socket.gethostname()
427

    
428
  @staticmethod
429
  def LookupHostname(hostname):
430
    """Look up hostname
431

432
    Args:
433
      hostname: hostname to look up
434

435
    Returns:
436
      a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
437
      in case of errors in resolving, we raise a ResolverError
438

439
    """
440
    try:
441
      result = socket.gethostbyname_ex(hostname)
442
    except socket.gaierror, err:
443
      # hostname not found in DNS
444
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
445

    
446
    return result
447

    
448

    
449
def ListVolumeGroups():
450
  """List volume groups and their size
451

452
  Returns:
453
     Dictionary with keys volume name and values the size of the volume
454

455
  """
456
  command = "vgs --noheadings --units m --nosuffix -o name,size"
457
  result = RunCmd(command)
458
  retval = {}
459
  if result.failed:
460
    return retval
461

    
462
  for line in result.stdout.splitlines():
463
    try:
464
      name, size = line.split()
465
      size = int(float(size))
466
    except (IndexError, ValueError), err:
467
      logger.Error("Invalid output from vgs (%s): %s" % (err, line))
468
      continue
469

    
470
    retval[name] = size
471

    
472
  return retval
473

    
474

    
475
def BridgeExists(bridge):
476
  """Check whether the given bridge exists in the system
477

478
  Returns:
479
     True if it does, false otherwise.
480

481
  """
482
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
483

    
484

    
485
def NiceSort(name_list):
486
  """Sort a list of strings based on digit and non-digit groupings.
487

488
  Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
489
  sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
490

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

495
  Return value
496
    - a copy of the list sorted according to our algorithm
497

498
  """
499
  _SORTER_BASE = "(\D+|\d+)"
500
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
501
                                                  _SORTER_BASE, _SORTER_BASE,
502
                                                  _SORTER_BASE, _SORTER_BASE,
503
                                                  _SORTER_BASE, _SORTER_BASE)
504
  _SORTER_RE = re.compile(_SORTER_FULL)
505
  _SORTER_NODIGIT = re.compile("^\D*$")
506
  def _TryInt(val):
507
    """Attempts to convert a variable to integer."""
508
    if val is None or _SORTER_NODIGIT.match(val):
509
      return val
510
    rval = int(val)
511
    return rval
512

    
513
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
514
             for name in name_list]
515
  to_sort.sort()
516
  return [tup[1] for tup in to_sort]
517

    
518

    
519
def CheckDaemonAlive(pid_file, process_string):
520
  """Check wether the specified daemon is alive.
521

522
  Args:
523
   - pid_file: file to read the daemon pid from, the file is
524
               expected to contain only a single line containing
525
               only the PID
526
   - process_string: a substring that we expect to find in
527
                     the command line of the daemon process
528

529
  Returns:
530
   - True if the daemon is judged to be alive (that is:
531
      - the PID file exists, is readable and contains a number
532
      - a process of the specified PID is running
533
      - that process contains the specified string in its
534
        command line
535
      - the process is not in state Z (zombie))
536
   - False otherwise
537

538
  """
539
  try:
540
    pid_file = file(pid_file, 'r')
541
    try:
542
      pid = int(pid_file.readline())
543
    finally:
544
      pid_file.close()
545

    
546
    cmdline_file_path = "/proc/%s/cmdline" % (pid)
547
    cmdline_file = open(cmdline_file_path, 'r')
548
    try:
549
      cmdline = cmdline_file.readline()
550
    finally:
551
      cmdline_file.close()
552

    
553
    if not process_string in cmdline:
554
      return False
555

    
556
    stat_file_path =  "/proc/%s/stat" % (pid)
557
    stat_file = open(stat_file_path, 'r')
558
    try:
559
      process_state = stat_file.readline().split()[2]
560
    finally:
561
      stat_file.close()
562

    
563
    if process_state == 'Z':
564
      return False
565

    
566
  except (IndexError, IOError, ValueError):
567
    return False
568

    
569
  return True
570

    
571

    
572
def TryConvert(fn, val):
573
  """Try to convert a value ignoring errors.
574

575
  This function tries to apply function `fn` to `val`. If no
576
  ValueError or TypeError exceptions are raised, it will return the
577
  result, else it will return the original value. Any other exceptions
578
  are propagated to the caller.
579

580
  """
581
  try:
582
    nv = fn(val)
583
  except (ValueError, TypeError), err:
584
    nv = val
585
  return nv
586

    
587

    
588
def IsValidIP(ip):
589
  """Verifies the syntax of an IP address.
590

591
  This function checks if the ip address passes is valid or not based
592
  on syntax (not ip range, class calculations or anything).
593

594
  """
595
  unit = "(0|[1-9]\d{0,2})"
596
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
597

    
598

    
599
def IsValidShellParam(word):
600
  """Verifies is the given word is safe from the shell's p.o.v.
601

602
  This means that we can pass this to a command via the shell and be
603
  sure that it doesn't alter the command line and is passed as such to
604
  the actual command.
605

606
  Note that we are overly restrictive here, in order to be on the safe
607
  side.
608

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

    
612

    
613
def BuildShellCmd(template, *args):
614
  """Build a safe shell command line from the given arguments.
615

616
  This function will check all arguments in the args list so that they
617
  are valid shell parameters (i.e. they don't contain shell
618
  metacharaters). If everything is ok, it will return the result of
619
  template % args.
620

621
  """
622
  for word in args:
623
    if not IsValidShellParam(word):
624
      raise errors.ProgrammerError("Shell argument '%s' contains"
625
                                   " invalid characters" % word)
626
  return template % args
627

    
628

    
629
def FormatUnit(value):
630
  """Formats an incoming number of MiB with the appropriate unit.
631

632
  Value needs to be passed as a numeric type. Return value is always a string.
633

634
  """
635
  if value < 1024:
636
    return "%dM" % round(value, 0)
637

    
638
  elif value < (1024 * 1024):
639
    return "%0.1fG" % round(float(value) / 1024, 1)
640

    
641
  else:
642
    return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
643

    
644

    
645
def ParseUnit(input_string):
646
  """Tries to extract number and scale from the given string.
647

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

651
  """
652
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
653
  if not m:
654
    raise errors.UnitParseError("Invalid format")
655

    
656
  value = float(m.groups()[0])
657

    
658
  unit = m.groups()[1]
659
  if unit:
660
    lcunit = unit.lower()
661
  else:
662
    lcunit = 'm'
663

    
664
  if lcunit in ('m', 'mb', 'mib'):
665
    # Value already in MiB
666
    pass
667

    
668
  elif lcunit in ('g', 'gb', 'gib'):
669
    value *= 1024
670

    
671
  elif lcunit in ('t', 'tb', 'tib'):
672
    value *= 1024 * 1024
673

    
674
  else:
675
    raise errors.UnitParseError("Unknown unit: %s" % unit)
676

    
677
  # Make sure we round up
678
  if int(value) < value:
679
    value += 1
680

    
681
  # Round up to the next multiple of 4
682
  value = int(value)
683
  if value % 4:
684
    value += 4 - value % 4
685

    
686
  return value
687

    
688

    
689
def AddAuthorizedKey(file_name, key):
690
  """Adds an SSH public key to an authorized_keys file.
691

692
  Args:
693
    file_name: Path to authorized_keys file
694
    key: String containing key
695
  """
696
  key_fields = key.split()
697

    
698
  f = open(file_name, 'a+')
699
  try:
700
    nl = True
701
    for line in f:
702
      # Ignore whitespace changes
703
      if line.split() == key_fields:
704
        break
705
      nl = line.endswith('\n')
706
    else:
707
      if not nl:
708
        f.write("\n")
709
      f.write(key.rstrip('\r\n'))
710
      f.write("\n")
711
      f.flush()
712
  finally:
713
    f.close()
714

    
715

    
716
def RemoveAuthorizedKey(file_name, key):
717
  """Removes an SSH public key from an authorized_keys file.
718

719
  Args:
720
    file_name: Path to authorized_keys file
721
    key: String containing key
722
  """
723
  key_fields = key.split()
724

    
725
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
726
  out = os.fdopen(fd, 'w')
727
  try:
728
    f = open(file_name, 'r')
729
    try:
730
      for line in f:
731
        # Ignore whitespace changes while comparing lines
732
        if line.split() != key_fields:
733
          out.write(line)
734

    
735
      out.flush()
736
      os.rename(tmpname, file_name)
737
    finally:
738
      f.close()
739
  finally:
740
    out.close()
741

    
742

    
743
def CreateBackup(file_name):
744
  """Creates a backup of a file.
745

746
  Returns: the path to the newly created backup file.
747

748
  """
749
  if not os.path.isfile(file_name):
750
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
751
                                file_name)
752

    
753
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
754
  dir = os.path.dirname(file_name)
755

    
756
  fsrc = open(file_name, 'rb')
757
  try:
758
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir)
759
    fdst = os.fdopen(fd, 'wb')
760
    try:
761
      shutil.copyfileobj(fsrc, fdst)
762
    finally:
763
      fdst.close()
764
  finally:
765
    fsrc.close()
766

    
767
  return backup_name
768

    
769

    
770
def ShellQuote(value):
771
  """Quotes shell argument according to POSIX.
772

773
  """
774
  if _re_shell_unquoted.match(value):
775
    return value
776
  else:
777
    return "'%s'" % value.replace("'", "'\\''")
778

    
779

    
780
def ShellQuoteArgs(args):
781
  """Quotes all given shell arguments and concatenates using spaces.
782

783
  """
784
  return ' '.join([ShellQuote(i) for i in args])
785

    
786

    
787

    
788
def TcpPing(source, target, port, timeout=10, live_port_needed=False):
789
  """Simple ping implementation using TCP connect(2).
790

791
  Try to do a TCP connect(2) from the specified source IP to the specified
792
  target IP and the specified target port. If live_port_needed is set to true,
793
  requires the remote end to accept the connection. The timeout is specified
794
  in seconds and defaults to 10 seconds
795

796
  """
797
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
798

    
799
  sucess = False
800

    
801
  try:
802
    sock.bind((source, 0))
803
  except socket.error, (errcode, errstring):
804
    if errcode == errno.EADDRNOTAVAIL:
805
      success = False
806

    
807
  sock.settimeout(timeout)
808

    
809
  try:
810
    sock.connect((target, port))
811
    sock.close()
812
    success = True
813
  except socket.timeout:
814
    success = False
815
  except socket.error, (errcode, errstring):
816
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
817

    
818
  return success
819

    
820

    
821
def ListVisibleFiles(path):
822
  """Returns a list of all visible files in a directory.
823

824
  """
825
  return [i for i in os.listdir(path) if not i.startswith(".")]
826

    
827

    
828
def GetHomeDir(uid, default=None):
829
  """Try to get the homedir of the given user id.
830

831
  """
832
  try:
833
    result = pwd.getpwuid(uid)
834
  except KeyError:
835
    return default
836
  return result.pw_dir
837

    
838

    
839
def GetUUID():
840
  """Returns a random UUID.
841

842
  """
843
  f = open("/proc/sys/kernel/random/uuid", "r")
844
  try:
845
    return f.read(128).rstrip("\n")
846
  finally:
847
    f.close()