utils.KillProcess: Use waitpid() to wait for child processes
[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 utility module.
23
24 This module holds functions that can be used in both daemons (all) and
25 the command line scripts.
26
27 """
28
29
30 import sys
31 import os
32 import sha
33 import time
34 import subprocess
35 import re
36 import socket
37 import tempfile
38 import shutil
39 import errno
40 import pwd
41 import itertools
42 import select
43 import fcntl
44 import resource
45 import logging
46 import signal
47
48 from cStringIO import StringIO
49
50 from ganeti import errors
51 from ganeti import constants
52
53
54 _locksheld = []
55 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
56
57 debug = False
58 debug_locks = False
59
60 #: when set to True, L{RunCmd} is disabled
61 no_fork = False
62
63
64 class RunResult(object):
65   """Holds the result of running external programs.
66
67   @type exit_code: int
68   @ivar exit_code: the exit code of the program, or None (if the program
69       didn't exit())
70   @type signal: int or None
71   @ivar signal: the signal that caused the program to finish, or None
72       (if the program wasn't terminated by a signal)
73   @type stdout: str
74   @ivar stdout: the standard output of the program
75   @type stderr: str
76   @ivar stderr: the standard error of the program
77   @type failed: boolean
78   @ivar failed: True in case the program was
79       terminated by a signal or exited with a non-zero exit code
80   @ivar fail_reason: a string detailing the termination reason
81
82   """
83   __slots__ = ["exit_code", "signal", "stdout", "stderr",
84                "failed", "fail_reason", "cmd"]
85
86
87   def __init__(self, exit_code, signal_, stdout, stderr, cmd):
88     self.cmd = cmd
89     self.exit_code = exit_code
90     self.signal = signal_
91     self.stdout = stdout
92     self.stderr = stderr
93     self.failed = (signal_ is not None or exit_code != 0)
94
95     if self.signal is not None:
96       self.fail_reason = "terminated by signal %s" % self.signal
97     elif self.exit_code is not None:
98       self.fail_reason = "exited with exit code %s" % self.exit_code
99     else:
100       self.fail_reason = "unable to determine termination reason"
101
102     if self.failed:
103       logging.debug("Command '%s' failed (%s); output: %s",
104                     self.cmd, self.fail_reason, self.output)
105
106   def _GetOutput(self):
107     """Returns the combined stdout and stderr for easier usage.
108
109     """
110     return self.stdout + self.stderr
111
112   output = property(_GetOutput, None, None, "Return full output")
113
114
115 def RunCmd(cmd, env=None, output=None, cwd='/'):
116   """Execute a (shell) command.
117
118   The command should not read from its standard input, as it will be
119   closed.
120
121   @type  cmd: string or list
122   @param cmd: Command to run
123   @type env: dict
124   @param env: Additional environment
125   @type output: str
126   @param output: if desired, the output of the command can be
127       saved in a file instead of the RunResult instance; this
128       parameter denotes the file name (if not None)
129   @type cwd: string
130   @param cwd: if specified, will be used as the working
131       directory for the command; the default will be /
132   @rtype: L{RunResult}
133   @return: RunResult instance
134   @raise erors.ProgrammerError: if we call this when forks are disabled
135
136   """
137   if no_fork:
138     raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
139
140   if isinstance(cmd, list):
141     cmd = [str(val) for val in cmd]
142     strcmd = " ".join(cmd)
143     shell = False
144   else:
145     strcmd = cmd
146     shell = True
147   logging.debug("RunCmd '%s'", strcmd)
148
149   cmd_env = os.environ.copy()
150   cmd_env["LC_ALL"] = "C"
151   if env is not None:
152     cmd_env.update(env)
153
154   if output is None:
155     out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
156   else:
157     status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
158     out = err = ""
159
160   if status >= 0:
161     exitcode = status
162     signal_ = None
163   else:
164     exitcode = None
165     signal_ = -status
166
167   return RunResult(exitcode, signal_, out, err, strcmd)
168
169 def _RunCmdPipe(cmd, env, via_shell, cwd):
170   """Run a command and return its output.
171
172   @type  cmd: string or list
173   @param cmd: Command to run
174   @type env: dict
175   @param env: The environment to use
176   @type via_shell: bool
177   @param via_shell: if we should run via the shell
178   @type cwd: string
179   @param cwd: the working directory for the program
180   @rtype: tuple
181   @return: (out, err, status)
182
183   """
184   poller = select.poll()
185   child = subprocess.Popen(cmd, shell=via_shell,
186                            stderr=subprocess.PIPE,
187                            stdout=subprocess.PIPE,
188                            stdin=subprocess.PIPE,
189                            close_fds=True, env=env,
190                            cwd=cwd)
191
192   child.stdin.close()
193   poller.register(child.stdout, select.POLLIN)
194   poller.register(child.stderr, select.POLLIN)
195   out = StringIO()
196   err = StringIO()
197   fdmap = {
198     child.stdout.fileno(): (out, child.stdout),
199     child.stderr.fileno(): (err, child.stderr),
200     }
201   for fd in fdmap:
202     status = fcntl.fcntl(fd, fcntl.F_GETFL)
203     fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
204
205   while fdmap:
206     try:
207       pollresult = poller.poll()
208     except EnvironmentError, eerr:
209       if eerr.errno == errno.EINTR:
210         continue
211       raise
212     except select.error, serr:
213       if serr[0] == errno.EINTR:
214         continue
215       raise
216
217     for fd, event in pollresult:
218       if event & select.POLLIN or event & select.POLLPRI:
219         data = fdmap[fd][1].read()
220         # no data from read signifies EOF (the same as POLLHUP)
221         if not data:
222           poller.unregister(fd)
223           del fdmap[fd]
224           continue
225         fdmap[fd][0].write(data)
226       if (event & select.POLLNVAL or event & select.POLLHUP or
227           event & select.POLLERR):
228         poller.unregister(fd)
229         del fdmap[fd]
230
231   out = out.getvalue()
232   err = err.getvalue()
233
234   status = child.wait()
235   return out, err, status
236
237
238 def _RunCmdFile(cmd, env, via_shell, output, cwd):
239   """Run a command and save its output to a file.
240
241   @type  cmd: string or list
242   @param cmd: Command to run
243   @type env: dict
244   @param env: The environment to use
245   @type via_shell: bool
246   @param via_shell: if we should run via the shell
247   @type output: str
248   @param output: the filename in which to save the output
249   @type cwd: string
250   @param cwd: the working directory for the program
251   @rtype: int
252   @return: the exit status
253
254   """
255   fh = open(output, "a")
256   try:
257     child = subprocess.Popen(cmd, shell=via_shell,
258                              stderr=subprocess.STDOUT,
259                              stdout=fh,
260                              stdin=subprocess.PIPE,
261                              close_fds=True, env=env,
262                              cwd=cwd)
263
264     child.stdin.close()
265     status = child.wait()
266   finally:
267     fh.close()
268   return status
269
270
271 def RemoveFile(filename):
272   """Remove a file ignoring some errors.
273
274   Remove a file, ignoring non-existing ones or directories. Other
275   errors are passed.
276
277   @type filename: str
278   @param filename: the file to be removed
279
280   """
281   try:
282     os.unlink(filename)
283   except OSError, err:
284     if err.errno not in (errno.ENOENT, errno.EISDIR):
285       raise
286
287
288 def _FingerprintFile(filename):
289   """Compute the fingerprint of a file.
290
291   If the file does not exist, a None will be returned
292   instead.
293
294   @type filename: str
295   @param filename: the filename to checksum
296   @rtype: str
297   @return: the hex digest of the sha checksum of the contents
298       of the file
299
300   """
301   if not (os.path.exists(filename) and os.path.isfile(filename)):
302     return None
303
304   f = open(filename)
305
306   fp = sha.sha()
307   while True:
308     data = f.read(4096)
309     if not data:
310       break
311
312     fp.update(data)
313
314   return fp.hexdigest()
315
316
317 def FingerprintFiles(files):
318   """Compute fingerprints for a list of files.
319
320   @type files: list
321   @param files: the list of filename to fingerprint
322   @rtype: dict
323   @return: a dictionary filename: fingerprint, holding only
324       existing files
325
326   """
327   ret = {}
328
329   for filename in files:
330     cksum = _FingerprintFile(filename)
331     if cksum:
332       ret[filename] = cksum
333
334   return ret
335
336
337 def CheckDict(target, template, logname=None):
338   """Ensure a dictionary has a required set of keys.
339
340   For the given dictionaries I{target} and I{template}, ensure
341   I{target} has all the keys from I{template}. Missing keys are added
342   with values from template.
343
344   @type target: dict
345   @param target: the dictionary to update
346   @type template: dict
347   @param template: the dictionary holding the default values
348   @type logname: str or None
349   @param logname: if not None, causes the missing keys to be
350       logged with this name
351
352   """
353   missing = []
354   for k in template:
355     if k not in target:
356       missing.append(k)
357       target[k] = template[k]
358
359   if missing and logname:
360     logging.warning('%s missing keys %s', logname, ', '.join(missing))
361
362
363 def IsProcessAlive(pid):
364   """Check if a given pid exists on the system.
365
366   @note: zombie status is not handled, so zombie processes
367       will be returned as alive
368   @type pid: int
369   @param pid: the process ID to check
370   @rtype: boolean
371   @return: True if the process exists
372
373   """
374   if pid <= 0:
375     return False
376
377   try:
378     os.stat("/proc/%d/status" % pid)
379     return True
380   except EnvironmentError, err:
381     if err.errno in (errno.ENOENT, errno.ENOTDIR):
382       return False
383     raise
384
385
386 def ReadPidFile(pidfile):
387   """Read a pid from a file.
388
389   @type  pidfile: string
390   @param pidfile: path to the file containing the pid
391   @rtype: int
392   @return: The process id, if the file exista and contains a valid PID,
393            otherwise 0
394
395   """
396   try:
397     pf = open(pidfile, 'r')
398   except EnvironmentError, err:
399     if err.errno != errno.ENOENT:
400       logging.exception("Can't read pid file?!")
401     return 0
402
403   try:
404     pid = int(pf.read())
405   except ValueError, err:
406     logging.info("Can't parse pid file contents", exc_info=True)
407     return 0
408
409   return pid
410
411
412 def MatchNameComponent(key, name_list):
413   """Try to match a name against a list.
414
415   This function will try to match a name like test1 against a list
416   like C{['test1.example.com', 'test2.example.com', ...]}. Against
417   this list, I{'test1'} as well as I{'test1.example'} will match, but
418   not I{'test1.ex'}. A multiple match will be considered as no match
419   at all (e.g. I{'test1'} against C{['test1.example.com',
420   'test1.example.org']}).
421
422   @type key: str
423   @param key: the name to be searched
424   @type name_list: list
425   @param name_list: the list of strings against which to search the key
426
427   @rtype: None or str
428   @return: None if there is no match I{or} if there are multiple matches,
429       otherwise the element from the list which matches
430
431   """
432   mo = re.compile("^%s(\..*)?$" % re.escape(key))
433   names_filtered = [name for name in name_list if mo.match(name) is not None]
434   if len(names_filtered) != 1:
435     return None
436   return names_filtered[0]
437
438
439 class HostInfo:
440   """Class implementing resolver and hostname functionality
441
442   """
443   def __init__(self, name=None):
444     """Initialize the host name object.
445
446     If the name argument is not passed, it will use this system's
447     name.
448
449     """
450     if name is None:
451       name = self.SysName()
452
453     self.query = name
454     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
455     self.ip = self.ipaddrs[0]
456
457   def ShortName(self):
458     """Returns the hostname without domain.
459
460     """
461     return self.name.split('.')[0]
462
463   @staticmethod
464   def SysName():
465     """Return the current system's name.
466
467     This is simply a wrapper over C{socket.gethostname()}.
468
469     """
470     return socket.gethostname()
471
472   @staticmethod
473   def LookupHostname(hostname):
474     """Look up hostname
475
476     @type hostname: str
477     @param hostname: hostname to look up
478
479     @rtype: tuple
480     @return: a tuple (name, aliases, ipaddrs) as returned by
481         C{socket.gethostbyname_ex}
482     @raise errors.ResolverError: in case of errors in resolving
483
484     """
485     try:
486       result = socket.gethostbyname_ex(hostname)
487     except socket.gaierror, err:
488       # hostname not found in DNS
489       raise errors.ResolverError(hostname, err.args[0], err.args[1])
490
491     return result
492
493
494 def ListVolumeGroups():
495   """List volume groups and their size
496
497   @rtype: dict
498   @return:
499        Dictionary with keys volume name and values
500        the size of the volume
501
502   """
503   command = "vgs --noheadings --units m --nosuffix -o name,size"
504   result = RunCmd(command)
505   retval = {}
506   if result.failed:
507     return retval
508
509   for line in result.stdout.splitlines():
510     try:
511       name, size = line.split()
512       size = int(float(size))
513     except (IndexError, ValueError), err:
514       logging.error("Invalid output from vgs (%s): %s", err, line)
515       continue
516
517     retval[name] = size
518
519   return retval
520
521
522 def BridgeExists(bridge):
523   """Check whether the given bridge exists in the system
524
525   @type bridge: str
526   @param bridge: the bridge name to check
527   @rtype: boolean
528   @return: True if it does
529
530   """
531   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
532
533
534 def CheckBEParams(beparams):
535   """Checks whether the user-supplied be-params are valid,
536   and converts them from string format where appropriate.
537
538   @type beparams: dict
539   @param beparams: new params dict
540
541   """
542   if beparams:
543     for item in beparams:
544       if item not in constants.BES_PARAMETERS:
545         raise errors.OpPrereqError("Unknown backend parameter %s" % item)
546       if item in (constants.BE_MEMORY, constants.BE_VCPUS):
547         val = beparams[item]
548         if val != constants.VALUE_DEFAULT:
549           try:
550             val = int(val)
551           except ValueError, err:
552             raise errors.OpPrereqError("Invalid %s size: %s" % (item, str(err)))
553           beparams[item] = val
554       if item in (constants.BE_AUTO_BALANCE):
555         val = beparams[item]
556         if not isinstance(val, bool):
557           if val == constants.VALUE_TRUE:
558             beparams[item] = True
559           elif val == constants.VALUE_FALSE:
560             beparams[item] = False
561           else:
562             raise errors.OpPrereqError("Invalid %s value: %s" % (item, val))
563
564
565 def NiceSort(name_list):
566   """Sort a list of strings based on digit and non-digit groupings.
567
568   Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
569   will sort the list in the logical order C{['a1', 'a2', 'a10',
570   'a11']}.
571
572   The sort algorithm breaks each name in groups of either only-digits
573   or no-digits. Only the first eight such groups are considered, and
574   after that we just use what's left of the string.
575
576   @type name_list: list
577   @param name_list: the names to be sorted
578   @rtype: list
579   @return: a copy of the name list sorted with our algorithm
580
581   """
582   _SORTER_BASE = "(\D+|\d+)"
583   _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
584                                                   _SORTER_BASE, _SORTER_BASE,
585                                                   _SORTER_BASE, _SORTER_BASE,
586                                                   _SORTER_BASE, _SORTER_BASE)
587   _SORTER_RE = re.compile(_SORTER_FULL)
588   _SORTER_NODIGIT = re.compile("^\D*$")
589   def _TryInt(val):
590     """Attempts to convert a variable to integer."""
591     if val is None or _SORTER_NODIGIT.match(val):
592       return val
593     rval = int(val)
594     return rval
595
596   to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
597              for name in name_list]
598   to_sort.sort()
599   return [tup[1] for tup in to_sort]
600
601
602 def TryConvert(fn, val):
603   """Try to convert a value ignoring errors.
604
605   This function tries to apply function I{fn} to I{val}. If no
606   C{ValueError} or C{TypeError} exceptions are raised, it will return
607   the result, else it will return the original value. Any other
608   exceptions are propagated to the caller.
609
610   @type fn: callable
611   @param fn: function to apply to the value
612   @param val: the value to be converted
613   @return: The converted value if the conversion was successful,
614       otherwise the original value.
615
616   """
617   try:
618     nv = fn(val)
619   except (ValueError, TypeError), err:
620     nv = val
621   return nv
622
623
624 def IsValidIP(ip):
625   """Verifies the syntax of an IPv4 address.
626
627   This function checks if the IPv4 address passes is valid or not based
628   on syntax (not IP range, class calculations, etc.).
629
630   @type ip: str
631   @param ip: the address to be checked
632   @rtype: a regular expression match object
633   @return: a regular epression match object, or None if the
634       address is not valid
635
636   """
637   unit = "(0|[1-9]\d{0,2})"
638   #TODO: convert and return only boolean
639   return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
640
641
642 def IsValidShellParam(word):
643   """Verifies is the given word is safe from the shell's p.o.v.
644
645   This means that we can pass this to a command via the shell and be
646   sure that it doesn't alter the command line and is passed as such to
647   the actual command.
648
649   Note that we are overly restrictive here, in order to be on the safe
650   side.
651
652   @type word: str
653   @param word: the word to check
654   @rtype: boolean
655   @return: True if the word is 'safe'
656
657   """
658   return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
659
660
661 def BuildShellCmd(template, *args):
662   """Build a safe shell command line from the given arguments.
663
664   This function will check all arguments in the args list so that they
665   are valid shell parameters (i.e. they don't contain shell
666   metacharaters). If everything is ok, it will return the result of
667   template % args.
668
669   @type template: str
670   @param template: the string holding the template for the
671       string formatting
672   @rtype: str
673   @return: the expanded command line
674
675   """
676   for word in args:
677     if not IsValidShellParam(word):
678       raise errors.ProgrammerError("Shell argument '%s' contains"
679                                    " invalid characters" % word)
680   return template % args
681
682
683 def FormatUnit(value, units):
684   """Formats an incoming number of MiB with the appropriate unit.
685
686   @type value: int
687   @param value: integer representing the value in MiB (1048576)
688   @type units: char
689   @param units: the type of formatting we should do:
690       - 'h' for automatic scaling
691       - 'm' for MiBs
692       - 'g' for GiBs
693       - 't' for TiBs
694   @rtype: str
695   @return: the formatted value (with suffix)
696
697   """
698   if units not in ('m', 'g', 't', 'h'):
699     raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
700
701   suffix = ''
702
703   if units == 'm' or (units == 'h' and value < 1024):
704     if units == 'h':
705       suffix = 'M'
706     return "%d%s" % (round(value, 0), suffix)
707
708   elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
709     if units == 'h':
710       suffix = 'G'
711     return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
712
713   else:
714     if units == 'h':
715       suffix = 'T'
716     return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
717
718
719 def ParseUnit(input_string):
720   """Tries to extract number and scale from the given string.
721
722   Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
723   [UNIT]}. If no unit is specified, it defaults to MiB. Return value
724   is always an int in MiB.
725
726   """
727   m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
728   if not m:
729     raise errors.UnitParseError("Invalid format")
730
731   value = float(m.groups()[0])
732
733   unit = m.groups()[1]
734   if unit:
735     lcunit = unit.lower()
736   else:
737     lcunit = 'm'
738
739   if lcunit in ('m', 'mb', 'mib'):
740     # Value already in MiB
741     pass
742
743   elif lcunit in ('g', 'gb', 'gib'):
744     value *= 1024
745
746   elif lcunit in ('t', 'tb', 'tib'):
747     value *= 1024 * 1024
748
749   else:
750     raise errors.UnitParseError("Unknown unit: %s" % unit)
751
752   # Make sure we round up
753   if int(value) < value:
754     value += 1
755
756   # Round up to the next multiple of 4
757   value = int(value)
758   if value % 4:
759     value += 4 - value % 4
760
761   return value
762
763
764 def AddAuthorizedKey(file_name, key):
765   """Adds an SSH public key to an authorized_keys file.
766
767   @type file_name: str
768   @param file_name: path to authorized_keys file
769   @type key: str
770   @param key: string containing key
771
772   """
773   key_fields = key.split()
774
775   f = open(file_name, 'a+')
776   try:
777     nl = True
778     for line in f:
779       # Ignore whitespace changes
780       if line.split() == key_fields:
781         break
782       nl = line.endswith('\n')
783     else:
784       if not nl:
785         f.write("\n")
786       f.write(key.rstrip('\r\n'))
787       f.write("\n")
788       f.flush()
789   finally:
790     f.close()
791
792
793 def RemoveAuthorizedKey(file_name, key):
794   """Removes an SSH public key from an authorized_keys file.
795
796   @type file_name: str
797   @param file_name: path to authorized_keys file
798   @type key: str
799   @param key: string containing key
800
801   """
802   key_fields = key.split()
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           # Ignore whitespace changes while comparing lines
812           if line.split() != key_fields:
813             out.write(line)
814
815         out.flush()
816         os.rename(tmpname, file_name)
817       finally:
818         f.close()
819     finally:
820       out.close()
821   except:
822     RemoveFile(tmpname)
823     raise
824
825
826 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
827   """Sets the name of an IP address and hostname in /etc/hosts.
828
829   @type file_name: str
830   @param file_name: path to the file to modify (usually C{/etc/hosts})
831   @type ip: str
832   @param ip: the IP address
833   @type hostname: str
834   @param hostname: the hostname to be added
835   @type aliases: list
836   @param aliases: the list of aliases to add for the hostname
837
838   """
839   # Ensure aliases are unique
840   aliases = UniqueSequence([hostname] + aliases)[1:]
841
842   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
843   try:
844     out = os.fdopen(fd, 'w')
845     try:
846       f = open(file_name, 'r')
847       try:
848         for line in f:
849           fields = line.split()
850           if fields and not fields[0].startswith('#') and ip == fields[0]:
851             continue
852           out.write(line)
853
854         out.write("%s\t%s" % (ip, hostname))
855         if aliases:
856           out.write(" %s" % ' '.join(aliases))
857         out.write('\n')
858
859         out.flush()
860         os.fsync(out)
861         os.rename(tmpname, file_name)
862       finally:
863         f.close()
864     finally:
865       out.close()
866   except:
867     RemoveFile(tmpname)
868     raise
869
870
871 def AddHostToEtcHosts(hostname):
872   """Wrapper around SetEtcHostsEntry.
873
874   @type hostname: str
875   @param hostname: a hostname that will be resolved and added to
876       L{constants.ETC_HOSTS}
877
878   """
879   hi = HostInfo(name=hostname)
880   SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
881
882
883 def RemoveEtcHostsEntry(file_name, hostname):
884   """Removes a hostname from /etc/hosts.
885
886   IP addresses without names are removed from the file.
887
888   @type file_name: str
889   @param file_name: path to the file to modify (usually C{/etc/hosts})
890   @type hostname: str
891   @param hostname: the hostname to be removed
892
893   """
894   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
895   try:
896     out = os.fdopen(fd, 'w')
897     try:
898       f = open(file_name, 'r')
899       try:
900         for line in f:
901           fields = line.split()
902           if len(fields) > 1 and not fields[0].startswith('#'):
903             names = fields[1:]
904             if hostname in names:
905               while hostname in names:
906                 names.remove(hostname)
907               if names:
908                 out.write("%s %s\n" % (fields[0], ' '.join(names)))
909               continue
910
911           out.write(line)
912
913         out.flush()
914         os.fsync(out)
915         os.rename(tmpname, file_name)
916       finally:
917         f.close()
918     finally:
919       out.close()
920   except:
921     RemoveFile(tmpname)
922     raise
923
924
925 def RemoveHostFromEtcHosts(hostname):
926   """Wrapper around RemoveEtcHostsEntry.
927
928   @type hostname: str
929   @param hostname: hostname that will be resolved and its
930       full and shot name will be removed from
931       L{constants.ETC_HOSTS}
932
933   """
934   hi = HostInfo(name=hostname)
935   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
936   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
937
938
939 def CreateBackup(file_name):
940   """Creates a backup of a file.
941
942   @type file_name: str
943   @param file_name: file to be backed up
944   @rtype: str
945   @return: the path to the newly created backup
946   @raise errors.ProgrammerError: for invalid file names
947
948   """
949   if not os.path.isfile(file_name):
950     raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
951                                 file_name)
952
953   prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
954   dir_name = os.path.dirname(file_name)
955
956   fsrc = open(file_name, 'rb')
957   try:
958     (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
959     fdst = os.fdopen(fd, 'wb')
960     try:
961       shutil.copyfileobj(fsrc, fdst)
962     finally:
963       fdst.close()
964   finally:
965     fsrc.close()
966
967   return backup_name
968
969
970 def ShellQuote(value):
971   """Quotes shell argument according to POSIX.
972
973   @type value: str
974   @param value: the argument to be quoted
975   @rtype: str
976   @return: the quoted value
977
978   """
979   if _re_shell_unquoted.match(value):
980     return value
981   else:
982     return "'%s'" % value.replace("'", "'\\''")
983
984
985 def ShellQuoteArgs(args):
986   """Quotes a list of shell arguments.
987
988   @type args: list
989   @param args: list of arguments to be quoted
990   @rtype: str
991   @return: the quoted arguments concatenaned with spaces
992
993   """
994   return ' '.join([ShellQuote(i) for i in args])
995
996
997 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
998   """Simple ping implementation using TCP connect(2).
999
1000   Check if the given IP is reachable by doing attempting a TCP connect
1001   to it.
1002
1003   @type target: str
1004   @param target: the IP or hostname to ping
1005   @type port: int
1006   @param port: the port to connect to
1007   @type timeout: int
1008   @param timeout: the timeout on the connection attemp
1009   @type live_port_needed: boolean
1010   @param live_port_needed: whether a closed port will cause the
1011       function to return failure, as if there was a timeout
1012   @type source: str or None
1013   @param source: if specified, will cause the connect to be made
1014       from this specific source address; failures to bind other
1015       than C{EADDRNOTAVAIL} will be ignored
1016
1017   """
1018   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1019
1020   success = False
1021
1022   if source is not None:
1023     try:
1024       sock.bind((source, 0))
1025     except socket.error, (errcode, errstring):
1026       if errcode == errno.EADDRNOTAVAIL:
1027         success = False
1028
1029   sock.settimeout(timeout)
1030
1031   try:
1032     sock.connect((target, port))
1033     sock.close()
1034     success = True
1035   except socket.timeout:
1036     success = False
1037   except socket.error, (errcode, errstring):
1038     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1039
1040   return success
1041
1042
1043 def OwnIpAddress(address):
1044   """Check if the current host has the the given IP address.
1045
1046   Currently this is done by TCP-pinging the address from the loopback
1047   address.
1048
1049   @type address: string
1050   @param address: the addres to check
1051   @rtype: bool
1052   @return: True if we own the address
1053
1054   """
1055   return TcpPing(address, constants.DEFAULT_NODED_PORT,
1056                  source=constants.LOCALHOST_IP_ADDRESS)
1057
1058
1059 def ListVisibleFiles(path):
1060   """Returns a list of visible files in a directory.
1061
1062   @type path: str
1063   @param path: the directory to enumerate
1064   @rtype: list
1065   @return: the list of all files not starting with a dot
1066
1067   """
1068   files = [i for i in os.listdir(path) if not i.startswith(".")]
1069   files.sort()
1070   return files
1071
1072
1073 def GetHomeDir(user, default=None):
1074   """Try to get the homedir of the given user.
1075
1076   The user can be passed either as a string (denoting the name) or as
1077   an integer (denoting the user id). If the user is not found, the
1078   'default' argument is returned, which defaults to None.
1079
1080   """
1081   try:
1082     if isinstance(user, basestring):
1083       result = pwd.getpwnam(user)
1084     elif isinstance(user, (int, long)):
1085       result = pwd.getpwuid(user)
1086     else:
1087       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1088                                    type(user))
1089   except KeyError:
1090     return default
1091   return result.pw_dir
1092
1093
1094 def NewUUID():
1095   """Returns a random UUID.
1096
1097   @note: This is a Linux-specific method as it uses the /proc
1098       filesystem.
1099   @rtype: str
1100
1101   """
1102   f = open("/proc/sys/kernel/random/uuid", "r")
1103   try:
1104     return f.read(128).rstrip("\n")
1105   finally:
1106     f.close()
1107
1108
1109 def GenerateSecret():
1110   """Generates a random secret.
1111
1112   This will generate a pseudo-random secret, and return its sha digest
1113   (so that it can be used where an ASCII string is needed).
1114
1115   @rtype: str
1116   @return: a sha1 hexdigest of a block of 64 random bytes
1117
1118   """
1119   return sha.new(os.urandom(64)).hexdigest()
1120
1121
1122 def ReadFile(file_name, size=None):
1123   """Reads a file.
1124
1125   @type size: None or int
1126   @param size: Read at most size bytes
1127   @rtype: str
1128   @return: the (possibly partial) conent of the file
1129
1130   """
1131   f = open(file_name, "r")
1132   try:
1133     if size is None:
1134       return f.read()
1135     else:
1136       return f.read(size)
1137   finally:
1138     f.close()
1139
1140
1141 def WriteFile(file_name, fn=None, data=None,
1142               mode=None, uid=-1, gid=-1,
1143               atime=None, mtime=None, close=True,
1144               dry_run=False, backup=False,
1145               prewrite=None, postwrite=None):
1146   """(Over)write a file atomically.
1147
1148   The file_name and either fn (a function taking one argument, the
1149   file descriptor, and which should write the data to it) or data (the
1150   contents of the file) must be passed. The other arguments are
1151   optional and allow setting the file mode, owner and group, and the
1152   mtime/atime of the file.
1153
1154   If the function doesn't raise an exception, it has succeeded and the
1155   target file has the new contents. If the file has raised an
1156   exception, an existing target file should be unmodified and the
1157   temporary file should be removed.
1158
1159   @type file_name: str
1160   @param file_name: the target filename
1161   @type fn: callable
1162   @param fn: content writing function, called with
1163       file descriptor as parameter
1164   @type data: sr
1165   @param data: contents of the file
1166   @type mode: int
1167   @param mode: file mode
1168   @type uid: int
1169   @param uid: the owner of the file
1170   @type gid: int
1171   @param gid: the group of the file
1172   @type atime: int
1173   @param atime: a custom access time to be set on the file
1174   @type mtime: int
1175   @param mtime: a custom modification time to be set on the file
1176   @type close: boolean
1177   @param close: whether to close file after writing it
1178   @type prewrite: callable
1179   @param prewrite: function to be called before writing content
1180   @type postwrite: callable
1181   @param postwrite: function to be called after writing content
1182
1183   @rtype: None or int
1184   @return: None if the 'close' parameter evaluates to True,
1185       otherwise the file descriptor
1186
1187   @raise errors.ProgrammerError: if an of the arguments are not valid
1188
1189   """
1190   if not os.path.isabs(file_name):
1191     raise errors.ProgrammerError("Path passed to WriteFile is not"
1192                                  " absolute: '%s'" % file_name)
1193
1194   if [fn, data].count(None) != 1:
1195     raise errors.ProgrammerError("fn or data required")
1196
1197   if [atime, mtime].count(None) == 1:
1198     raise errors.ProgrammerError("Both atime and mtime must be either"
1199                                  " set or None")
1200
1201   if backup and not dry_run and os.path.isfile(file_name):
1202     CreateBackup(file_name)
1203
1204   dir_name, base_name = os.path.split(file_name)
1205   fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1206   # here we need to make sure we remove the temp file, if any error
1207   # leaves it in place
1208   try:
1209     if uid != -1 or gid != -1:
1210       os.chown(new_name, uid, gid)
1211     if mode:
1212       os.chmod(new_name, mode)
1213     if callable(prewrite):
1214       prewrite(fd)
1215     if data is not None:
1216       os.write(fd, data)
1217     else:
1218       fn(fd)
1219     if callable(postwrite):
1220       postwrite(fd)
1221     os.fsync(fd)
1222     if atime is not None and mtime is not None:
1223       os.utime(new_name, (atime, mtime))
1224     if not dry_run:
1225       os.rename(new_name, file_name)
1226   finally:
1227     if close:
1228       os.close(fd)
1229       result = None
1230     else:
1231       result = fd
1232     RemoveFile(new_name)
1233
1234   return result
1235
1236
1237 def FirstFree(seq, base=0):
1238   """Returns the first non-existing integer from seq.
1239
1240   The seq argument should be a sorted list of positive integers. The
1241   first time the index of an element is smaller than the element
1242   value, the index will be returned.
1243
1244   The base argument is used to start at a different offset,
1245   i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1246
1247   Example: C{[0, 1, 3]} will return I{2}.
1248
1249   @type seq: sequence
1250   @param seq: the sequence to be analyzed.
1251   @type base: int
1252   @param base: use this value as the base index of the sequence
1253   @rtype: int
1254   @return: the first non-used index in the sequence
1255
1256   """
1257   for idx, elem in enumerate(seq):
1258     assert elem >= base, "Passed element is higher than base offset"
1259     if elem > idx + base:
1260       # idx is not used
1261       return idx + base
1262   return None
1263
1264
1265 def all(seq, pred=bool):
1266   "Returns True if pred(x) is True for every element in the iterable"
1267   for elem in itertools.ifilterfalse(pred, seq):
1268     return False
1269   return True
1270
1271
1272 def any(seq, pred=bool):
1273   "Returns True if pred(x) is True for at least one element in the iterable"
1274   for elem in itertools.ifilter(pred, seq):
1275     return True
1276   return False
1277
1278
1279 def UniqueSequence(seq):
1280   """Returns a list with unique elements.
1281
1282   Element order is preserved.
1283
1284   @type seq: sequence
1285   @param seq: the sequence with the source elementes
1286   @rtype: list
1287   @return: list of unique elements from seq
1288
1289   """
1290   seen = set()
1291   return [i for i in seq if i not in seen and not seen.add(i)]
1292
1293
1294 def IsValidMac(mac):
1295   """Predicate to check if a MAC address is valid.
1296
1297   Checks wether the supplied MAC address is formally correct, only
1298   accepts colon separated format.
1299
1300   @type mac: str
1301   @param mac: the MAC to be validated
1302   @rtype: boolean
1303   @return: True is the MAC seems valid
1304
1305   """
1306   mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1307   return mac_check.match(mac) is not None
1308
1309
1310 def TestDelay(duration):
1311   """Sleep for a fixed amount of time.
1312
1313   @type duration: float
1314   @param duration: the sleep duration
1315   @rtype: boolean
1316   @return: False for negative value, True otherwise
1317
1318   """
1319   if duration < 0:
1320     return False
1321   time.sleep(duration)
1322   return True
1323
1324
1325 def Daemonize(logfile, noclose_fds=None):
1326   """Daemonize the current process.
1327
1328   This detaches the current process from the controlling terminal and
1329   runs it in the background as a daemon.
1330
1331   @type logfile: str
1332   @param logfile: the logfile to which we should redirect stdout/stderr
1333   @type noclose_fds: list or None
1334   @param noclose_fds: if given, it denotes a list of file descriptor
1335       that should not be closed
1336   @rtype: int
1337   @returns: the value zero
1338
1339   """
1340   UMASK = 077
1341   WORKDIR = "/"
1342   # Default maximum for the number of available file descriptors.
1343   if 'SC_OPEN_MAX' in os.sysconf_names:
1344     try:
1345       MAXFD = os.sysconf('SC_OPEN_MAX')
1346       if MAXFD < 0:
1347         MAXFD = 1024
1348     except OSError:
1349       MAXFD = 1024
1350   else:
1351     MAXFD = 1024
1352
1353   # this might fail
1354   pid = os.fork()
1355   if (pid == 0):  # The first child.
1356     os.setsid()
1357     # this might fail
1358     pid = os.fork() # Fork a second child.
1359     if (pid == 0):  # The second child.
1360       os.chdir(WORKDIR)
1361       os.umask(UMASK)
1362     else:
1363       # exit() or _exit()?  See below.
1364       os._exit(0) # Exit parent (the first child) of the second child.
1365   else:
1366     os._exit(0) # Exit parent of the first child.
1367   maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1368   if (maxfd == resource.RLIM_INFINITY):
1369     maxfd = MAXFD
1370
1371   # Iterate through and close all file descriptors.
1372   for fd in range(0, maxfd):
1373     if noclose_fds and fd in noclose_fds:
1374       continue
1375     try:
1376       os.close(fd)
1377     except OSError: # ERROR, fd wasn't open to begin with (ignored)
1378       pass
1379   os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1380   # Duplicate standard input to standard output and standard error.
1381   os.dup2(0, 1)     # standard output (1)
1382   os.dup2(0, 2)     # standard error (2)
1383   return 0
1384
1385
1386 def DaemonPidFileName(name):
1387   """Compute a ganeti pid file absolute path
1388
1389   @type name: str
1390   @param name: the daemon name
1391   @rtype: str
1392   @return: the full path to the pidfile corresponding to the given
1393       daemon name
1394
1395   """
1396   return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1397
1398
1399 def WritePidFile(name):
1400   """Write the current process pidfile.
1401
1402   The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1403
1404   @type name: str
1405   @param name: the daemon name to use
1406   @raise errors.GenericError: if the pid file already exists and
1407       points to a live process
1408
1409   """
1410   pid = os.getpid()
1411   pidfilename = DaemonPidFileName(name)
1412   if IsProcessAlive(ReadPidFile(pidfilename)):
1413     raise errors.GenericError("%s contains a live process" % pidfilename)
1414
1415   WriteFile(pidfilename, data="%d\n" % pid)
1416
1417
1418 def RemovePidFile(name):
1419   """Remove the current process pidfile.
1420
1421   Any errors are ignored.
1422
1423   @type name: str
1424   @param name: the daemon name used to derive the pidfile name
1425
1426   """
1427   pid = os.getpid()
1428   pidfilename = DaemonPidFileName(name)
1429   # TODO: we could check here that the file contains our pid
1430   try:
1431     RemoveFile(pidfilename)
1432   except:
1433     pass
1434
1435
1436 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1437                 waitpid=False):
1438   """Kill a process given by its pid.
1439
1440   @type pid: int
1441   @param pid: The PID to terminate.
1442   @type signal_: int
1443   @param signal_: The signal to send, by default SIGTERM
1444   @type timeout: int
1445   @param timeout: The timeout after which, if the process is still alive,
1446                   a SIGKILL will be sent. If not positive, no such checking
1447                   will be done
1448   @type waitpid: boolean
1449   @param waitpid: If true, we should waitpid on this process after
1450       sending signals, since it's our own child and otherwise it
1451       would remain as zombie
1452
1453   """
1454   def _helper(pid, signal_, wait):
1455     """Simple helper to encapsulate the kill/waitpid sequence"""
1456     os.kill(pid, signal_)
1457     if wait:
1458       try:
1459         os.waitpid(pid, os.WNOHANG)
1460       except OSError:
1461         pass
1462
1463   if pid <= 0:
1464     # kill with pid=0 == suicide
1465     raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1466
1467   if not IsProcessAlive(pid):
1468     return
1469   _helper(pid, signal_, waitpid)
1470   if timeout <= 0:
1471     return
1472
1473   # Wait up to $timeout seconds
1474   end = time.time() + timeout
1475   wait = 0.01
1476   while time.time() < end and IsProcessAlive(pid):
1477     try:
1478       (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1479       if result_pid > 0:
1480         break
1481     except OSError:
1482       pass
1483     time.sleep(wait)
1484     # Make wait time longer for next try
1485     if wait < 0.1:
1486       wait *= 1.5
1487
1488   if IsProcessAlive(pid):
1489     # Kill process if it's still alive
1490     _helper(pid, signal.SIGKILL, waitpid)
1491
1492
1493 def FindFile(name, search_path, test=os.path.exists):
1494   """Look for a filesystem object in a given path.
1495
1496   This is an abstract method to search for filesystem object (files,
1497   dirs) under a given search path.
1498
1499   @type name: str
1500   @param name: the name to look for
1501   @type search_path: str
1502   @param search_path: location to start at
1503   @type test: callable
1504   @param test: a function taking one argument that should return True
1505       if the a given object is valid; the default value is
1506       os.path.exists, causing only existing files to be returned
1507   @rtype: str or None
1508   @return: full path to the object if found, None otherwise
1509
1510   """
1511   for dir_name in search_path:
1512     item_name = os.path.sep.join([dir_name, name])
1513     if test(item_name):
1514       return item_name
1515   return None
1516
1517
1518 def CheckVolumeGroupSize(vglist, vgname, minsize):
1519   """Checks if the volume group list is valid.
1520
1521   The function will check if a given volume group is in the list of
1522   volume groups and has a minimum size.
1523
1524   @type vglist: dict
1525   @param vglist: dictionary of volume group names and their size
1526   @type vgname: str
1527   @param vgname: the volume group we should check
1528   @type minsize: int
1529   @param minsize: the minimum size we accept
1530   @rtype: None or str
1531   @return: None for success, otherwise the error message
1532
1533   """
1534   vgsize = vglist.get(vgname, None)
1535   if vgsize is None:
1536     return "volume group '%s' missing" % vgname
1537   elif vgsize < minsize:
1538     return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1539             (vgname, minsize, vgsize))
1540   return None
1541
1542
1543 def SplitTime(value):
1544   """Splits time as floating point number into a tuple.
1545
1546   @param value: Time in seconds
1547   @type value: int or float
1548   @return: Tuple containing (seconds, microseconds)
1549
1550   """
1551   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1552
1553   assert 0 <= seconds, \
1554     "Seconds must be larger than or equal to 0, but are %s" % seconds
1555   assert 0 <= microseconds <= 999999, \
1556     "Microseconds must be 0-999999, but are %s" % microseconds
1557
1558   return (int(seconds), int(microseconds))
1559
1560
1561 def MergeTime(timetuple):
1562   """Merges a tuple into time as a floating point number.
1563
1564   @param timetuple: Time as tuple, (seconds, microseconds)
1565   @type timetuple: tuple
1566   @return: Time as a floating point number expressed in seconds
1567
1568   """
1569   (seconds, microseconds) = timetuple
1570
1571   assert 0 <= seconds, \
1572     "Seconds must be larger than or equal to 0, but are %s" % seconds
1573   assert 0 <= microseconds <= 999999, \
1574     "Microseconds must be 0-999999, but are %s" % microseconds
1575
1576   return float(seconds) + (float(microseconds) * 0.000001)
1577
1578
1579 def GetNodeDaemonPort():
1580   """Get the node daemon port for this cluster.
1581
1582   Note that this routine does not read a ganeti-specific file, but
1583   instead uses C{socket.getservbyname} to allow pre-customization of
1584   this parameter outside of Ganeti.
1585
1586   @rtype: int
1587
1588   """
1589   try:
1590     port = socket.getservbyname("ganeti-noded", "tcp")
1591   except socket.error:
1592     port = constants.DEFAULT_NODED_PORT
1593
1594   return port
1595
1596
1597 def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1598   """Configures the logging module.
1599
1600   @type logfile: str
1601   @param logfile: the filename to which we should log
1602   @type debug: boolean
1603   @param debug: whether to enable debug messages too or
1604       only those at C{INFO} and above level
1605   @type stderr_logging: boolean
1606   @param stderr_logging: whether we should also log to the standard error
1607   @type program: str
1608   @param program: the name under which we should log messages
1609   @raise EnvironmentError: if we can't open the log file and
1610       stderr logging is disabled
1611
1612   """
1613   fmt = "%(asctime)s: " + program + " "
1614   if debug:
1615     fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1616            " %(module)s:%(lineno)s %(message)s")
1617   else:
1618     fmt += "pid=%(process)d %(levelname)s %(message)s"
1619   formatter = logging.Formatter(fmt)
1620
1621   root_logger = logging.getLogger("")
1622   root_logger.setLevel(logging.NOTSET)
1623
1624   # Remove all previously setup handlers
1625   for handler in root_logger.handlers:
1626     root_logger.removeHandler(handler)
1627
1628   if stderr_logging:
1629     stderr_handler = logging.StreamHandler()
1630     stderr_handler.setFormatter(formatter)
1631     if debug:
1632       stderr_handler.setLevel(logging.NOTSET)
1633     else:
1634       stderr_handler.setLevel(logging.CRITICAL)
1635     root_logger.addHandler(stderr_handler)
1636
1637   # this can fail, if the logging directories are not setup or we have
1638   # a permisssion problem; in this case, it's best to log but ignore
1639   # the error if stderr_logging is True, and if false we re-raise the
1640   # exception since otherwise we could run but without any logs at all
1641   try:
1642     logfile_handler = logging.FileHandler(logfile)
1643     logfile_handler.setFormatter(formatter)
1644     if debug:
1645       logfile_handler.setLevel(logging.DEBUG)
1646     else:
1647       logfile_handler.setLevel(logging.INFO)
1648     root_logger.addHandler(logfile_handler)
1649   except EnvironmentError, err:
1650     if stderr_logging:
1651       logging.exception("Failed to enable logging to file '%s'", logfile)
1652     else:
1653       # we need to re-raise the exception
1654       raise
1655
1656
1657 def LockedMethod(fn):
1658   """Synchronized object access decorator.
1659
1660   This decorator is intended to protect access to an object using the
1661   object's own lock which is hardcoded to '_lock'.
1662
1663   """
1664   def _LockDebug(*args, **kwargs):
1665     if debug_locks:
1666       logging.debug(*args, **kwargs)
1667
1668   def wrapper(self, *args, **kwargs):
1669     assert hasattr(self, '_lock')
1670     lock = self._lock
1671     _LockDebug("Waiting for %s", lock)
1672     lock.acquire()
1673     try:
1674       _LockDebug("Acquired %s", lock)
1675       result = fn(self, *args, **kwargs)
1676     finally:
1677       _LockDebug("Releasing %s", lock)
1678       lock.release()
1679       _LockDebug("Released %s", lock)
1680     return result
1681   return wrapper
1682
1683
1684 def LockFile(fd):
1685   """Locks a file using POSIX locks.
1686
1687   @type fd: int
1688   @param fd: the file descriptor we need to lock
1689
1690   """
1691   try:
1692     fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1693   except IOError, err:
1694     if err.errno == errno.EAGAIN:
1695       raise errors.LockError("File already locked")
1696     raise
1697
1698
1699 class FileLock(object):
1700   """Utility class for file locks.
1701
1702   """
1703   def __init__(self, filename):
1704     """Constructor for FileLock.
1705
1706     This will open the file denoted by the I{filename} argument.
1707
1708     @type filename: str
1709     @param filename: path to the file to be locked
1710
1711     """
1712     self.filename = filename
1713     self.fd = open(self.filename, "w")
1714
1715   def __del__(self):
1716     self.Close()
1717
1718   def Close(self):
1719     """Close the file and release the lock.
1720
1721     """
1722     if self.fd:
1723       self.fd.close()
1724       self.fd = None
1725
1726   def _flock(self, flag, blocking, timeout, errmsg):
1727     """Wrapper for fcntl.flock.
1728
1729     @type flag: int
1730     @param flag: operation flag
1731     @type blocking: bool
1732     @param blocking: whether the operation should be done in blocking mode.
1733     @type timeout: None or float
1734     @param timeout: for how long the operation should be retried (implies
1735                     non-blocking mode).
1736     @type errmsg: string
1737     @param errmsg: error message in case operation fails.
1738
1739     """
1740     assert self.fd, "Lock was closed"
1741     assert timeout is None or timeout >= 0, \
1742       "If specified, timeout must be positive"
1743
1744     if timeout is not None:
1745       flag |= fcntl.LOCK_NB
1746       timeout_end = time.time() + timeout
1747
1748     # Blocking doesn't have effect with timeout
1749     elif not blocking:
1750       flag |= fcntl.LOCK_NB
1751       timeout_end = None
1752
1753     retry = True
1754     while retry:
1755       try:
1756         fcntl.flock(self.fd, flag)
1757         retry = False
1758       except IOError, err:
1759         if err.errno in (errno.EAGAIN, ):
1760           if timeout_end is not None and time.time() < timeout_end:
1761             # Wait before trying again
1762             time.sleep(max(0.1, min(1.0, timeout)))
1763           else:
1764             raise errors.LockError(errmsg)
1765         else:
1766           logging.exception("fcntl.flock failed")
1767           raise
1768
1769   def Exclusive(self, blocking=False, timeout=None):
1770     """Locks the file in exclusive mode.
1771
1772     @type blocking: boolean
1773     @param blocking: whether to block and wait until we
1774         can lock the file or return immediately
1775     @type timeout: int or None
1776     @param timeout: if not None, the duration to wait for the lock
1777         (in blocking mode)
1778
1779     """
1780     self._flock(fcntl.LOCK_EX, blocking, timeout,
1781                 "Failed to lock %s in exclusive mode" % self.filename)
1782
1783   def Shared(self, blocking=False, timeout=None):
1784     """Locks the file in shared mode.
1785
1786     @type blocking: boolean
1787     @param blocking: whether to block and wait until we
1788         can lock the file or return immediately
1789     @type timeout: int or None
1790     @param timeout: if not None, the duration to wait for the lock
1791         (in blocking mode)
1792
1793     """
1794     self._flock(fcntl.LOCK_SH, blocking, timeout,
1795                 "Failed to lock %s in shared mode" % self.filename)
1796
1797   def Unlock(self, blocking=True, timeout=None):
1798     """Unlocks the file.
1799
1800     According to C{flock(2)}, unlocking can also be a nonblocking
1801     operation::
1802
1803       To make a non-blocking request, include LOCK_NB with any of the above
1804       operations.
1805
1806     @type blocking: boolean
1807     @param blocking: whether to block and wait until we
1808         can lock the file or return immediately
1809     @type timeout: int or None
1810     @param timeout: if not None, the duration to wait for the lock
1811         (in blocking mode)
1812
1813     """
1814     self._flock(fcntl.LOCK_UN, blocking, timeout,
1815                 "Failed to unlock %s" % self.filename)
1816
1817
1818 class SignalHandler(object):
1819   """Generic signal handler class.
1820
1821   It automatically restores the original handler when deconstructed or
1822   when L{Reset} is called. You can either pass your own handler
1823   function in or query the L{called} attribute to detect whether the
1824   signal was sent.
1825
1826   @type signum: list
1827   @ivar signum: the signals we handle
1828   @type called: boolean
1829   @ivar called: tracks whether any of the signals have been raised
1830
1831   """
1832   def __init__(self, signum):
1833     """Constructs a new SignalHandler instance.
1834
1835     @type signum: int or list of ints
1836     @param signum: Single signal number or set of signal numbers
1837
1838     """
1839     if isinstance(signum, (int, long)):
1840       self.signum = set([signum])
1841     else:
1842       self.signum = set(signum)
1843
1844     self.called = False
1845
1846     self._previous = {}
1847     try:
1848       for signum in self.signum:
1849         # Setup handler
1850         prev_handler = signal.signal(signum, self._HandleSignal)
1851         try:
1852           self._previous[signum] = prev_handler
1853         except:
1854           # Restore previous handler
1855           signal.signal(signum, prev_handler)
1856           raise
1857     except:
1858       # Reset all handlers
1859       self.Reset()
1860       # Here we have a race condition: a handler may have already been called,
1861       # but there's not much we can do about it at this point.
1862       raise
1863
1864   def __del__(self):
1865     self.Reset()
1866
1867   def Reset(self):
1868     """Restore previous handler.
1869
1870     This will reset all the signals to their previous handlers.
1871
1872     """
1873     for signum, prev_handler in self._previous.items():
1874       signal.signal(signum, prev_handler)
1875       # If successful, remove from dict
1876       del self._previous[signum]
1877
1878   def Clear(self):
1879     """Unsets the L{called} flag.
1880
1881     This function can be used in case a signal may arrive several times.
1882
1883     """
1884     self.called = False
1885
1886   def _HandleSignal(self, signum, frame):
1887     """Actual signal handling function.
1888
1889     """
1890     # This is not nice and not absolutely atomic, but it appears to be the only
1891     # solution in Python -- there are no atomic types.
1892     self.called = True
1893
1894
1895 class FieldSet(object):
1896   """A simple field set.
1897
1898   Among the features are:
1899     - checking if a string is among a list of static string or regex objects
1900     - checking if a whole list of string matches
1901     - returning the matching groups from a regex match
1902
1903   Internally, all fields are held as regular expression objects.
1904
1905   """
1906   def __init__(self, *items):
1907     self.items = [re.compile("^%s$" % value) for value in items]
1908
1909   def Extend(self, other_set):
1910     """Extend the field set with the items from another one"""
1911     self.items.extend(other_set.items)
1912
1913   def Matches(self, field):
1914     """Checks if a field matches the current set
1915
1916     @type field: str
1917     @param field: the string to match
1918     @return: either False or a regular expression match object
1919
1920     """
1921     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
1922       return m
1923     return False
1924
1925   def NonMatching(self, items):
1926     """Returns the list of fields not matching the current set
1927
1928     @type items: list
1929     @param items: the list of fields to check
1930     @rtype: list
1931     @return: list of non-matching fields
1932
1933     """
1934     return [val for val in items if not self.Matches(val)]