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