Statistics
| Branch: | Tag: | Revision:

root / lib / utils.py @ 899d2a81

History | View | Annotate | Download (24.5 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
class RunResult(object):
48
  """Simple class for holding the result of running external programs.
49

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

61
  """
62
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
63
               "failed", "fail_reason", "cmd"]
64

    
65

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

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

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

84
    """
85
    return self.stdout + self.stderr
86

    
87
  output = property(_GetOutput, None, None, "Return full output")
88

    
89

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

    
94

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

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

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

108
  """
109
  lockfile = _GetLockFile(name)
110

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

    
114
  errcount = 0
115

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

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

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

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

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

    
152
      time.sleep(1)
153
      retries += 1
154
      continue
155

    
156
  os.write(fd, '%d\n' % (os.getpid(),))
157
  os.close(fd)
158

    
159
  _locksheld.append(name)
160

    
161

    
162
def Unlock(name):
163
  """Unlock a given subsystem.
164

165
  """
166
  lockfile = _GetLockFile(name)
167

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

    
173
  f = os.fdopen(fd, 'r')
174
  pid_str = f.read()
175

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

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

    
185
  os.unlink(lockfile)
186
  _locksheld.remove(name)
187

    
188

    
189
def LockCleanup():
190
  """Remove all locks.
191

192
  """
193
  for lock in _locksheld:
194
    Unlock(lock)
195

    
196

    
197
def RunCmd(cmd):
198
  """Execute a (shell) command.
199

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

203
  Args:
204
    cmd: command to run. (str)
205

206
  Returns: `RunResult` instance
207

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

    
224
  child.stdin.close()
225
  out = child.stdout.read()
226
  err = child.stderr.read()
227

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

    
236
  return RunResult(exitcode, signal, out, err, strcmd)
237

    
238

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

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

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

249
  Args:
250
    cmd - command to run. (str)
251

252
  Returns:
253
    `RunResult`
254

255
  """
256
  Unlock('cmd')
257
  ret = RunCmd(cmd)
258
  Lock('cmd')
259

    
260
  return ret
261

    
262

    
263
def RemoveFile(filename):
264
  """Remove a file ignoring some errors.
265

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

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

    
276

    
277
def _FingerprintFile(filename):
278
  """Compute the fingerprint of a file.
279

280
  If the file does not exist, a None will be returned
281
  instead.
282

283
  Args:
284
    filename - Filename (str)
285

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

    
290
  f = open(filename)
291

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

    
298
    fp.update(data)
299

    
300
  return fp.hexdigest()
301

    
302

    
303
def FingerprintFiles(files):
304
  """Compute fingerprints for a list of files.
305

306
  Args:
307
    files - array of filenames.  ( [str, ...] )
308

309
  Return value:
310
    dictionary of filename: fingerprint for the files that exist
311

312
  """
313
  ret = {}
314

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

    
320
  return ret
321

    
322

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

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

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

336
  Returns value:
337
    None
338

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

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

    
350

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

354
  Returns: true or false, depending on if the pid exists or not
355

356
  Remarks: zombie processes treated as not alive
357

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

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

    
375
  return alive
376

    
377

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

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

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

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

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

    
402

    
403
class HostInfo:
404
  """Class implementing resolver and hostname functionality
405

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

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

413
    """
414
    if name is None:
415
      name = self.SysName()
416

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

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

425
    This is simply a wrapper over socket.gethostname()
426

427
    """
428
    return socket.gethostname()
429

    
430
  @staticmethod
431
  def LookupHostname(hostname):
432
    """Look up hostname
433

434
    Args:
435
      hostname: hostname to look up
436

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

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

    
448
    return result
449

    
450

    
451
def ListVolumeGroups():
452
  """List volume groups and their size
453

454
  Returns:
455
     Dictionary with keys volume name and values the size of the volume
456

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

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

    
472
    retval[name] = size
473

    
474
  return retval
475

    
476

    
477
def BridgeExists(bridge):
478
  """Check whether the given bridge exists in the system
479

480
  Returns:
481
     True if it does, false otherwise.
482

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

    
486

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

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

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

497
  Return value
498
    - a copy of the list sorted according to our algorithm
499

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

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

    
520

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

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

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

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

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

    
555
    if not process_string in cmdline:
556
      return False
557

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

    
565
    if process_state == 'Z':
566
      return False
567

    
568
  except (IndexError, IOError, ValueError):
569
    return False
570

    
571
  return True
572

    
573

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

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

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

    
589

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

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

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

    
600

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

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

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

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

    
614

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

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

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

    
630

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

634
  Value needs to be passed as a numeric type. Return value is always a string.
635

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

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

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

    
646

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

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

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

    
658
  value = float(m.groups()[0])
659

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

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

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

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

    
676
  else:
677
    raise errors.UnitParseError("Unknown unit: %s" % unit)
678

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

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

    
688
  return value
689

    
690

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

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

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

    
717

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

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

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

    
738
        out.flush()
739
        os.rename(tmpname, file_name)
740
      finally:
741
        f.close()
742
    finally:
743
      out.close()
744
  except:
745
    RemoveFile(tmpname)
746
    raise
747

    
748

    
749
def AddEtcHostsEntry(file_name, hostname, ip):
750
  """
751

752
  """
753
  f = open(file_name, 'a+')
754
  try:
755
    nl = True
756
    for line in f:
757
      fields = line.split()
758
      if len(fields) < 2 or fields[0].startswith('#'):
759
        continue
760
      if fields[0] == ip and hostname in fields[1:]:
761
        break
762
      nl = line.endswith('\n')
763
    else:
764
      if not nl:
765
        f.write("\n")
766
      f.write(ip)
767
      f.write(' ')
768
      f.write(hostname)
769
      f.write("\n")
770
      f.flush()
771
  finally:
772
    f.close()
773

    
774

    
775
def RemoveEtcHostsEntry(file_name, hostname):
776
  """
777

778
  """
779
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
780
  try:
781
    out = os.fdopen(fd, 'w')
782
    try:
783
      f = open(file_name, 'r')
784
      try:
785
        for line in f:
786
          fields = line.split()
787
          if len(fields) > 1 and not fields[0].startswith('#'):
788
            names = fields[1:]
789
            if hostname in names:
790
              while hostname in names:
791
                names.remove(hostname)
792
              if names:
793
                out.write(fields[0])
794
                out.write(' ')
795
                out.write(' '.join(names))
796
              continue
797

    
798
          out.write(line)
799

    
800
        out.flush()
801
        os.rename(tmpname, file_name)
802
      finally:
803
        f.close()
804
    finally:
805
      out.close()
806
  except:
807
    RemoveFile(tmpname)
808
    raise
809

    
810

    
811
def CreateBackup(file_name):
812
  """Creates a backup of a file.
813

814
  Returns: the path to the newly created backup file.
815

816
  """
817
  if not os.path.isfile(file_name):
818
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
819
                                file_name)
820

    
821
  prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
822
  dir_name = os.path.dirname(file_name)
823

    
824
  fsrc = open(file_name, 'rb')
825
  try:
826
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
827
    fdst = os.fdopen(fd, 'wb')
828
    try:
829
      shutil.copyfileobj(fsrc, fdst)
830
    finally:
831
      fdst.close()
832
  finally:
833
    fsrc.close()
834

    
835
  return backup_name
836

    
837

    
838
def ShellQuote(value):
839
  """Quotes shell argument according to POSIX.
840

841
  """
842
  if _re_shell_unquoted.match(value):
843
    return value
844
  else:
845
    return "'%s'" % value.replace("'", "'\\''")
846

    
847

    
848
def ShellQuoteArgs(args):
849
  """Quotes all given shell arguments and concatenates using spaces.
850

851
  """
852
  return ' '.join([ShellQuote(i) for i in args])
853

    
854

    
855

    
856
def TcpPing(source, target, port, timeout=10, live_port_needed=False):
857
  """Simple ping implementation using TCP connect(2).
858

859
  Try to do a TCP connect(2) from the specified source IP to the specified
860
  target IP and the specified target port. If live_port_needed is set to true,
861
  requires the remote end to accept the connection. The timeout is specified
862
  in seconds and defaults to 10 seconds
863

864
  """
865
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
866

    
867
  sucess = False
868

    
869
  try:
870
    sock.bind((source, 0))
871
  except socket.error, (errcode, errstring):
872
    if errcode == errno.EADDRNOTAVAIL:
873
      success = False
874

    
875
  sock.settimeout(timeout)
876

    
877
  try:
878
    sock.connect((target, port))
879
    sock.close()
880
    success = True
881
  except socket.timeout:
882
    success = False
883
  except socket.error, (errcode, errstring):
884
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
885

    
886
  return success
887

    
888

    
889
def ListVisibleFiles(path):
890
  """Returns a list of all visible files in a directory.
891

892
  """
893
  return [i for i in os.listdir(path) if not i.startswith(".")]
894

    
895

    
896
def GetHomeDir(user, default=None):
897
  """Try to get the homedir of the given user.
898

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

903
  """
904
  try:
905
    if isinstance(user, basestring):
906
      result = pwd.getpwnam(user)
907
    elif isinstance(user, (int, long)):
908
      result = pwd.getpwuid(user)
909
    else:
910
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
911
                                   type(user))
912
  except KeyError:
913
    return default
914
  return result.pw_dir
915

    
916

    
917
def NewUUID():
918
  """Returns a random UUID.
919

920
  """
921
  f = open("/proc/sys/kernel/random/uuid", "r")
922
  try:
923
    return f.read(128).rstrip("\n")
924
  finally:
925
    f.close()
926

    
927

    
928
def WriteFile(file_name, fn=None, data=None,
929
              mode=None, uid=-1, gid=-1,
930
              atime=None, mtime=None):
931
  """(Over)write a file atomically.
932

933
  The file_name and either fn (a function taking one argument, the
934
  file descriptor, and which should write the data to it) or data (the
935
  contents of the file) must be passed. The other arguments are
936
  optional and allow setting the file mode, owner and group, and the
937
  mtime/atime of the file.
938

939
  If the function doesn't raise an exception, it has succeeded and the
940
  target file has the new contents. If the file has raised an
941
  exception, an existing target file should be unmodified and the
942
  temporary file should be removed.
943

944
  """
945
  if not os.path.isabs(file_name):
946
    raise errors.ProgrammerError("Path passed to WriteFile is not"
947
                                 " absolute: '%s'" % file_name)
948

    
949
  if [fn, data].count(None) != 1:
950
    raise errors.ProgrammerError("fn or data required")
951

    
952
  if [atime, mtime].count(None) == 1:
953
    raise errors.ProgrammerError("Both atime and mtime must be either"
954
                                 " set or None")
955

    
956

    
957
  dir_name, base_name = os.path.split(file_name)
958
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
959
  # here we need to make sure we remove the temp file, if any error
960
  # leaves it in place
961
  try:
962
    if uid != -1 or gid != -1:
963
      os.chown(new_name, uid, gid)
964
    if mode:
965
      os.chmod(new_name, mode)
966
    if data is not None:
967
      os.write(fd, data)
968
    else:
969
      fn(fd)
970
    os.fsync(fd)
971
    if atime is not None and mtime is not None:
972
      os.utime(new_name, (atime, mtime))
973
    os.rename(new_name, file_name)
974
  finally:
975
    os.close(fd)
976
    RemoveFile(new_name)
977

    
978

    
979
def all(seq, pred=bool):
980
  "Returns True if pred(x) is True for every element in the iterable"
981
  for elem in itertools.ifilterfalse(pred, seq):
982
    return False
983
  return True
984

    
985

    
986
def any(seq, pred=bool):
987
  "Returns True if pred(x) is True for at least one element in the iterable"
988
  for elem in itertools.ifilter(pred, seq):
989
    return True
990
  return False