Replace hardcoded lock dir
[ganeti-local] / lib / utils.py
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 from ganeti import constants
43
44
45 _locksheld = []
46 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
47
48 debug = False
49
50 class RunResult(object):
51   """Simple class for holding the result of running external programs.
52
53   Instance variables:
54     exit_code: the exit code of the program, or None (if the program
55                didn't exit())
56     signal: numeric signal that caused the program to finish, or None
57             (if the program wasn't terminated by a signal)
58     stdout: the standard output of the program
59     stderr: the standard error of the program
60     failed: a Boolean value which is True in case the program was
61             terminated by a signal or exited with a non-zero exit code
62     fail_reason: a string detailing the termination reason
63
64   """
65   __slots__ = ["exit_code", "signal", "stdout", "stderr",
66                "failed", "fail_reason", "cmd"]
67
68
69   def __init__(self, exit_code, signal, stdout, stderr, cmd):
70     self.cmd = cmd
71     self.exit_code = exit_code
72     self.signal = signal
73     self.stdout = stdout
74     self.stderr = stderr
75     self.failed = (signal is not None or exit_code != 0)
76
77     if self.signal is not None:
78       self.fail_reason = "terminated by signal %s" % self.signal
79     elif self.exit_code is not None:
80       self.fail_reason = "exited with exit code %s" % self.exit_code
81     else:
82       self.fail_reason = "unable to determine termination reason"
83
84     if debug and self.failed:
85       logger.Debug("Command '%s' failed (%s); output: %s" %
86                    (self.cmd, self.fail_reason, self.output))
87
88   def _GetOutput(self):
89     """Returns the combined stdout and stderr for easier usage.
90
91     """
92     return self.stdout + self.stderr
93
94   output = property(_GetOutput, None, None, "Return full output")
95
96
97 def _GetLockFile(subsystem):
98   """Compute the file name for a given lock name."""
99   return "%s/ganeti_lock_%s" % (constants.LOCK_DIR, subsystem)
100
101
102 def Lock(name, max_retries=None, debug=False):
103   """Lock a given subsystem.
104
105   In case the lock is already held by an alive process, the function
106   will sleep indefintely and poll with a one second interval.
107
108   When the optional integer argument 'max_retries' is passed with a
109   non-zero value, the function will sleep only for this number of
110   times, and then it will will raise a LockError if the lock can't be
111   acquired. Passing in a negative number will cause only one try to
112   get the lock. Passing a positive number will make the function retry
113   for approximately that number of seconds.
114
115   """
116   lockfile = _GetLockFile(name)
117
118   if name in _locksheld:
119     raise errors.LockError('Lock "%s" already held!' % (name,))
120
121   errcount = 0
122
123   retries = 0
124   while True:
125     try:
126       fd = os.open(lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_SYNC)
127       break
128     except OSError, creat_err:
129       if creat_err.errno != errno.EEXIST:
130         raise errors.LockError("Can't create the lock file. Error '%s'." %
131                                str(creat_err))
132
133       try:
134         pf = open(lockfile, 'r')
135       except IOError, open_err:
136         errcount += 1
137         if errcount >= 5:
138           raise errors.LockError("Lock file exists but cannot be opened."
139                                  " Error: '%s'." % str(open_err))
140         time.sleep(1)
141         continue
142
143       try:
144         pid = int(pf.read())
145       except ValueError:
146         raise errors.LockError("Invalid pid string in %s" %
147                                (lockfile,))
148
149       if not IsProcessAlive(pid):
150         raise errors.LockError("Stale lockfile %s for pid %d?" %
151                                (lockfile, pid))
152
153       if max_retries and max_retries <= retries:
154         raise errors.LockError("Can't acquire lock during the specified"
155                                " time, aborting.")
156       if retries == 5 and (debug or sys.stdin.isatty()):
157         logger.ToStderr("Waiting for '%s' lock from pid %d..." % (name, pid))
158
159       time.sleep(1)
160       retries += 1
161       continue
162
163   os.write(fd, '%d\n' % (os.getpid(),))
164   os.close(fd)
165
166   _locksheld.append(name)
167
168
169 def Unlock(name):
170   """Unlock a given subsystem.
171
172   """
173   lockfile = _GetLockFile(name)
174
175   try:
176     fd = os.open(lockfile, os.O_RDONLY)
177   except OSError:
178     raise errors.LockError('Lock "%s" not held.' % (name,))
179
180   f = os.fdopen(fd, 'r')
181   pid_str = f.read()
182
183   try:
184     pid = int(pid_str)
185   except ValueError:
186     raise errors.LockError('Unable to determine PID of locking process.')
187
188   if pid != os.getpid():
189     raise errors.LockError('Lock not held by me (%d != %d)' %
190                            (os.getpid(), pid,))
191
192   os.unlink(lockfile)
193   _locksheld.remove(name)
194
195
196 def LockCleanup():
197   """Remove all locks.
198
199   """
200   for lock in _locksheld:
201     Unlock(lock)
202
203
204 def RunCmd(cmd):
205   """Execute a (shell) command.
206
207   The command should not read from its standard input, as it will be
208   closed.
209
210   Args:
211     cmd: command to run. (str)
212
213   Returns: `RunResult` instance
214
215   """
216   if isinstance(cmd, list):
217     cmd = [str(val) for val in cmd]
218     strcmd = " ".join(cmd)
219     shell = False
220   else:
221     strcmd = cmd
222     shell = True
223   env = os.environ.copy()
224   env["LC_ALL"] = "C"
225   child = subprocess.Popen(cmd, shell=shell,
226                            stderr=subprocess.PIPE,
227                            stdout=subprocess.PIPE,
228                            stdin=subprocess.PIPE,
229                            close_fds=True, env=env)
230
231   child.stdin.close()
232   out = child.stdout.read()
233   err = child.stderr.read()
234
235   status = child.wait()
236   if status >= 0:
237     exitcode = status
238     signal = None
239   else:
240     exitcode = None
241     signal = -status
242
243   return RunResult(exitcode, signal, out, err, strcmd)
244
245
246 def RunCmdUnlocked(cmd):
247   """Execute a shell command without the 'cmd' lock.
248
249   This variant of `RunCmd()` drops the 'cmd' lock before running the
250   command and re-aquires it afterwards, thus it can be used to call
251   other ganeti commands.
252
253   The argument and return values are the same as for the `RunCmd()`
254   function.
255
256   Args:
257     cmd - command to run. (str)
258
259   Returns:
260     `RunResult`
261
262   """
263   Unlock('cmd')
264   ret = RunCmd(cmd)
265   Lock('cmd')
266
267   return ret
268
269
270 def RemoveFile(filename):
271   """Remove a file ignoring some errors.
272
273   Remove a file, ignoring non-existing ones or directories. Other
274   errors are passed.
275
276   """
277   try:
278     os.unlink(filename)
279   except OSError, err:
280     if err.errno not in (errno.ENOENT, errno.EISDIR):
281       raise
282
283
284 def _FingerprintFile(filename):
285   """Compute the fingerprint of a file.
286
287   If the file does not exist, a None will be returned
288   instead.
289
290   Args:
291     filename - Filename (str)
292
293   """
294   if not (os.path.exists(filename) and os.path.isfile(filename)):
295     return None
296
297   f = open(filename)
298
299   fp = sha.sha()
300   while True:
301     data = f.read(4096)
302     if not data:
303       break
304
305     fp.update(data)
306
307   return fp.hexdigest()
308
309
310 def FingerprintFiles(files):
311   """Compute fingerprints for a list of files.
312
313   Args:
314     files - array of filenames.  ( [str, ...] )
315
316   Return value:
317     dictionary of filename: fingerprint for the files that exist
318
319   """
320   ret = {}
321
322   for filename in files:
323     cksum = _FingerprintFile(filename)
324     if cksum:
325       ret[filename] = cksum
326
327   return ret
328
329
330 def CheckDict(target, template, logname=None):
331   """Ensure a dictionary has a required set of keys.
332
333   For the given dictionaries `target` and `template`, ensure target
334   has all the keys from template. Missing keys are added with values
335   from template.
336
337   Args:
338     target   - the dictionary to check
339     template - template dictionary
340     logname  - a caller-chosen string to identify the debug log
341                entry; if None, no logging will be done
342
343   Returns value:
344     None
345
346   """
347   missing = []
348   for k in template:
349     if k not in target:
350       missing.append(k)
351       target[k] = template[k]
352
353   if missing and logname:
354     logger.Debug('%s missing keys %s' %
355                  (logname, ', '.join(missing)))
356
357
358 def IsProcessAlive(pid):
359   """Check if a given pid exists on the system.
360
361   Returns: true or false, depending on if the pid exists or not
362
363   Remarks: zombie processes treated as not alive
364
365   """
366   try:
367     f = open("/proc/%d/status" % pid)
368   except IOError, err:
369     if err.errno in (errno.ENOENT, errno.ENOTDIR):
370       return False
371
372   alive = True
373   try:
374     data = f.readlines()
375     if len(data) > 1:
376       state = data[1].split()
377       if len(state) > 1 and state[1] == "Z":
378         alive = False
379   finally:
380     f.close()
381
382   return alive
383
384
385 def MatchNameComponent(key, name_list):
386   """Try to match a name against a list.
387
388   This function will try to match a name like test1 against a list
389   like ['test1.example.com', 'test2.example.com', ...]. Against this
390   list, 'test1' as well as 'test1.example' will match, but not
391   'test1.ex'. A multiple match will be considered as no match at all
392   (e.g. 'test1' against ['test1.example.com', 'test1.example.org']).
393
394   Args:
395     key: the name to be searched
396     name_list: the list of strings against which to search the key
397
398   Returns:
399     None if there is no match *or* if there are multiple matches
400     otherwise the element from the list which matches
401
402   """
403   mo = re.compile("^%s(\..*)?$" % re.escape(key))
404   names_filtered = [name for name in name_list if mo.match(name) is not None]
405   if len(names_filtered) != 1:
406     return None
407   return names_filtered[0]
408
409
410 class HostInfo:
411   """Class implementing resolver and hostname functionality
412
413   """
414   def __init__(self, name=None):
415     """Initialize the host name object.
416
417     If the name argument is not passed, it will use this system's
418     name.
419
420     """
421     if name is None:
422       name = self.SysName()
423
424     self.query = name
425     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
426     self.ip = self.ipaddrs[0]
427
428   def ShortName(self):
429     """Returns the hostname without domain.
430
431     """
432     return self.name.split('.')[0]
433
434   @staticmethod
435   def SysName():
436     """Return the current system's name.
437
438     This is simply a wrapper over socket.gethostname()
439
440     """
441     return socket.gethostname()
442
443   @staticmethod
444   def LookupHostname(hostname):
445     """Look up hostname
446
447     Args:
448       hostname: hostname to look up
449
450     Returns:
451       a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
452       in case of errors in resolving, we raise a ResolverError
453
454     """
455     try:
456       result = socket.gethostbyname_ex(hostname)
457     except socket.gaierror, err:
458       # hostname not found in DNS
459       raise errors.ResolverError(hostname, err.args[0], err.args[1])
460
461     return result
462
463
464 def ListVolumeGroups():
465   """List volume groups and their size
466
467   Returns:
468      Dictionary with keys volume name and values the size of the volume
469
470   """
471   command = "vgs --noheadings --units m --nosuffix -o name,size"
472   result = RunCmd(command)
473   retval = {}
474   if result.failed:
475     return retval
476
477   for line in result.stdout.splitlines():
478     try:
479       name, size = line.split()
480       size = int(float(size))
481     except (IndexError, ValueError), err:
482       logger.Error("Invalid output from vgs (%s): %s" % (err, line))
483       continue
484
485     retval[name] = size
486
487   return retval
488
489
490 def BridgeExists(bridge):
491   """Check whether the given bridge exists in the system
492
493   Returns:
494      True if it does, false otherwise.
495
496   """
497   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
498
499
500 def NiceSort(name_list):
501   """Sort a list of strings based on digit and non-digit groupings.
502
503   Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
504   sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
505
506   The sort algorithm breaks each name in groups of either only-digits
507   or no-digits. Only the first eight such groups are considered, and
508   after that we just use what's left of the string.
509
510   Return value
511     - a copy of the list sorted according to our algorithm
512
513   """
514   _SORTER_BASE = "(\D+|\d+)"
515   _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
516                                                   _SORTER_BASE, _SORTER_BASE,
517                                                   _SORTER_BASE, _SORTER_BASE,
518                                                   _SORTER_BASE, _SORTER_BASE)
519   _SORTER_RE = re.compile(_SORTER_FULL)
520   _SORTER_NODIGIT = re.compile("^\D*$")
521   def _TryInt(val):
522     """Attempts to convert a variable to integer."""
523     if val is None or _SORTER_NODIGIT.match(val):
524       return val
525     rval = int(val)
526     return rval
527
528   to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
529              for name in name_list]
530   to_sort.sort()
531   return [tup[1] for tup in to_sort]
532
533
534 def CheckDaemonAlive(pid_file, process_string):
535   """Check wether the specified daemon is alive.
536
537   Args:
538    - pid_file: file to read the daemon pid from, the file is
539                expected to contain only a single line containing
540                only the PID
541    - process_string: a substring that we expect to find in
542                      the command line of the daemon process
543
544   Returns:
545    - True if the daemon is judged to be alive (that is:
546       - the PID file exists, is readable and contains a number
547       - a process of the specified PID is running
548       - that process contains the specified string in its
549         command line
550       - the process is not in state Z (zombie))
551    - False otherwise
552
553   """
554   try:
555     pid_file = file(pid_file, 'r')
556     try:
557       pid = int(pid_file.readline())
558     finally:
559       pid_file.close()
560
561     cmdline_file_path = "/proc/%s/cmdline" % (pid)
562     cmdline_file = open(cmdline_file_path, 'r')
563     try:
564       cmdline = cmdline_file.readline()
565     finally:
566       cmdline_file.close()
567
568     if not process_string in cmdline:
569       return False
570
571     stat_file_path =  "/proc/%s/stat" % (pid)
572     stat_file = open(stat_file_path, 'r')
573     try:
574       process_state = stat_file.readline().split()[2]
575     finally:
576       stat_file.close()
577
578     if process_state == 'Z':
579       return False
580
581   except (IndexError, IOError, ValueError):
582     return False
583
584   return True
585
586
587 def TryConvert(fn, val):
588   """Try to convert a value ignoring errors.
589
590   This function tries to apply function `fn` to `val`. If no
591   ValueError or TypeError exceptions are raised, it will return the
592   result, else it will return the original value. Any other exceptions
593   are propagated to the caller.
594
595   """
596   try:
597     nv = fn(val)
598   except (ValueError, TypeError), err:
599     nv = val
600   return nv
601
602
603 def IsValidIP(ip):
604   """Verifies the syntax of an IP address.
605
606   This function checks if the ip address passes is valid or not based
607   on syntax (not ip range, class calculations or anything).
608
609   """
610   unit = "(0|[1-9]\d{0,2})"
611   return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
612
613
614 def IsValidShellParam(word):
615   """Verifies is the given word is safe from the shell's p.o.v.
616
617   This means that we can pass this to a command via the shell and be
618   sure that it doesn't alter the command line and is passed as such to
619   the actual command.
620
621   Note that we are overly restrictive here, in order to be on the safe
622   side.
623
624   """
625   return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
626
627
628 def BuildShellCmd(template, *args):
629   """Build a safe shell command line from the given arguments.
630
631   This function will check all arguments in the args list so that they
632   are valid shell parameters (i.e. they don't contain shell
633   metacharaters). If everything is ok, it will return the result of
634   template % args.
635
636   """
637   for word in args:
638     if not IsValidShellParam(word):
639       raise errors.ProgrammerError("Shell argument '%s' contains"
640                                    " invalid characters" % word)
641   return template % args
642
643
644 def FormatUnit(value):
645   """Formats an incoming number of MiB with the appropriate unit.
646
647   Value needs to be passed as a numeric type. Return value is always a string.
648
649   """
650   if value < 1024:
651     return "%dM" % round(value, 0)
652
653   elif value < (1024 * 1024):
654     return "%0.1fG" % round(float(value) / 1024, 1)
655
656   else:
657     return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
658
659
660 def ParseUnit(input_string):
661   """Tries to extract number and scale from the given string.
662
663   Input must be in the format NUMBER+ [DOT NUMBER+] SPACE* [UNIT]. If no unit
664   is specified, it defaults to MiB. Return value is always an int in MiB.
665
666   """
667   m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
668   if not m:
669     raise errors.UnitParseError("Invalid format")
670
671   value = float(m.groups()[0])
672
673   unit = m.groups()[1]
674   if unit:
675     lcunit = unit.lower()
676   else:
677     lcunit = 'm'
678
679   if lcunit in ('m', 'mb', 'mib'):
680     # Value already in MiB
681     pass
682
683   elif lcunit in ('g', 'gb', 'gib'):
684     value *= 1024
685
686   elif lcunit in ('t', 'tb', 'tib'):
687     value *= 1024 * 1024
688
689   else:
690     raise errors.UnitParseError("Unknown unit: %s" % unit)
691
692   # Make sure we round up
693   if int(value) < value:
694     value += 1
695
696   # Round up to the next multiple of 4
697   value = int(value)
698   if value % 4:
699     value += 4 - value % 4
700
701   return value
702
703
704 def AddAuthorizedKey(file_name, key):
705   """Adds an SSH public key to an authorized_keys file.
706
707   Args:
708     file_name: Path to authorized_keys file
709     key: String containing key
710   """
711   key_fields = key.split()
712
713   f = open(file_name, 'a+')
714   try:
715     nl = True
716     for line in f:
717       # Ignore whitespace changes
718       if line.split() == key_fields:
719         break
720       nl = line.endswith('\n')
721     else:
722       if not nl:
723         f.write("\n")
724       f.write(key.rstrip('\r\n'))
725       f.write("\n")
726       f.flush()
727   finally:
728     f.close()
729
730
731 def RemoveAuthorizedKey(file_name, key):
732   """Removes an SSH public key from an authorized_keys file.
733
734   Args:
735     file_name: Path to authorized_keys file
736     key: String containing key
737   """
738   key_fields = key.split()
739
740   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
741   try:
742     out = os.fdopen(fd, 'w')
743     try:
744       f = open(file_name, 'r')
745       try:
746         for line in f:
747           # Ignore whitespace changes while comparing lines
748           if line.split() != key_fields:
749             out.write(line)
750
751         out.flush()
752         os.rename(tmpname, file_name)
753       finally:
754         f.close()
755     finally:
756       out.close()
757   except:
758     RemoveFile(tmpname)
759     raise
760
761
762 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
763   """Sets the name of an IP address and hostname in /etc/hosts.
764
765   """
766   # Ensure aliases are unique
767   aliases = UniqueSequence([hostname] + aliases)[1:]
768
769   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
770   try:
771     out = os.fdopen(fd, 'w')
772     try:
773       f = open(file_name, 'r')
774       try:
775         written = False
776         for line in f:
777           fields = line.split()
778           if fields and not fields[0].startswith('#') and ip == fields[0]:
779             continue
780           out.write(line)
781
782         out.write("%s\t%s" % (ip, hostname))
783         if aliases:
784           out.write(" %s" % ' '.join(aliases))
785         out.write('\n')
786
787         out.flush()
788         os.fsync(out)
789         os.rename(tmpname, file_name)
790       finally:
791         f.close()
792     finally:
793       out.close()
794   except:
795     RemoveFile(tmpname)
796     raise
797
798
799 def RemoveEtcHostsEntry(file_name, hostname):
800   """Removes a hostname from /etc/hosts.
801
802   IP addresses without names are removed from the file.
803   """
804   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
805   try:
806     out = os.fdopen(fd, 'w')
807     try:
808       f = open(file_name, 'r')
809       try:
810         for line in f:
811           fields = line.split()
812           if len(fields) > 1 and not fields[0].startswith('#'):
813             names = fields[1:]
814             if hostname in names:
815               while hostname in names:
816                 names.remove(hostname)
817               if names:
818                 out.write("%s %s\n" % (fields[0], ' '.join(names)))
819               continue
820
821           out.write(line)
822
823         out.flush()
824         os.fsync(out)
825         os.rename(tmpname, file_name)
826       finally:
827         f.close()
828     finally:
829       out.close()
830   except:
831     RemoveFile(tmpname)
832     raise
833
834
835 def CreateBackup(file_name):
836   """Creates a backup of a file.
837
838   Returns: the path to the newly created backup file.
839
840   """
841   if not os.path.isfile(file_name):
842     raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
843                                 file_name)
844
845   prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
846   dir_name = os.path.dirname(file_name)
847
848   fsrc = open(file_name, 'rb')
849   try:
850     (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
851     fdst = os.fdopen(fd, 'wb')
852     try:
853       shutil.copyfileobj(fsrc, fdst)
854     finally:
855       fdst.close()
856   finally:
857     fsrc.close()
858
859   return backup_name
860
861
862 def ShellQuote(value):
863   """Quotes shell argument according to POSIX.
864
865   """
866   if _re_shell_unquoted.match(value):
867     return value
868   else:
869     return "'%s'" % value.replace("'", "'\\''")
870
871
872 def ShellQuoteArgs(args):
873   """Quotes all given shell arguments and concatenates using spaces.
874
875   """
876   return ' '.join([ShellQuote(i) for i in args])
877
878
879
880 def TcpPing(source, target, port, timeout=10, live_port_needed=False):
881   """Simple ping implementation using TCP connect(2).
882
883   Try to do a TCP connect(2) from the specified source IP to the specified
884   target IP and the specified target port. If live_port_needed is set to true,
885   requires the remote end to accept the connection. The timeout is specified
886   in seconds and defaults to 10 seconds
887
888   """
889   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
890
891   sucess = False
892
893   try:
894     sock.bind((source, 0))
895   except socket.error, (errcode, errstring):
896     if errcode == errno.EADDRNOTAVAIL:
897       success = False
898
899   sock.settimeout(timeout)
900
901   try:
902     sock.connect((target, port))
903     sock.close()
904     success = True
905   except socket.timeout:
906     success = False
907   except socket.error, (errcode, errstring):
908     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
909
910   return success
911
912
913 def ListVisibleFiles(path):
914   """Returns a list of all visible files in a directory.
915
916   """
917   files = [i for i in os.listdir(path) if not i.startswith(".")]
918   files.sort()
919   return files
920
921
922 def GetHomeDir(user, default=None):
923   """Try to get the homedir of the given user.
924
925   The user can be passed either as a string (denoting the name) or as
926   an integer (denoting the user id). If the user is not found, the
927   'default' argument is returned, which defaults to None.
928
929   """
930   try:
931     if isinstance(user, basestring):
932       result = pwd.getpwnam(user)
933     elif isinstance(user, (int, long)):
934       result = pwd.getpwuid(user)
935     else:
936       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
937                                    type(user))
938   except KeyError:
939     return default
940   return result.pw_dir
941
942
943 def NewUUID():
944   """Returns a random UUID.
945
946   """
947   f = open("/proc/sys/kernel/random/uuid", "r")
948   try:
949     return f.read(128).rstrip("\n")
950   finally:
951     f.close()
952
953
954 def WriteFile(file_name, fn=None, data=None,
955               mode=None, uid=-1, gid=-1,
956               atime=None, mtime=None):
957   """(Over)write a file atomically.
958
959   The file_name and either fn (a function taking one argument, the
960   file descriptor, and which should write the data to it) or data (the
961   contents of the file) must be passed. The other arguments are
962   optional and allow setting the file mode, owner and group, and the
963   mtime/atime of the file.
964
965   If the function doesn't raise an exception, it has succeeded and the
966   target file has the new contents. If the file has raised an
967   exception, an existing target file should be unmodified and the
968   temporary file should be removed.
969
970   """
971   if not os.path.isabs(file_name):
972     raise errors.ProgrammerError("Path passed to WriteFile is not"
973                                  " absolute: '%s'" % file_name)
974
975   if [fn, data].count(None) != 1:
976     raise errors.ProgrammerError("fn or data required")
977
978   if [atime, mtime].count(None) == 1:
979     raise errors.ProgrammerError("Both atime and mtime must be either"
980                                  " set or None")
981
982
983   dir_name, base_name = os.path.split(file_name)
984   fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
985   # here we need to make sure we remove the temp file, if any error
986   # leaves it in place
987   try:
988     if uid != -1 or gid != -1:
989       os.chown(new_name, uid, gid)
990     if mode:
991       os.chmod(new_name, mode)
992     if data is not None:
993       os.write(fd, data)
994     else:
995       fn(fd)
996     os.fsync(fd)
997     if atime is not None and mtime is not None:
998       os.utime(new_name, (atime, mtime))
999     os.rename(new_name, file_name)
1000   finally:
1001     os.close(fd)
1002     RemoveFile(new_name)
1003
1004
1005 def all(seq, pred=bool):
1006   "Returns True if pred(x) is True for every element in the iterable"
1007   for elem in itertools.ifilterfalse(pred, seq):
1008     return False
1009   return True
1010
1011
1012 def any(seq, pred=bool):
1013   "Returns True if pred(x) is True for at least one element in the iterable"
1014   for elem in itertools.ifilter(pred, seq):
1015     return True
1016   return False
1017
1018
1019 def UniqueSequence(seq):
1020   """Returns a list with unique elements.
1021
1022   Element order is preserved.
1023   """
1024   seen = set()
1025   return [i for i in seq if i not in seen and not seen.add(i)]