Sort list returned by ListVisibleFiles.
[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
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   def ShortName(self):
422     """Returns the hostname without domain.
423
424     """
425     return self.name.split('.')[0]
426
427   @staticmethod
428   def SysName():
429     """Return the current system's name.
430
431     This is simply a wrapper over socket.gethostname()
432
433     """
434     return socket.gethostname()
435
436   @staticmethod
437   def LookupHostname(hostname):
438     """Look up hostname
439
440     Args:
441       hostname: hostname to look up
442
443     Returns:
444       a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
445       in case of errors in resolving, we raise a ResolverError
446
447     """
448     try:
449       result = socket.gethostbyname_ex(hostname)
450     except socket.gaierror, err:
451       # hostname not found in DNS
452       raise errors.ResolverError(hostname, err.args[0], err.args[1])
453
454     return result
455
456
457 def ListVolumeGroups():
458   """List volume groups and their size
459
460   Returns:
461      Dictionary with keys volume name and values the size of the volume
462
463   """
464   command = "vgs --noheadings --units m --nosuffix -o name,size"
465   result = RunCmd(command)
466   retval = {}
467   if result.failed:
468     return retval
469
470   for line in result.stdout.splitlines():
471     try:
472       name, size = line.split()
473       size = int(float(size))
474     except (IndexError, ValueError), err:
475       logger.Error("Invalid output from vgs (%s): %s" % (err, line))
476       continue
477
478     retval[name] = size
479
480   return retval
481
482
483 def BridgeExists(bridge):
484   """Check whether the given bridge exists in the system
485
486   Returns:
487      True if it does, false otherwise.
488
489   """
490   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
491
492
493 def NiceSort(name_list):
494   """Sort a list of strings based on digit and non-digit groupings.
495
496   Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
497   sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
498
499   The sort algorithm breaks each name in groups of either only-digits
500   or no-digits. Only the first eight such groups are considered, and
501   after that we just use what's left of the string.
502
503   Return value
504     - a copy of the list sorted according to our algorithm
505
506   """
507   _SORTER_BASE = "(\D+|\d+)"
508   _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
509                                                   _SORTER_BASE, _SORTER_BASE,
510                                                   _SORTER_BASE, _SORTER_BASE,
511                                                   _SORTER_BASE, _SORTER_BASE)
512   _SORTER_RE = re.compile(_SORTER_FULL)
513   _SORTER_NODIGIT = re.compile("^\D*$")
514   def _TryInt(val):
515     """Attempts to convert a variable to integer."""
516     if val is None or _SORTER_NODIGIT.match(val):
517       return val
518     rval = int(val)
519     return rval
520
521   to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
522              for name in name_list]
523   to_sort.sort()
524   return [tup[1] for tup in to_sort]
525
526
527 def CheckDaemonAlive(pid_file, process_string):
528   """Check wether the specified daemon is alive.
529
530   Args:
531    - pid_file: file to read the daemon pid from, the file is
532                expected to contain only a single line containing
533                only the PID
534    - process_string: a substring that we expect to find in
535                      the command line of the daemon process
536
537   Returns:
538    - True if the daemon is judged to be alive (that is:
539       - the PID file exists, is readable and contains a number
540       - a process of the specified PID is running
541       - that process contains the specified string in its
542         command line
543       - the process is not in state Z (zombie))
544    - False otherwise
545
546   """
547   try:
548     pid_file = file(pid_file, 'r')
549     try:
550       pid = int(pid_file.readline())
551     finally:
552       pid_file.close()
553
554     cmdline_file_path = "/proc/%s/cmdline" % (pid)
555     cmdline_file = open(cmdline_file_path, 'r')
556     try:
557       cmdline = cmdline_file.readline()
558     finally:
559       cmdline_file.close()
560
561     if not process_string in cmdline:
562       return False
563
564     stat_file_path =  "/proc/%s/stat" % (pid)
565     stat_file = open(stat_file_path, 'r')
566     try:
567       process_state = stat_file.readline().split()[2]
568     finally:
569       stat_file.close()
570
571     if process_state == 'Z':
572       return False
573
574   except (IndexError, IOError, ValueError):
575     return False
576
577   return True
578
579
580 def TryConvert(fn, val):
581   """Try to convert a value ignoring errors.
582
583   This function tries to apply function `fn` to `val`. If no
584   ValueError or TypeError exceptions are raised, it will return the
585   result, else it will return the original value. Any other exceptions
586   are propagated to the caller.
587
588   """
589   try:
590     nv = fn(val)
591   except (ValueError, TypeError), err:
592     nv = val
593   return nv
594
595
596 def IsValidIP(ip):
597   """Verifies the syntax of an IP address.
598
599   This function checks if the ip address passes is valid or not based
600   on syntax (not ip range, class calculations or anything).
601
602   """
603   unit = "(0|[1-9]\d{0,2})"
604   return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
605
606
607 def IsValidShellParam(word):
608   """Verifies is the given word is safe from the shell's p.o.v.
609
610   This means that we can pass this to a command via the shell and be
611   sure that it doesn't alter the command line and is passed as such to
612   the actual command.
613
614   Note that we are overly restrictive here, in order to be on the safe
615   side.
616
617   """
618   return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
619
620
621 def BuildShellCmd(template, *args):
622   """Build a safe shell command line from the given arguments.
623
624   This function will check all arguments in the args list so that they
625   are valid shell parameters (i.e. they don't contain shell
626   metacharaters). If everything is ok, it will return the result of
627   template % args.
628
629   """
630   for word in args:
631     if not IsValidShellParam(word):
632       raise errors.ProgrammerError("Shell argument '%s' contains"
633                                    " invalid characters" % word)
634   return template % args
635
636
637 def FormatUnit(value):
638   """Formats an incoming number of MiB with the appropriate unit.
639
640   Value needs to be passed as a numeric type. Return value is always a string.
641
642   """
643   if value < 1024:
644     return "%dM" % round(value, 0)
645
646   elif value < (1024 * 1024):
647     return "%0.1fG" % round(float(value) / 1024, 1)
648
649   else:
650     return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
651
652
653 def ParseUnit(input_string):
654   """Tries to extract number and scale from the given string.
655
656   Input must be in the format NUMBER+ [DOT NUMBER+] SPACE* [UNIT]. If no unit
657   is specified, it defaults to MiB. Return value is always an int in MiB.
658
659   """
660   m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
661   if not m:
662     raise errors.UnitParseError("Invalid format")
663
664   value = float(m.groups()[0])
665
666   unit = m.groups()[1]
667   if unit:
668     lcunit = unit.lower()
669   else:
670     lcunit = 'm'
671
672   if lcunit in ('m', 'mb', 'mib'):
673     # Value already in MiB
674     pass
675
676   elif lcunit in ('g', 'gb', 'gib'):
677     value *= 1024
678
679   elif lcunit in ('t', 'tb', 'tib'):
680     value *= 1024 * 1024
681
682   else:
683     raise errors.UnitParseError("Unknown unit: %s" % unit)
684
685   # Make sure we round up
686   if int(value) < value:
687     value += 1
688
689   # Round up to the next multiple of 4
690   value = int(value)
691   if value % 4:
692     value += 4 - value % 4
693
694   return value
695
696
697 def AddAuthorizedKey(file_name, key):
698   """Adds an SSH public key to an authorized_keys file.
699
700   Args:
701     file_name: Path to authorized_keys file
702     key: String containing key
703   """
704   key_fields = key.split()
705
706   f = open(file_name, 'a+')
707   try:
708     nl = True
709     for line in f:
710       # Ignore whitespace changes
711       if line.split() == key_fields:
712         break
713       nl = line.endswith('\n')
714     else:
715       if not nl:
716         f.write("\n")
717       f.write(key.rstrip('\r\n'))
718       f.write("\n")
719       f.flush()
720   finally:
721     f.close()
722
723
724 def RemoveAuthorizedKey(file_name, key):
725   """Removes an SSH public key from an authorized_keys file.
726
727   Args:
728     file_name: Path to authorized_keys file
729     key: String containing key
730   """
731   key_fields = key.split()
732
733   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
734   try:
735     out = os.fdopen(fd, 'w')
736     try:
737       f = open(file_name, 'r')
738       try:
739         for line in f:
740           # Ignore whitespace changes while comparing lines
741           if line.split() != key_fields:
742             out.write(line)
743
744         out.flush()
745         os.rename(tmpname, file_name)
746       finally:
747         f.close()
748     finally:
749       out.close()
750   except:
751     RemoveFile(tmpname)
752     raise
753
754
755 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
756   """Sets the name of an IP address and hostname in /etc/hosts.
757
758   """
759   # Ensure aliases are unique
760   aliases = UniqueSequence([hostname] + aliases)[1:]
761
762   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
763   try:
764     out = os.fdopen(fd, 'w')
765     try:
766       f = open(file_name, 'r')
767       try:
768         written = False
769         for line in f:
770           fields = line.split()
771           if fields and not fields[0].startswith('#') and ip == fields[0]:
772             continue
773           out.write(line)
774
775         out.write("%s\t%s" % (ip, hostname))
776         if aliases:
777           out.write(" %s" % ' '.join(aliases))
778         out.write('\n')
779
780         out.flush()
781         os.fsync(out)
782         os.rename(tmpname, file_name)
783       finally:
784         f.close()
785     finally:
786       out.close()
787   except:
788     RemoveFile(tmpname)
789     raise
790
791
792 def RemoveEtcHostsEntry(file_name, hostname):
793   """Removes a hostname from /etc/hosts.
794
795   IP addresses without names are removed from the file.
796   """
797   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
798   try:
799     out = os.fdopen(fd, 'w')
800     try:
801       f = open(file_name, 'r')
802       try:
803         for line in f:
804           fields = line.split()
805           if len(fields) > 1 and not fields[0].startswith('#'):
806             names = fields[1:]
807             if hostname in names:
808               while hostname in names:
809                 names.remove(hostname)
810               if names:
811                 out.write("%s %s\n" % (fields[0], ' '.join(names)))
812               continue
813
814           out.write(line)
815
816         out.flush()
817         os.fsync(out)
818         os.rename(tmpname, file_name)
819       finally:
820         f.close()
821     finally:
822       out.close()
823   except:
824     RemoveFile(tmpname)
825     raise
826
827
828 def CreateBackup(file_name):
829   """Creates a backup of a file.
830
831   Returns: the path to the newly created backup file.
832
833   """
834   if not os.path.isfile(file_name):
835     raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
836                                 file_name)
837
838   prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
839   dir_name = os.path.dirname(file_name)
840
841   fsrc = open(file_name, 'rb')
842   try:
843     (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
844     fdst = os.fdopen(fd, 'wb')
845     try:
846       shutil.copyfileobj(fsrc, fdst)
847     finally:
848       fdst.close()
849   finally:
850     fsrc.close()
851
852   return backup_name
853
854
855 def ShellQuote(value):
856   """Quotes shell argument according to POSIX.
857
858   """
859   if _re_shell_unquoted.match(value):
860     return value
861   else:
862     return "'%s'" % value.replace("'", "'\\''")
863
864
865 def ShellQuoteArgs(args):
866   """Quotes all given shell arguments and concatenates using spaces.
867
868   """
869   return ' '.join([ShellQuote(i) for i in args])
870
871
872
873 def TcpPing(source, target, port, timeout=10, live_port_needed=False):
874   """Simple ping implementation using TCP connect(2).
875
876   Try to do a TCP connect(2) from the specified source IP to the specified
877   target IP and the specified target port. If live_port_needed is set to true,
878   requires the remote end to accept the connection. The timeout is specified
879   in seconds and defaults to 10 seconds
880
881   """
882   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
883
884   sucess = False
885
886   try:
887     sock.bind((source, 0))
888   except socket.error, (errcode, errstring):
889     if errcode == errno.EADDRNOTAVAIL:
890       success = False
891
892   sock.settimeout(timeout)
893
894   try:
895     sock.connect((target, port))
896     sock.close()
897     success = True
898   except socket.timeout:
899     success = False
900   except socket.error, (errcode, errstring):
901     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
902
903   return success
904
905
906 def ListVisibleFiles(path):
907   """Returns a list of all visible files in a directory.
908
909   """
910   files = [i for i in os.listdir(path) if not i.startswith(".")]
911   files.sort()
912   return files
913
914
915 def GetHomeDir(user, default=None):
916   """Try to get the homedir of the given user.
917
918   The user can be passed either as a string (denoting the name) or as
919   an integer (denoting the user id). If the user is not found, the
920   'default' argument is returned, which defaults to None.
921
922   """
923   try:
924     if isinstance(user, basestring):
925       result = pwd.getpwnam(user)
926     elif isinstance(user, (int, long)):
927       result = pwd.getpwuid(user)
928     else:
929       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
930                                    type(user))
931   except KeyError:
932     return default
933   return result.pw_dir
934
935
936 def NewUUID():
937   """Returns a random UUID.
938
939   """
940   f = open("/proc/sys/kernel/random/uuid", "r")
941   try:
942     return f.read(128).rstrip("\n")
943   finally:
944     f.close()
945
946
947 def WriteFile(file_name, fn=None, data=None,
948               mode=None, uid=-1, gid=-1,
949               atime=None, mtime=None):
950   """(Over)write a file atomically.
951
952   The file_name and either fn (a function taking one argument, the
953   file descriptor, and which should write the data to it) or data (the
954   contents of the file) must be passed. The other arguments are
955   optional and allow setting the file mode, owner and group, and the
956   mtime/atime of the file.
957
958   If the function doesn't raise an exception, it has succeeded and the
959   target file has the new contents. If the file has raised an
960   exception, an existing target file should be unmodified and the
961   temporary file should be removed.
962
963   """
964   if not os.path.isabs(file_name):
965     raise errors.ProgrammerError("Path passed to WriteFile is not"
966                                  " absolute: '%s'" % file_name)
967
968   if [fn, data].count(None) != 1:
969     raise errors.ProgrammerError("fn or data required")
970
971   if [atime, mtime].count(None) == 1:
972     raise errors.ProgrammerError("Both atime and mtime must be either"
973                                  " set or None")
974
975
976   dir_name, base_name = os.path.split(file_name)
977   fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
978   # here we need to make sure we remove the temp file, if any error
979   # leaves it in place
980   try:
981     if uid != -1 or gid != -1:
982       os.chown(new_name, uid, gid)
983     if mode:
984       os.chmod(new_name, mode)
985     if data is not None:
986       os.write(fd, data)
987     else:
988       fn(fd)
989     os.fsync(fd)
990     if atime is not None and mtime is not None:
991       os.utime(new_name, (atime, mtime))
992     os.rename(new_name, file_name)
993   finally:
994     os.close(fd)
995     RemoveFile(new_name)
996
997
998 def all(seq, pred=bool):
999   "Returns True if pred(x) is True for every element in the iterable"
1000   for elem in itertools.ifilterfalse(pred, seq):
1001     return False
1002   return True
1003
1004
1005 def any(seq, pred=bool):
1006   "Returns True if pred(x) is True for at least one element in the iterable"
1007   for elem in itertools.ifilter(pred, seq):
1008     return True
1009   return False
1010
1011
1012 def UniqueSequence(seq):
1013   """Returns a list with unique elements.
1014
1015   Element order is preserved.
1016   """
1017   seen = set()
1018   return [i for i in seq if i not in seen and not seen.add(i)]