Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ f362096f

History | View | Annotate | Download (25.4 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

    
40
from ganeti import logger
41
from ganeti import errors
42

    
43

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

    
47
debug = False
48

    
49
class RunResult(object):
50
  """Simple class for holding the result of running external programs.
51

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

63
  """
64
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
65
               "failed", "fail_reason", "cmd"]
66

    
67

    
68
  def __init__(self, exit_code, signal, stdout, stderr, cmd):
69
    self.cmd = cmd
70
    self.exit_code = exit_code
71
    self.signal = signal
72
    self.stdout = stdout
73
    self.stderr = stderr
74
    self.failed = (signal is not None or exit_code != 0)
75

    
76
    if self.signal is not None:
77
      self.fail_reason = "terminated by signal %s" % self.signal
78
    elif self.exit_code is not None:
79
      self.fail_reason = "exited with exit code %s" % self.exit_code
80
    else:
81
      self.fail_reason = "unable to determine termination reason"
82

    
83
    if debug and self.failed:
84
      logger.Debug("Command '%s' failed (%s); output: %s" %
85
                   (self.cmd, self.fail_reason, self.output))
86

    
87
  def _GetOutput(self):
88
    """Returns the combined stdout and stderr for easier usage.
89

90
    """
91
    return self.stdout + self.stderr
92

    
93
  output = property(_GetOutput, None, None, "Return full output")
94

    
95

    
96
def _GetLockFile(subsystem):
97
  """Compute the file name for a given lock name."""
98
  return "/var/lock/ganeti_lock_%s" % subsystem
99

    
100

    
101
def Lock(name, max_retries=None, debug=False):
102
  """Lock a given subsystem.
103

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

107
  When the optional integer argument 'max_retries' is passed with a
108
  non-zero value, the function will sleep only for this number of
109
  times, and then it will will raise a LockError if the lock can't be
110
  acquired. Passing in a negative number will cause only one try to
111
  get the lock. Passing a positive number will make the function retry
112
  for approximately that number of seconds.
113

114
  """
115
  lockfile = _GetLockFile(name)
116

    
117
  if name in _locksheld:
118
    raise errors.LockError('Lock "%s" already held!' % (name,))
119

    
120
  errcount = 0
121

    
122
  retries = 0
123
  while True:
124
    try:
125
      fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_SYNC)
126
      break
127
    except OSError, creat_err:
128
      if creat_err.errno != errno.EEXIST:
129
        raise errors.LockError("Can't create the lock file. Error '%s'." %
130
                               str(creat_err))
131

    
132
      try:
133
        pf = open(lockfile, 'r')
134
      except IOError, open_err:
135
        errcount += 1
136
        if errcount >= 5:
137
          raise errors.LockError("Lock file exists but cannot be opened."
138
                                 " Error: '%s'." % str(open_err))
139
        time.sleep(1)
140
        continue
141

    
142
      try:
143
        pid = int(pf.read())
144
      except ValueError:
145
        raise errors.LockError("Invalid pid string in %s" %
146
                               (lockfile,))
147

    
148
      if not IsProcessAlive(pid):
149
        raise errors.LockError("Stale lockfile %s for pid %d?" %
150
                               (lockfile, pid))
151

    
152
      if max_retries and max_retries <= retries:
153
        raise errors.LockError("Can't acquire lock during the specified"
154
                               " time, aborting.")
155
      if retries == 5 and (debug or sys.stdin.isatty()):
156
        logger.ToStderr("Waiting for '%s' lock from pid %d..." % (name, pid))
157

    
158
      time.sleep(1)
159
      retries += 1
160
      continue
161

    
162
  os.write(fd, '%d\n' % (os.getpid(),))
163
  os.close(fd)
164

    
165
  _locksheld.append(name)
166

    
167

    
168
def Unlock(name):
169
  """Unlock a given subsystem.
170

171
  """
172
  lockfile = _GetLockFile(name)
173

    
174
  try:
175
    fd = os.open(lockfile, os.O_RDONLY)
176
  except OSError:
177
    raise errors.LockError('Lock "%s" not held.' % (name,))
178

    
179
  f = os.fdopen(fd, 'r')
180
  pid_str = f.read()
181

    
182
  try:
183
    pid = int(pid_str)
184
  except ValueError:
185
    raise errors.LockError('Unable to determine PID of locking process.')
186

    
187
  if pid != os.getpid():
188
    raise errors.LockError('Lock not held by me (%d != %d)' %
189
                           (os.getpid(), pid,))
190

    
191
  os.unlink(lockfile)
192
  _locksheld.remove(name)
193

    
194

    
195
def LockCleanup():
196
  """Remove all locks.
197

198
  """
199
  for lock in _locksheld:
200
    Unlock(lock)
201

    
202

    
203
def RunCmd(cmd):
204
  """Execute a (shell) command.
205

206
  The command should not read from its standard input, as it will be
207
  closed.
208

209
  Args:
210
    cmd: command to run. (str)
211

212
  Returns: `RunResult` instance
213

214
  """
215
  if isinstance(cmd, list):
216
    cmd = [str(val) for val in cmd]
217
    strcmd = " ".join(cmd)
218
    shell = False
219
  else:
220
    strcmd = cmd
221
    shell = True
222
  env = os.environ.copy()
223
  env["LC_ALL"] = "C"
224
  child = subprocess.Popen(cmd, shell=shell,
225
                           stderr=subprocess.PIPE,
226
                           stdout=subprocess.PIPE,
227
                           stdin=subprocess.PIPE,
228
                           close_fds=True, env=env)
229

    
230
  child.stdin.close()
231
  out = child.stdout.read()
232
  err = child.stderr.read()
233

    
234
  status = child.wait()
235
  if status >= 0:
236
    exitcode = status
237
    signal = None
238
  else:
239
    exitcode = None
240
    signal = -status
241

    
242
  return RunResult(exitcode, signal, out, err, strcmd)
243

    
244

    
245
def RunCmdUnlocked(cmd):
246
  """Execute a shell command without the 'cmd' lock.
247

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

252
  The argument and return values are the same as for the `RunCmd()`
253
  function.
254

255
  Args:
256
    cmd - command to run. (str)
257

258
  Returns:
259
    `RunResult`
260

261
  """
262
  Unlock('cmd')
263
  ret = RunCmd(cmd)
264
  Lock('cmd')
265

    
266
  return ret
267

    
268

    
269
def RemoveFile(filename):
270
  """Remove a file ignoring some errors.
271

272
  Remove a file, ignoring non-existing ones or directories. Other
273
  errors are passed.
274

275
  """
276
  try:
277
    os.unlink(filename)
278
  except OSError, err:
279
    if err.errno not in (errno.ENOENT, errno.EISDIR):
280
      raise
281

    
282

    
283
def _FingerprintFile(filename):
284
  """Compute the fingerprint of a file.
285

286
  If the file does not exist, a None will be returned
287
  instead.
288

289
  Args:
290
    filename - Filename (str)
291

292
  """
293
  if not (os.path.exists(filename) and os.path.isfile(filename)):
294
    return None
295

    
296
  f = open(filename)
297

    
298
  fp = sha.sha()
299
  while True:
300
    data = f.read(4096)
301
    if not data:
302
      break
303

    
304
    fp.update(data)
305

    
306
  return fp.hexdigest()
307

    
308

    
309
def FingerprintFiles(files):
310
  """Compute fingerprints for a list of files.
311

312
  Args:
313
    files - array of filenames.  ( [str, ...] )
314

315
  Return value:
316
    dictionary of filename: fingerprint for the files that exist
317

318
  """
319
  ret = {}
320

    
321
  for filename in files:
322
    cksum = _FingerprintFile(filename)
323
    if cksum:
324
      ret[filename] = cksum
325

    
326
  return ret
327

    
328

    
329
def CheckDict(target, template, logname=None):
330
  """Ensure a dictionary has a required set of keys.
331

332
  For the given dictionaries `target` and `template`, ensure target
333
  has all the keys from template. Missing keys are added with values
334
  from template.
335

336
  Args:
337
    target   - the dictionary to check
338
    template - template dictionary
339
    logname  - a caller-chosen string to identify the debug log
340
               entry; if None, no logging will be done
341

342
  Returns value:
343
    None
344

345
  """
346
  missing = []
347
  for k in template:
348
    if k not in target:
349
      missing.append(k)
350
      target[k] = template[k]
351

    
352
  if missing and logname:
353
    logger.Debug('%s missing keys %s' %
354
                 (logname, ', '.join(missing)))
355

    
356

    
357
def IsProcessAlive(pid):
358
  """Check if a given pid exists on the system.
359

360
  Returns: true or false, depending on if the pid exists or not
361

362
  Remarks: zombie processes treated as not alive
363

364
  """
365
  try:
366
    f = open("/proc/%d/status" % pid)
367
  except IOError, err:
368
    if err.errno in (errno.ENOENT, errno.ENOTDIR):
369
      return False
370

    
371
  alive = True
372
  try:
373
    data = f.readlines()
374
    if len(data) > 1:
375
      state = data[1].split()
376
      if len(state) > 1 and state[1] == "Z":
377
        alive = False
378
  finally:
379
    f.close()
380

    
381
  return alive
382

    
383

    
384
def MatchNameComponent(key, name_list):
385
  """Try to match a name against a list.
386

387
  This function will try to match a name like test1 against a list
388
  like ['test1.example.com', 'test2.example.com', ...]. Against this
389
  list, 'test1' as well as 'test1.example' will match, but not
390
  'test1.ex'. A multiple match will be considered as no match at all
391
  (e.g. 'test1' against ['test1.example.com', 'test1.example.org']).
392

393
  Args:
394
    key: the name to be searched
395
    name_list: the list of strings against which to search the key
396

397
  Returns:
398
    None if there is no match *or* if there are multiple matches
399
    otherwise the element from the list which matches
400

401
  """
402
  mo = re.compile("^%s(\..*)?$" % re.escape(key))
403
  names_filtered = [name for name in name_list if mo.match(name) is not None]
404
  if len(names_filtered) != 1:
405
    return None
406
  return names_filtered[0]
407

    
408

    
409
class HostInfo:
410
  """Class implementing resolver and hostname functionality
411

412
  """
413
  def __init__(self, name=None):
414
    """Initialize the host name object.
415

416
    If the name argument is not passed, it will use this system's
417
    name.
418

419
    """
420
    if name is None:
421
      name = self.SysName()
422

    
423
    self.query = name
424
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
425
    self.ip = self.ipaddrs[0]
426

    
427
  def ShortName(self):
428
    """Returns the hostname without domain.
429

430
    """
431
    return self.name.split('.')[0]
432

    
433
  @staticmethod
434
  def SysName():
435
    """Return the current system's name.
436

437
    This is simply a wrapper over socket.gethostname()
438

439
    """
440
    return socket.gethostname()
441

    
442
  @staticmethod
443
  def LookupHostname(hostname):
444
    """Look up hostname
445

446
    Args:
447
      hostname: hostname to look up
448

449
    Returns:
450
      a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
451
      in case of errors in resolving, we raise a ResolverError
452

453
    """
454
    try:
455
      result = socket.gethostbyname_ex(hostname)
456
    except socket.gaierror, err:
457
      # hostname not found in DNS
458
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
459

    
460
    return result
461

    
462

    
463
def ListVolumeGroups():
464
  """List volume groups and their size
465

466
  Returns:
467
     Dictionary with keys volume name and values the size of the volume
468

469
  """
470
  command = "vgs --noheadings --units m --nosuffix -o name,size"
471
  result = RunCmd(command)
472
  retval = {}
473
  if result.failed:
474
    return retval
475

    
476
  for line in result.stdout.splitlines():
477
    try:
478
      name, size = line.split()
479
      size = int(float(size))
480
    except (IndexError, ValueError), err:
481
      logger.Error("Invalid output from vgs (%s): %s" % (err, line))
482
      continue
483

    
484
    retval[name] = size
485

    
486
  return retval
487

    
488

    
489
def BridgeExists(bridge):
490
  """Check whether the given bridge exists in the system
491

492
  Returns:
493
     True if it does, false otherwise.
494

495
  """
496
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
497

    
498

    
499
def NiceSort(name_list):
500
  """Sort a list of strings based on digit and non-digit groupings.
501

502
  Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
503
  sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
504

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

509
  Return value
510
    - a copy of the list sorted according to our algorithm
511

512
  """
513
  _SORTER_BASE = "(\D+|\d+)"
514
  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
515
                                                  _SORTER_BASE, _SORTER_BASE,
516
                                                  _SORTER_BASE, _SORTER_BASE,
517
                                                  _SORTER_BASE, _SORTER_BASE)
518
  _SORTER_RE = re.compile(_SORTER_FULL)
519
  _SORTER_NODIGIT = re.compile("^\D*$")
520
  def _TryInt(val):
521
    """Attempts to convert a variable to integer."""
522
    if val is None or _SORTER_NODIGIT.match(val):
523
      return val
524
    rval = int(val)
525
    return rval
526

    
527
  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
528
             for name in name_list]
529
  to_sort.sort()
530
  return [tup[1] for tup in to_sort]
531

    
532

    
533
def CheckDaemonAlive(pid_file, process_string):
534
  """Check wether the specified daemon is alive.
535

536
  Args:
537
   - pid_file: file to read the daemon pid from, the file is
538
               expected to contain only a single line containing
539
               only the PID
540
   - process_string: a substring that we expect to find in
541
                     the command line of the daemon process
542

543
  Returns:
544
   - True if the daemon is judged to be alive (that is:
545
      - the PID file exists, is readable and contains a number
546
      - a process of the specified PID is running
547
      - that process contains the specified string in its
548
        command line
549
      - the process is not in state Z (zombie))
550
   - False otherwise
551

552
  """
553
  try:
554
    pid_file = file(pid_file, 'r')
555
    try:
556
      pid = int(pid_file.readline())
557
    finally:
558
      pid_file.close()
559

    
560
    cmdline_file_path = "/proc/%s/cmdline" % (pid)
561
    cmdline_file = open(cmdline_file_path, 'r')
562
    try:
563
      cmdline = cmdline_file.readline()
564
    finally:
565
      cmdline_file.close()
566

    
567
    if not process_string in cmdline:
568
      return False
569

    
570
    stat_file_path =  "/proc/%s/stat" % (pid)
571
    stat_file = open(stat_file_path, 'r')
572
    try:
573
      process_state = stat_file.readline().split()[2]
574
    finally:
575
      stat_file.close()
576

    
577
    if process_state == 'Z':
578
      return False
579

    
580
  except (IndexError, IOError, ValueError):
581
    return False
582

    
583
  return True
584

    
585

    
586
def TryConvert(fn, val):
587
  """Try to convert a value ignoring errors.
588

589
  This function tries to apply function `fn` to `val`. If no
590
  ValueError or TypeError exceptions are raised, it will return the
591
  result, else it will return the original value. Any other exceptions
592
  are propagated to the caller.
593

594
  """
595
  try:
596
    nv = fn(val)
597
  except (ValueError, TypeError), err:
598
    nv = val
599
  return nv
600

    
601

    
602
def IsValidIP(ip):
603
  """Verifies the syntax of an IP address.
604

605
  This function checks if the ip address passes is valid or not based
606
  on syntax (not ip range, class calculations or anything).
607

608
  """
609
  unit = "(0|[1-9]\d{0,2})"
610
  return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
611

    
612

    
613
def IsValidShellParam(word):
614
  """Verifies is the given word is safe from the shell's p.o.v.
615

616
  This means that we can pass this to a command via the shell and be
617
  sure that it doesn't alter the command line and is passed as such to
618
  the actual command.
619

620
  Note that we are overly restrictive here, in order to be on the safe
621
  side.
622

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

    
626

    
627
def BuildShellCmd(template, *args):
628
  """Build a safe shell command line from the given arguments.
629

630
  This function will check all arguments in the args list so that they
631
  are valid shell parameters (i.e. they don't contain shell
632
  metacharaters). If everything is ok, it will return the result of
633
  template % args.
634

635
  """
636
  for word in args:
637
    if not IsValidShellParam(word):
638
      raise errors.ProgrammerError("Shell argument '%s' contains"
639
                                   " invalid characters" % word)
640
  return template % args
641

    
642

    
643
def FormatUnit(value):
644
  """Formats an incoming number of MiB with the appropriate unit.
645

646
  Value needs to be passed as a numeric type. Return value is always a string.
647

648
  """
649
  if value < 1024:
650
    return "%dM" % round(value, 0)
651

    
652
  elif value < (1024 * 1024):
653
    return "%0.1fG" % round(float(value) / 1024, 1)
654

    
655
  else:
656
    return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
657

    
658

    
659
def ParseUnit(input_string):
660
  """Tries to extract number and scale from the given string.
661

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

665
  """
666
  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
667
  if not m:
668
    raise errors.UnitParseError("Invalid format")
669

    
670
  value = float(m.groups()[0])
671

    
672
  unit = m.groups()[1]
673
  if unit:
674
    lcunit = unit.lower()
675
  else:
676
    lcunit = 'm'
677

    
678
  if lcunit in ('m', 'mb', 'mib'):
679
    # Value already in MiB
680
    pass
681

    
682
  elif lcunit in ('g', 'gb', 'gib'):
683
    value *= 1024
684

    
685
  elif lcunit in ('t', 'tb', 'tib'):
686
    value *= 1024 * 1024
687

    
688
  else:
689
    raise errors.UnitParseError("Unknown unit: %s" % unit)
690

    
691
  # Make sure we round up
692
  if int(value) < value:
693
    value += 1
694

    
695
  # Round up to the next multiple of 4
696
  value = int(value)
697
  if value % 4:
698
    value += 4 - value % 4
699

    
700
  return value
701

    
702

    
703
def AddAuthorizedKey(file_name, key):
704
  """Adds an SSH public key to an authorized_keys file.
705

706
  Args:
707
    file_name: Path to authorized_keys file
708
    key: String containing key
709
  """
710
  key_fields = key.split()
711

    
712
  f = open(file_name, 'a+')
713
  try:
714
    nl = True
715
    for line in f:
716
      # Ignore whitespace changes
717
      if line.split() == key_fields:
718
        break
719
      nl = line.endswith('\n')
720
    else:
721
      if not nl:
722
        f.write("\n")
723
      f.write(key.rstrip('\r\n'))
724
      f.write("\n")
725
      f.flush()
726
  finally:
727
    f.close()
728

    
729

    
730
def RemoveAuthorizedKey(file_name, key):
731
  """Removes an SSH public key from an authorized_keys file.
732

733
  Args:
734
    file_name: Path to authorized_keys file
735
    key: String containing key
736
  """
737
  key_fields = key.split()
738

    
739
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
740
  try:
741
    out = os.fdopen(fd, 'w')
742
    try:
743
      f = open(file_name, 'r')
744
      try:
745
        for line in f:
746
          # Ignore whitespace changes while comparing lines
747
          if line.split() != key_fields:
748
            out.write(line)
749

    
750
        out.flush()
751
        os.rename(tmpname, file_name)
752
      finally:
753
        f.close()
754
    finally:
755
      out.close()
756
  except:
757
    RemoveFile(tmpname)
758
    raise
759

    
760

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

764
  """
765
  # Ensure aliases are unique
766
  aliases = UniqueSequence([hostname] + aliases)[1:]
767

    
768
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
769
  try:
770
    out = os.fdopen(fd, 'w')
771
    try:
772
      f = open(file_name, 'r')
773
      try:
774
        written = False
775
        for line in f:
776
          fields = line.split()
777
          if fields and not fields[0].startswith('#') and ip == fields[0]:
778
            continue
779
          out.write(line)
780

    
781
        out.write("%s\t%s" % (ip, hostname))
782
        if aliases:
783
          out.write(" %s" % ' '.join(aliases))
784
        out.write('\n')
785

    
786
        out.flush()
787
        os.fsync(out)
788
        os.rename(tmpname, file_name)
789
      finally:
790
        f.close()
791
    finally:
792
      out.close()
793
  except:
794
    RemoveFile(tmpname)
795
    raise
796

    
797

    
798
def RemoveEtcHostsEntry(file_name, hostname):
799
  """Removes a hostname from /etc/hosts.
800

801
  IP addresses without names are removed from the file.
802
  """
803
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
804
  try:
805
    out = os.fdopen(fd, 'w')
806
    try:
807
      f = open(file_name, 'r')
808
      try:
809
        for line in f:
810
          fields = line.split()
811
          if len(fields) > 1 and not fields[0].startswith('#'):
812
            names = fields[1:]
813
            if hostname in names:
814
              while hostname in names:
815
                names.remove(hostname)
816
              if names:
817
                out.write("%s %s\n" % (fields[0], ' '.join(names)))
818
              continue
819

    
820
          out.write(line)
821

    
822
        out.flush()
823
        os.fsync(out)
824
        os.rename(tmpname, file_name)
825
      finally:
826
        f.close()
827
    finally:
828
      out.close()
829
  except:
830
    RemoveFile(tmpname)
831
    raise
832

    
833

    
834
def CreateBackup(file_name):
835
  """Creates a backup of a file.
836

837
  Returns: the path to the newly created backup file.
838

839
  """
840
  if not os.path.isfile(file_name):
841
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
842
                                file_name)
843

    
844
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
845
  dir_name = os.path.dirname(file_name)
846

    
847
  fsrc = open(file_name, 'rb')
848
  try:
849
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
850
    fdst = os.fdopen(fd, 'wb')
851
    try:
852
      shutil.copyfileobj(fsrc, fdst)
853
    finally:
854
      fdst.close()
855
  finally:
856
    fsrc.close()
857

    
858
  return backup_name
859

    
860

    
861
def ShellQuote(value):
862
  """Quotes shell argument according to POSIX.
863

864
  """
865
  if _re_shell_unquoted.match(value):
866
    return value
867
  else:
868
    return "'%s'" % value.replace("'", "'\\''")
869

    
870

    
871
def ShellQuoteArgs(args):
872
  """Quotes all given shell arguments and concatenates using spaces.
873

874
  """
875
  return ' '.join([ShellQuote(i) for i in args])
876

    
877

    
878

    
879
def TcpPing(source, target, port, timeout=10, live_port_needed=False):
880
  """Simple ping implementation using TCP connect(2).
881

882
  Try to do a TCP connect(2) from the specified source IP to the specified
883
  target IP and the specified target port. If live_port_needed is set to true,
884
  requires the remote end to accept the connection. The timeout is specified
885
  in seconds and defaults to 10 seconds
886

887
  """
888
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
889

    
890
  sucess = False
891

    
892
  try:
893
    sock.bind((source, 0))
894
  except socket.error, (errcode, errstring):
895
    if errcode == errno.EADDRNOTAVAIL:
896
      success = False
897

    
898
  sock.settimeout(timeout)
899

    
900
  try:
901
    sock.connect((target, port))
902
    sock.close()
903
    success = True
904
  except socket.timeout:
905
    success = False
906
  except socket.error, (errcode, errstring):
907
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
908

    
909
  return success
910

    
911

    
912
def ListVisibleFiles(path):
913
  """Returns a list of all visible files in a directory.
914

915
  """
916
  files = [i for i in os.listdir(path) if not i.startswith(".")]
917
  files.sort()
918
  return files
919

    
920

    
921
def GetHomeDir(user, default=None):
922
  """Try to get the homedir of the given user.
923

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

928
  """
929
  try:
930
    if isinstance(user, basestring):
931
      result = pwd.getpwnam(user)
932
    elif isinstance(user, (int, long)):
933
      result = pwd.getpwuid(user)
934
    else:
935
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
936
                                   type(user))
937
  except KeyError:
938
    return default
939
  return result.pw_dir
940

    
941

    
942
def NewUUID():
943
  """Returns a random UUID.
944

945
  """
946
  f = open("/proc/sys/kernel/random/uuid", "r")
947
  try:
948
    return f.read(128).rstrip("\n")
949
  finally:
950
    f.close()
951

    
952

    
953
def WriteFile(file_name, fn=None, data=None,
954
              mode=None, uid=-1, gid=-1,
955
              atime=None, mtime=None):
956
  """(Over)write a file atomically.
957

958
  The file_name and either fn (a function taking one argument, the
959
  file descriptor, and which should write the data to it) or data (the
960
  contents of the file) must be passed. The other arguments are
961
  optional and allow setting the file mode, owner and group, and the
962
  mtime/atime of the file.
963

964
  If the function doesn't raise an exception, it has succeeded and the
965
  target file has the new contents. If the file has raised an
966
  exception, an existing target file should be unmodified and the
967
  temporary file should be removed.
968

969
  """
970
  if not os.path.isabs(file_name):
971
    raise errors.ProgrammerError("Path passed to WriteFile is not"
972
                                 " absolute: '%s'" % file_name)
973

    
974
  if [fn, data].count(None) != 1:
975
    raise errors.ProgrammerError("fn or data required")
976

    
977
  if [atime, mtime].count(None) == 1:
978
    raise errors.ProgrammerError("Both atime and mtime must be either"
979
                                 " set or None")
980

    
981

    
982
  dir_name, base_name = os.path.split(file_name)
983
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
984
  # here we need to make sure we remove the temp file, if any error
985
  # leaves it in place
986
  try:
987
    if uid != -1 or gid != -1:
988
      os.chown(new_name, uid, gid)
989
    if mode:
990
      os.chmod(new_name, mode)
991
    if data is not None:
992
      os.write(fd, data)
993
    else:
994
      fn(fd)
995
    os.fsync(fd)
996
    if atime is not None and mtime is not None:
997
      os.utime(new_name, (atime, mtime))
998
    os.rename(new_name, file_name)
999
  finally:
1000
    os.close(fd)
1001
    RemoveFile(new_name)
1002

    
1003

    
1004
def all(seq, pred=bool):
1005
  "Returns True if pred(x) is True for every element in the iterable"
1006
  for elem in itertools.ifilterfalse(pred, seq):
1007
    return False
1008
  return True
1009

    
1010

    
1011
def any(seq, pred=bool):
1012
  "Returns True if pred(x) is True for at least one element in the iterable"
1013
  for elem in itertools.ifilter(pred, seq):
1014
    return True
1015
  return False
1016

    
1017

    
1018
def UniqueSequence(seq):
1019
  """Returns a list with unique elements.
1020

1021
  Element order is preserved.
1022
  """
1023
  seen = set()
1024
  return [i for i in seq if i not in seen and not seen.add(i)]