utils: Move wrapper code around os.makedirs into separate function
[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 os
31 import time
32 import subprocess
33 import re
34 import socket
35 import tempfile
36 import shutil
37 import errno
38 import pwd
39 import itertools
40 import select
41 import fcntl
42 import resource
43 import logging
44 import logging.handlers
45 import signal
46 import datetime
47 import calendar
48
49 from cStringIO import StringIO
50
51 try:
52   from hashlib import sha1
53 except ImportError:
54   import sha
55   sha1 = sha.new
56
57 from ganeti import errors
58 from ganeti import constants
59
60
61 _locksheld = []
62 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
63
64 debug_locks = False
65
66 #: when set to True, L{RunCmd} is disabled
67 no_fork = False
68
69 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
70
71
72 class RunResult(object):
73   """Holds the result of running external programs.
74
75   @type exit_code: int
76   @ivar exit_code: the exit code of the program, or None (if the program
77       didn't exit())
78   @type signal: int or None
79   @ivar signal: the signal that caused the program to finish, or None
80       (if the program wasn't terminated by a signal)
81   @type stdout: str
82   @ivar stdout: the standard output of the program
83   @type stderr: str
84   @ivar stderr: the standard error of the program
85   @type failed: boolean
86   @ivar failed: True in case the program was
87       terminated by a signal or exited with a non-zero exit code
88   @ivar fail_reason: a string detailing the termination reason
89
90   """
91   __slots__ = ["exit_code", "signal", "stdout", "stderr",
92                "failed", "fail_reason", "cmd"]
93
94
95   def __init__(self, exit_code, signal_, stdout, stderr, cmd):
96     self.cmd = cmd
97     self.exit_code = exit_code
98     self.signal = signal_
99     self.stdout = stdout
100     self.stderr = stderr
101     self.failed = (signal_ is not None or exit_code != 0)
102
103     if self.signal is not None:
104       self.fail_reason = "terminated by signal %s" % self.signal
105     elif self.exit_code is not None:
106       self.fail_reason = "exited with exit code %s" % self.exit_code
107     else:
108       self.fail_reason = "unable to determine termination reason"
109
110     if self.failed:
111       logging.debug("Command '%s' failed (%s); output: %s",
112                     self.cmd, self.fail_reason, self.output)
113
114   def _GetOutput(self):
115     """Returns the combined stdout and stderr for easier usage.
116
117     """
118     return self.stdout + self.stderr
119
120   output = property(_GetOutput, None, None, "Return full output")
121
122
123 def RunCmd(cmd, env=None, output=None, cwd='/', reset_env=False):
124   """Execute a (shell) command.
125
126   The command should not read from its standard input, as it will be
127   closed.
128
129   @type cmd: string or list
130   @param cmd: Command to run
131   @type env: dict
132   @param env: Additional environment
133   @type output: str
134   @param output: if desired, the output of the command can be
135       saved in a file instead of the RunResult instance; this
136       parameter denotes the file name (if not None)
137   @type cwd: string
138   @param cwd: if specified, will be used as the working
139       directory for the command; the default will be /
140   @type reset_env: boolean
141   @param reset_env: whether to reset or keep the default os environment
142   @rtype: L{RunResult}
143   @return: RunResult instance
144   @raise errors.ProgrammerError: if we call this when forks are disabled
145
146   """
147   if no_fork:
148     raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
149
150   if isinstance(cmd, list):
151     cmd = [str(val) for val in cmd]
152     strcmd = " ".join(cmd)
153     shell = False
154   else:
155     strcmd = cmd
156     shell = True
157   logging.debug("RunCmd '%s'", strcmd)
158
159   if not reset_env:
160     cmd_env = os.environ.copy()
161     cmd_env["LC_ALL"] = "C"
162   else:
163     cmd_env = {}
164
165   if env is not None:
166     cmd_env.update(env)
167
168   try:
169     if output is None:
170       out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
171     else:
172       status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
173       out = err = ""
174   except OSError, err:
175     if err.errno == errno.ENOENT:
176       raise errors.OpExecError("Can't execute '%s': not found (%s)" %
177                                (strcmd, err))
178     else:
179       raise
180
181   if status >= 0:
182     exitcode = status
183     signal_ = None
184   else:
185     exitcode = None
186     signal_ = -status
187
188   return RunResult(exitcode, signal_, out, err, strcmd)
189
190
191 def _RunCmdPipe(cmd, env, via_shell, cwd):
192   """Run a command and return its output.
193
194   @type  cmd: string or list
195   @param cmd: Command to run
196   @type env: dict
197   @param env: The environment to use
198   @type via_shell: bool
199   @param via_shell: if we should run via the shell
200   @type cwd: string
201   @param cwd: the working directory for the program
202   @rtype: tuple
203   @return: (out, err, status)
204
205   """
206   poller = select.poll()
207   child = subprocess.Popen(cmd, shell=via_shell,
208                            stderr=subprocess.PIPE,
209                            stdout=subprocess.PIPE,
210                            stdin=subprocess.PIPE,
211                            close_fds=True, env=env,
212                            cwd=cwd)
213
214   child.stdin.close()
215   poller.register(child.stdout, select.POLLIN)
216   poller.register(child.stderr, select.POLLIN)
217   out = StringIO()
218   err = StringIO()
219   fdmap = {
220     child.stdout.fileno(): (out, child.stdout),
221     child.stderr.fileno(): (err, child.stderr),
222     }
223   for fd in fdmap:
224     status = fcntl.fcntl(fd, fcntl.F_GETFL)
225     fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
226
227   while fdmap:
228     try:
229       pollresult = poller.poll()
230     except EnvironmentError, eerr:
231       if eerr.errno == errno.EINTR:
232         continue
233       raise
234     except select.error, serr:
235       if serr[0] == errno.EINTR:
236         continue
237       raise
238
239     for fd, event in pollresult:
240       if event & select.POLLIN or event & select.POLLPRI:
241         data = fdmap[fd][1].read()
242         # no data from read signifies EOF (the same as POLLHUP)
243         if not data:
244           poller.unregister(fd)
245           del fdmap[fd]
246           continue
247         fdmap[fd][0].write(data)
248       if (event & select.POLLNVAL or event & select.POLLHUP or
249           event & select.POLLERR):
250         poller.unregister(fd)
251         del fdmap[fd]
252
253   out = out.getvalue()
254   err = err.getvalue()
255
256   status = child.wait()
257   return out, err, status
258
259
260 def _RunCmdFile(cmd, env, via_shell, output, cwd):
261   """Run a command and save its output to a file.
262
263   @type  cmd: string or list
264   @param cmd: Command to run
265   @type env: dict
266   @param env: The environment to use
267   @type via_shell: bool
268   @param via_shell: if we should run via the shell
269   @type output: str
270   @param output: the filename in which to save the output
271   @type cwd: string
272   @param cwd: the working directory for the program
273   @rtype: int
274   @return: the exit status
275
276   """
277   fh = open(output, "a")
278   try:
279     child = subprocess.Popen(cmd, shell=via_shell,
280                              stderr=subprocess.STDOUT,
281                              stdout=fh,
282                              stdin=subprocess.PIPE,
283                              close_fds=True, env=env,
284                              cwd=cwd)
285
286     child.stdin.close()
287     status = child.wait()
288   finally:
289     fh.close()
290   return status
291
292
293 def RunParts(dir_name, env=None, reset_env=False):
294   """Run Scripts or programs in a directory
295
296   @type dir_name: string
297   @param dir_name: absolute path to a directory
298   @type env: dict
299   @param env: The environment to use
300   @type reset_env: boolean
301   @param reset_env: whether to reset or keep the default os environment
302   @rtype: list of tuples
303   @return: list of (name, (one of RUNDIR_STATUS), RunResult)
304
305   """
306   rr = []
307
308   try:
309     dir_contents = ListVisibleFiles(dir_name)
310   except OSError, err:
311     logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
312     return rr
313
314   for relname in sorted(dir_contents):
315     fname = PathJoin(dir_name, relname)
316     if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
317             constants.EXT_PLUGIN_MASK.match(relname) is not None):
318       rr.append((relname, constants.RUNPARTS_SKIP, None))
319     else:
320       try:
321         result = RunCmd([fname], env=env, reset_env=reset_env)
322       except Exception, err: # pylint: disable-msg=W0703
323         rr.append((relname, constants.RUNPARTS_ERR, str(err)))
324       else:
325         rr.append((relname, constants.RUNPARTS_RUN, result))
326
327   return rr
328
329
330 def RemoveFile(filename):
331   """Remove a file ignoring some errors.
332
333   Remove a file, ignoring non-existing ones or directories. Other
334   errors are passed.
335
336   @type filename: str
337   @param filename: the file to be removed
338
339   """
340   try:
341     os.unlink(filename)
342   except OSError, err:
343     if err.errno not in (errno.ENOENT, errno.EISDIR):
344       raise
345
346
347 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
348   """Renames a file.
349
350   @type old: string
351   @param old: Original path
352   @type new: string
353   @param new: New path
354   @type mkdir: bool
355   @param mkdir: Whether to create target directory if it doesn't exist
356   @type mkdir_mode: int
357   @param mkdir_mode: Mode for newly created directories
358
359   """
360   try:
361     return os.rename(old, new)
362   except OSError, err:
363     # In at least one use case of this function, the job queue, directory
364     # creation is very rare. Checking for the directory before renaming is not
365     # as efficient.
366     if mkdir and err.errno == errno.ENOENT:
367       # Create directory and try again
368       Makedirs(os.path.dirname(new))
369
370       return os.rename(old, new)
371
372     raise
373
374
375 def Makedirs(path, mode=0750):
376   """Super-mkdir; create a leaf directory and all intermediate ones.
377
378   This is a wrapper around C{os.makedirs} adding error handling not implemented
379   before Python 2.5.
380
381   """
382   try:
383     os.makedirs(path, mode)
384   except OSError, err:
385     # Ignore EEXIST. This is only handled in os.makedirs as included in
386     # Python 2.5 and above.
387     if err.errno != errno.EEXIST or not os.path.exists(path):
388       raise
389
390
391 def ResetTempfileModule():
392   """Resets the random name generator of the tempfile module.
393
394   This function should be called after C{os.fork} in the child process to
395   ensure it creates a newly seeded random generator. Otherwise it would
396   generate the same random parts as the parent process. If several processes
397   race for the creation of a temporary file, this could lead to one not getting
398   a temporary name.
399
400   """
401   # pylint: disable-msg=W0212
402   if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
403     tempfile._once_lock.acquire()
404     try:
405       # Reset random name generator
406       tempfile._name_sequence = None
407     finally:
408       tempfile._once_lock.release()
409   else:
410     logging.critical("The tempfile module misses at least one of the"
411                      " '_once_lock' and '_name_sequence' attributes")
412
413
414 def _FingerprintFile(filename):
415   """Compute the fingerprint of a file.
416
417   If the file does not exist, a None will be returned
418   instead.
419
420   @type filename: str
421   @param filename: the filename to checksum
422   @rtype: str
423   @return: the hex digest of the sha checksum of the contents
424       of the file
425
426   """
427   if not (os.path.exists(filename) and os.path.isfile(filename)):
428     return None
429
430   f = open(filename)
431
432   fp = sha1()
433   while True:
434     data = f.read(4096)
435     if not data:
436       break
437
438     fp.update(data)
439
440   return fp.hexdigest()
441
442
443 def FingerprintFiles(files):
444   """Compute fingerprints for a list of files.
445
446   @type files: list
447   @param files: the list of filename to fingerprint
448   @rtype: dict
449   @return: a dictionary filename: fingerprint, holding only
450       existing files
451
452   """
453   ret = {}
454
455   for filename in files:
456     cksum = _FingerprintFile(filename)
457     if cksum:
458       ret[filename] = cksum
459
460   return ret
461
462
463 def ForceDictType(target, key_types, allowed_values=None):
464   """Force the values of a dict to have certain types.
465
466   @type target: dict
467   @param target: the dict to update
468   @type key_types: dict
469   @param key_types: dict mapping target dict keys to types
470                     in constants.ENFORCEABLE_TYPES
471   @type allowed_values: list
472   @keyword allowed_values: list of specially allowed values
473
474   """
475   if allowed_values is None:
476     allowed_values = []
477
478   if not isinstance(target, dict):
479     msg = "Expected dictionary, got '%s'" % target
480     raise errors.TypeEnforcementError(msg)
481
482   for key in target:
483     if key not in key_types:
484       msg = "Unknown key '%s'" % key
485       raise errors.TypeEnforcementError(msg)
486
487     if target[key] in allowed_values:
488       continue
489
490     ktype = key_types[key]
491     if ktype not in constants.ENFORCEABLE_TYPES:
492       msg = "'%s' has non-enforceable type %s" % (key, ktype)
493       raise errors.ProgrammerError(msg)
494
495     if ktype == constants.VTYPE_STRING:
496       if not isinstance(target[key], basestring):
497         if isinstance(target[key], bool) and not target[key]:
498           target[key] = ''
499         else:
500           msg = "'%s' (value %s) is not a valid string" % (key, target[key])
501           raise errors.TypeEnforcementError(msg)
502     elif ktype == constants.VTYPE_BOOL:
503       if isinstance(target[key], basestring) and target[key]:
504         if target[key].lower() == constants.VALUE_FALSE:
505           target[key] = False
506         elif target[key].lower() == constants.VALUE_TRUE:
507           target[key] = True
508         else:
509           msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
510           raise errors.TypeEnforcementError(msg)
511       elif target[key]:
512         target[key] = True
513       else:
514         target[key] = False
515     elif ktype == constants.VTYPE_SIZE:
516       try:
517         target[key] = ParseUnit(target[key])
518       except errors.UnitParseError, err:
519         msg = "'%s' (value %s) is not a valid size. error: %s" % \
520               (key, target[key], err)
521         raise errors.TypeEnforcementError(msg)
522     elif ktype == constants.VTYPE_INT:
523       try:
524         target[key] = int(target[key])
525       except (ValueError, TypeError):
526         msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
527         raise errors.TypeEnforcementError(msg)
528
529
530 def IsProcessAlive(pid):
531   """Check if a given pid exists on the system.
532
533   @note: zombie status is not handled, so zombie processes
534       will be returned as alive
535   @type pid: int
536   @param pid: the process ID to check
537   @rtype: boolean
538   @return: True if the process exists
539
540   """
541   if pid <= 0:
542     return False
543
544   try:
545     os.stat("/proc/%d/status" % pid)
546     return True
547   except EnvironmentError, err:
548     if err.errno in (errno.ENOENT, errno.ENOTDIR):
549       return False
550     raise
551
552
553 def ReadPidFile(pidfile):
554   """Read a pid from a file.
555
556   @type  pidfile: string
557   @param pidfile: path to the file containing the pid
558   @rtype: int
559   @return: The process id, if the file exists and contains a valid PID,
560            otherwise 0
561
562   """
563   try:
564     raw_data = ReadFile(pidfile)
565   except EnvironmentError, err:
566     if err.errno != errno.ENOENT:
567       logging.exception("Can't read pid file")
568     return 0
569
570   try:
571     pid = int(raw_data)
572   except (TypeError, ValueError), err:
573     logging.info("Can't parse pid file contents", exc_info=True)
574     return 0
575
576   return pid
577
578
579 def MatchNameComponent(key, name_list, case_sensitive=True):
580   """Try to match a name against a list.
581
582   This function will try to match a name like test1 against a list
583   like C{['test1.example.com', 'test2.example.com', ...]}. Against
584   this list, I{'test1'} as well as I{'test1.example'} will match, but
585   not I{'test1.ex'}. A multiple match will be considered as no match
586   at all (e.g. I{'test1'} against C{['test1.example.com',
587   'test1.example.org']}), except when the key fully matches an entry
588   (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
589
590   @type key: str
591   @param key: the name to be searched
592   @type name_list: list
593   @param name_list: the list of strings against which to search the key
594   @type case_sensitive: boolean
595   @param case_sensitive: whether to provide a case-sensitive match
596
597   @rtype: None or str
598   @return: None if there is no match I{or} if there are multiple matches,
599       otherwise the element from the list which matches
600
601   """
602   if key in name_list:
603     return key
604
605   re_flags = 0
606   if not case_sensitive:
607     re_flags |= re.IGNORECASE
608     key = key.upper()
609   mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
610   names_filtered = []
611   string_matches = []
612   for name in name_list:
613     if mo.match(name) is not None:
614       names_filtered.append(name)
615       if not case_sensitive and key == name.upper():
616         string_matches.append(name)
617
618   if len(string_matches) == 1:
619     return string_matches[0]
620   if len(names_filtered) == 1:
621     return names_filtered[0]
622   return None
623
624
625 class HostInfo:
626   """Class implementing resolver and hostname functionality
627
628   """
629   _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
630
631   def __init__(self, name=None):
632     """Initialize the host name object.
633
634     If the name argument is not passed, it will use this system's
635     name.
636
637     """
638     if name is None:
639       name = self.SysName()
640
641     self.query = name
642     self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
643     self.ip = self.ipaddrs[0]
644
645   def ShortName(self):
646     """Returns the hostname without domain.
647
648     """
649     return self.name.split('.')[0]
650
651   @staticmethod
652   def SysName():
653     """Return the current system's name.
654
655     This is simply a wrapper over C{socket.gethostname()}.
656
657     """
658     return socket.gethostname()
659
660   @staticmethod
661   def LookupHostname(hostname):
662     """Look up hostname
663
664     @type hostname: str
665     @param hostname: hostname to look up
666
667     @rtype: tuple
668     @return: a tuple (name, aliases, ipaddrs) as returned by
669         C{socket.gethostbyname_ex}
670     @raise errors.ResolverError: in case of errors in resolving
671
672     """
673     try:
674       result = socket.gethostbyname_ex(hostname)
675     except socket.gaierror, err:
676       # hostname not found in DNS
677       raise errors.ResolverError(hostname, err.args[0], err.args[1])
678
679     return result
680
681   @classmethod
682   def NormalizeName(cls, hostname):
683     """Validate and normalize the given hostname.
684
685     @attention: the validation is a bit more relaxed than the standards
686         require; most importantly, we allow underscores in names
687     @raise errors.OpPrereqError: when the name is not valid
688
689     """
690     hostname = hostname.lower()
691     if (not cls._VALID_NAME_RE.match(hostname) or
692         # double-dots, meaning empty label
693         ".." in hostname or
694         # empty initial label
695         hostname.startswith(".")):
696       raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
697                                  errors.ECODE_INVAL)
698     if hostname.endswith("."):
699       hostname = hostname.rstrip(".")
700     return hostname
701
702
703 def GetHostInfo(name=None):
704   """Lookup host name and raise an OpPrereqError for failures"""
705
706   try:
707     return HostInfo(name)
708   except errors.ResolverError, err:
709     raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
710                                (err[0], err[2]), errors.ECODE_RESOLVER)
711
712
713 def ListVolumeGroups():
714   """List volume groups and their size
715
716   @rtype: dict
717   @return:
718        Dictionary with keys volume name and values
719        the size of the volume
720
721   """
722   command = "vgs --noheadings --units m --nosuffix -o name,size"
723   result = RunCmd(command)
724   retval = {}
725   if result.failed:
726     return retval
727
728   for line in result.stdout.splitlines():
729     try:
730       name, size = line.split()
731       size = int(float(size))
732     except (IndexError, ValueError), err:
733       logging.error("Invalid output from vgs (%s): %s", err, line)
734       continue
735
736     retval[name] = size
737
738   return retval
739
740
741 def BridgeExists(bridge):
742   """Check whether the given bridge exists in the system
743
744   @type bridge: str
745   @param bridge: the bridge name to check
746   @rtype: boolean
747   @return: True if it does
748
749   """
750   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
751
752
753 def NiceSort(name_list):
754   """Sort a list of strings based on digit and non-digit groupings.
755
756   Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
757   will sort the list in the logical order C{['a1', 'a2', 'a10',
758   'a11']}.
759
760   The sort algorithm breaks each name in groups of either only-digits
761   or no-digits. Only the first eight such groups are considered, and
762   after that we just use what's left of the string.
763
764   @type name_list: list
765   @param name_list: the names to be sorted
766   @rtype: list
767   @return: a copy of the name list sorted with our algorithm
768
769   """
770   _SORTER_BASE = "(\D+|\d+)"
771   _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
772                                                   _SORTER_BASE, _SORTER_BASE,
773                                                   _SORTER_BASE, _SORTER_BASE,
774                                                   _SORTER_BASE, _SORTER_BASE)
775   _SORTER_RE = re.compile(_SORTER_FULL)
776   _SORTER_NODIGIT = re.compile("^\D*$")
777   def _TryInt(val):
778     """Attempts to convert a variable to integer."""
779     if val is None or _SORTER_NODIGIT.match(val):
780       return val
781     rval = int(val)
782     return rval
783
784   to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
785              for name in name_list]
786   to_sort.sort()
787   return [tup[1] for tup in to_sort]
788
789
790 def TryConvert(fn, val):
791   """Try to convert a value ignoring errors.
792
793   This function tries to apply function I{fn} to I{val}. If no
794   C{ValueError} or C{TypeError} exceptions are raised, it will return
795   the result, else it will return the original value. Any other
796   exceptions are propagated to the caller.
797
798   @type fn: callable
799   @param fn: function to apply to the value
800   @param val: the value to be converted
801   @return: The converted value if the conversion was successful,
802       otherwise the original value.
803
804   """
805   try:
806     nv = fn(val)
807   except (ValueError, TypeError):
808     nv = val
809   return nv
810
811
812 def IsValidIP(ip):
813   """Verifies the syntax of an IPv4 address.
814
815   This function checks if the IPv4 address passes is valid or not based
816   on syntax (not IP range, class calculations, etc.).
817
818   @type ip: str
819   @param ip: the address to be checked
820   @rtype: a regular expression match object
821   @return: a regular expression match object, or None if the
822       address is not valid
823
824   """
825   unit = "(0|[1-9]\d{0,2})"
826   #TODO: convert and return only boolean
827   return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
828
829
830 def IsValidShellParam(word):
831   """Verifies is the given word is safe from the shell's p.o.v.
832
833   This means that we can pass this to a command via the shell and be
834   sure that it doesn't alter the command line and is passed as such to
835   the actual command.
836
837   Note that we are overly restrictive here, in order to be on the safe
838   side.
839
840   @type word: str
841   @param word: the word to check
842   @rtype: boolean
843   @return: True if the word is 'safe'
844
845   """
846   return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
847
848
849 def BuildShellCmd(template, *args):
850   """Build a safe shell command line from the given arguments.
851
852   This function will check all arguments in the args list so that they
853   are valid shell parameters (i.e. they don't contain shell
854   metacharacters). If everything is ok, it will return the result of
855   template % args.
856
857   @type template: str
858   @param template: the string holding the template for the
859       string formatting
860   @rtype: str
861   @return: the expanded command line
862
863   """
864   for word in args:
865     if not IsValidShellParam(word):
866       raise errors.ProgrammerError("Shell argument '%s' contains"
867                                    " invalid characters" % word)
868   return template % args
869
870
871 def FormatUnit(value, units):
872   """Formats an incoming number of MiB with the appropriate unit.
873
874   @type value: int
875   @param value: integer representing the value in MiB (1048576)
876   @type units: char
877   @param units: the type of formatting we should do:
878       - 'h' for automatic scaling
879       - 'm' for MiBs
880       - 'g' for GiBs
881       - 't' for TiBs
882   @rtype: str
883   @return: the formatted value (with suffix)
884
885   """
886   if units not in ('m', 'g', 't', 'h'):
887     raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
888
889   suffix = ''
890
891   if units == 'm' or (units == 'h' and value < 1024):
892     if units == 'h':
893       suffix = 'M'
894     return "%d%s" % (round(value, 0), suffix)
895
896   elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
897     if units == 'h':
898       suffix = 'G'
899     return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
900
901   else:
902     if units == 'h':
903       suffix = 'T'
904     return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
905
906
907 def ParseUnit(input_string):
908   """Tries to extract number and scale from the given string.
909
910   Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
911   [UNIT]}. If no unit is specified, it defaults to MiB. Return value
912   is always an int in MiB.
913
914   """
915   m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
916   if not m:
917     raise errors.UnitParseError("Invalid format")
918
919   value = float(m.groups()[0])
920
921   unit = m.groups()[1]
922   if unit:
923     lcunit = unit.lower()
924   else:
925     lcunit = 'm'
926
927   if lcunit in ('m', 'mb', 'mib'):
928     # Value already in MiB
929     pass
930
931   elif lcunit in ('g', 'gb', 'gib'):
932     value *= 1024
933
934   elif lcunit in ('t', 'tb', 'tib'):
935     value *= 1024 * 1024
936
937   else:
938     raise errors.UnitParseError("Unknown unit: %s" % unit)
939
940   # Make sure we round up
941   if int(value) < value:
942     value += 1
943
944   # Round up to the next multiple of 4
945   value = int(value)
946   if value % 4:
947     value += 4 - value % 4
948
949   return value
950
951
952 def AddAuthorizedKey(file_name, key):
953   """Adds an SSH public key to an authorized_keys file.
954
955   @type file_name: str
956   @param file_name: path to authorized_keys file
957   @type key: str
958   @param key: string containing key
959
960   """
961   key_fields = key.split()
962
963   f = open(file_name, 'a+')
964   try:
965     nl = True
966     for line in f:
967       # Ignore whitespace changes
968       if line.split() == key_fields:
969         break
970       nl = line.endswith('\n')
971     else:
972       if not nl:
973         f.write("\n")
974       f.write(key.rstrip('\r\n'))
975       f.write("\n")
976       f.flush()
977   finally:
978     f.close()
979
980
981 def RemoveAuthorizedKey(file_name, key):
982   """Removes an SSH public key from an authorized_keys file.
983
984   @type file_name: str
985   @param file_name: path to authorized_keys file
986   @type key: str
987   @param key: string containing key
988
989   """
990   key_fields = key.split()
991
992   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
993   try:
994     out = os.fdopen(fd, 'w')
995     try:
996       f = open(file_name, 'r')
997       try:
998         for line in f:
999           # Ignore whitespace changes while comparing lines
1000           if line.split() != key_fields:
1001             out.write(line)
1002
1003         out.flush()
1004         os.rename(tmpname, file_name)
1005       finally:
1006         f.close()
1007     finally:
1008       out.close()
1009   except:
1010     RemoveFile(tmpname)
1011     raise
1012
1013
1014 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
1015   """Sets the name of an IP address and hostname in /etc/hosts.
1016
1017   @type file_name: str
1018   @param file_name: path to the file to modify (usually C{/etc/hosts})
1019   @type ip: str
1020   @param ip: the IP address
1021   @type hostname: str
1022   @param hostname: the hostname to be added
1023   @type aliases: list
1024   @param aliases: the list of aliases to add for the hostname
1025
1026   """
1027   # FIXME: use WriteFile + fn rather than duplicating its efforts
1028   # Ensure aliases are unique
1029   aliases = UniqueSequence([hostname] + aliases)[1:]
1030
1031   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1032   try:
1033     out = os.fdopen(fd, 'w')
1034     try:
1035       f = open(file_name, 'r')
1036       try:
1037         for line in f:
1038           fields = line.split()
1039           if fields and not fields[0].startswith('#') and ip == fields[0]:
1040             continue
1041           out.write(line)
1042
1043         out.write("%s\t%s" % (ip, hostname))
1044         if aliases:
1045           out.write(" %s" % ' '.join(aliases))
1046         out.write('\n')
1047
1048         out.flush()
1049         os.fsync(out)
1050         os.chmod(tmpname, 0644)
1051         os.rename(tmpname, file_name)
1052       finally:
1053         f.close()
1054     finally:
1055       out.close()
1056   except:
1057     RemoveFile(tmpname)
1058     raise
1059
1060
1061 def AddHostToEtcHosts(hostname):
1062   """Wrapper around SetEtcHostsEntry.
1063
1064   @type hostname: str
1065   @param hostname: a hostname that will be resolved and added to
1066       L{constants.ETC_HOSTS}
1067
1068   """
1069   hi = HostInfo(name=hostname)
1070   SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1071
1072
1073 def RemoveEtcHostsEntry(file_name, hostname):
1074   """Removes a hostname from /etc/hosts.
1075
1076   IP addresses without names are removed from the file.
1077
1078   @type file_name: str
1079   @param file_name: path to the file to modify (usually C{/etc/hosts})
1080   @type hostname: str
1081   @param hostname: the hostname to be removed
1082
1083   """
1084   # FIXME: use WriteFile + fn rather than duplicating its efforts
1085   fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1086   try:
1087     out = os.fdopen(fd, 'w')
1088     try:
1089       f = open(file_name, 'r')
1090       try:
1091         for line in f:
1092           fields = line.split()
1093           if len(fields) > 1 and not fields[0].startswith('#'):
1094             names = fields[1:]
1095             if hostname in names:
1096               while hostname in names:
1097                 names.remove(hostname)
1098               if names:
1099                 out.write("%s %s\n" % (fields[0], ' '.join(names)))
1100               continue
1101
1102           out.write(line)
1103
1104         out.flush()
1105         os.fsync(out)
1106         os.chmod(tmpname, 0644)
1107         os.rename(tmpname, file_name)
1108       finally:
1109         f.close()
1110     finally:
1111       out.close()
1112   except:
1113     RemoveFile(tmpname)
1114     raise
1115
1116
1117 def RemoveHostFromEtcHosts(hostname):
1118   """Wrapper around RemoveEtcHostsEntry.
1119
1120   @type hostname: str
1121   @param hostname: hostname that will be resolved and its
1122       full and shot name will be removed from
1123       L{constants.ETC_HOSTS}
1124
1125   """
1126   hi = HostInfo(name=hostname)
1127   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1128   RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1129
1130
1131 def TimestampForFilename():
1132   """Returns the current time formatted for filenames.
1133
1134   The format doesn't contain colons as some shells and applications them as
1135   separators.
1136
1137   """
1138   return time.strftime("%Y-%m-%d_%H_%M_%S")
1139
1140
1141 def CreateBackup(file_name):
1142   """Creates a backup of a file.
1143
1144   @type file_name: str
1145   @param file_name: file to be backed up
1146   @rtype: str
1147   @return: the path to the newly created backup
1148   @raise errors.ProgrammerError: for invalid file names
1149
1150   """
1151   if not os.path.isfile(file_name):
1152     raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1153                                 file_name)
1154
1155   prefix = ("%s.backup-%s." %
1156             (os.path.basename(file_name), TimestampForFilename()))
1157   dir_name = os.path.dirname(file_name)
1158
1159   fsrc = open(file_name, 'rb')
1160   try:
1161     (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1162     fdst = os.fdopen(fd, 'wb')
1163     try:
1164       logging.debug("Backing up %s at %s", file_name, backup_name)
1165       shutil.copyfileobj(fsrc, fdst)
1166     finally:
1167       fdst.close()
1168   finally:
1169     fsrc.close()
1170
1171   return backup_name
1172
1173
1174 def ShellQuote(value):
1175   """Quotes shell argument according to POSIX.
1176
1177   @type value: str
1178   @param value: the argument to be quoted
1179   @rtype: str
1180   @return: the quoted value
1181
1182   """
1183   if _re_shell_unquoted.match(value):
1184     return value
1185   else:
1186     return "'%s'" % value.replace("'", "'\\''")
1187
1188
1189 def ShellQuoteArgs(args):
1190   """Quotes a list of shell arguments.
1191
1192   @type args: list
1193   @param args: list of arguments to be quoted
1194   @rtype: str
1195   @return: the quoted arguments concatenated with spaces
1196
1197   """
1198   return ' '.join([ShellQuote(i) for i in args])
1199
1200
1201 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1202   """Simple ping implementation using TCP connect(2).
1203
1204   Check if the given IP is reachable by doing attempting a TCP connect
1205   to it.
1206
1207   @type target: str
1208   @param target: the IP or hostname to ping
1209   @type port: int
1210   @param port: the port to connect to
1211   @type timeout: int
1212   @param timeout: the timeout on the connection attempt
1213   @type live_port_needed: boolean
1214   @param live_port_needed: whether a closed port will cause the
1215       function to return failure, as if there was a timeout
1216   @type source: str or None
1217   @param source: if specified, will cause the connect to be made
1218       from this specific source address; failures to bind other
1219       than C{EADDRNOTAVAIL} will be ignored
1220
1221   """
1222   sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1223
1224   success = False
1225
1226   if source is not None:
1227     try:
1228       sock.bind((source, 0))
1229     except socket.error, (errcode, _):
1230       if errcode == errno.EADDRNOTAVAIL:
1231         success = False
1232
1233   sock.settimeout(timeout)
1234
1235   try:
1236     sock.connect((target, port))
1237     sock.close()
1238     success = True
1239   except socket.timeout:
1240     success = False
1241   except socket.error, (errcode, _):
1242     success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1243
1244   return success
1245
1246
1247 def OwnIpAddress(address):
1248   """Check if the current host has the the given IP address.
1249
1250   Currently this is done by TCP-pinging the address from the loopback
1251   address.
1252
1253   @type address: string
1254   @param address: the address to check
1255   @rtype: bool
1256   @return: True if we own the address
1257
1258   """
1259   return TcpPing(address, constants.DEFAULT_NODED_PORT,
1260                  source=constants.LOCALHOST_IP_ADDRESS)
1261
1262
1263 def ListVisibleFiles(path):
1264   """Returns a list of visible files in a directory.
1265
1266   @type path: str
1267   @param path: the directory to enumerate
1268   @rtype: list
1269   @return: the list of all files not starting with a dot
1270   @raise ProgrammerError: if L{path} is not an absolue and normalized path
1271
1272   """
1273   if not IsNormAbsPath(path):
1274     raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1275                                  " absolute/normalized: '%s'" % path)
1276   files = [i for i in os.listdir(path) if not i.startswith(".")]
1277   files.sort()
1278   return files
1279
1280
1281 def GetHomeDir(user, default=None):
1282   """Try to get the homedir of the given user.
1283
1284   The user can be passed either as a string (denoting the name) or as
1285   an integer (denoting the user id). If the user is not found, the
1286   'default' argument is returned, which defaults to None.
1287
1288   """
1289   try:
1290     if isinstance(user, basestring):
1291       result = pwd.getpwnam(user)
1292     elif isinstance(user, (int, long)):
1293       result = pwd.getpwuid(user)
1294     else:
1295       raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1296                                    type(user))
1297   except KeyError:
1298     return default
1299   return result.pw_dir
1300
1301
1302 def NewUUID():
1303   """Returns a random UUID.
1304
1305   @note: This is a Linux-specific method as it uses the /proc
1306       filesystem.
1307   @rtype: str
1308
1309   """
1310   return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1311
1312
1313 def GenerateSecret(numbytes=20):
1314   """Generates a random secret.
1315
1316   This will generate a pseudo-random secret returning an hex string
1317   (so that it can be used where an ASCII string is needed).
1318
1319   @param numbytes: the number of bytes which will be represented by the returned
1320       string (defaulting to 20, the length of a SHA1 hash)
1321   @rtype: str
1322   @return: an hex representation of the pseudo-random sequence
1323
1324   """
1325   return os.urandom(numbytes).encode('hex')
1326
1327
1328 def EnsureDirs(dirs):
1329   """Make required directories, if they don't exist.
1330
1331   @param dirs: list of tuples (dir_name, dir_mode)
1332   @type dirs: list of (string, integer)
1333
1334   """
1335   for dir_name, dir_mode in dirs:
1336     try:
1337       os.mkdir(dir_name, dir_mode)
1338     except EnvironmentError, err:
1339       if err.errno != errno.EEXIST:
1340         raise errors.GenericError("Cannot create needed directory"
1341                                   " '%s': %s" % (dir_name, err))
1342     if not os.path.isdir(dir_name):
1343       raise errors.GenericError("%s is not a directory" % dir_name)
1344
1345
1346 def ReadFile(file_name, size=-1):
1347   """Reads a file.
1348
1349   @type size: int
1350   @param size: Read at most size bytes (if negative, entire file)
1351   @rtype: str
1352   @return: the (possibly partial) content of the file
1353
1354   """
1355   f = open(file_name, "r")
1356   try:
1357     return f.read(size)
1358   finally:
1359     f.close()
1360
1361
1362 def WriteFile(file_name, fn=None, data=None,
1363               mode=None, uid=-1, gid=-1,
1364               atime=None, mtime=None, close=True,
1365               dry_run=False, backup=False,
1366               prewrite=None, postwrite=None):
1367   """(Over)write a file atomically.
1368
1369   The file_name and either fn (a function taking one argument, the
1370   file descriptor, and which should write the data to it) or data (the
1371   contents of the file) must be passed. The other arguments are
1372   optional and allow setting the file mode, owner and group, and the
1373   mtime/atime of the file.
1374
1375   If the function doesn't raise an exception, it has succeeded and the
1376   target file has the new contents. If the function has raised an
1377   exception, an existing target file should be unmodified and the
1378   temporary file should be removed.
1379
1380   @type file_name: str
1381   @param file_name: the target filename
1382   @type fn: callable
1383   @param fn: content writing function, called with
1384       file descriptor as parameter
1385   @type data: str
1386   @param data: contents of the file
1387   @type mode: int
1388   @param mode: file mode
1389   @type uid: int
1390   @param uid: the owner of the file
1391   @type gid: int
1392   @param gid: the group of the file
1393   @type atime: int
1394   @param atime: a custom access time to be set on the file
1395   @type mtime: int
1396   @param mtime: a custom modification time to be set on the file
1397   @type close: boolean
1398   @param close: whether to close file after writing it
1399   @type prewrite: callable
1400   @param prewrite: function to be called before writing content
1401   @type postwrite: callable
1402   @param postwrite: function to be called after writing content
1403
1404   @rtype: None or int
1405   @return: None if the 'close' parameter evaluates to True,
1406       otherwise the file descriptor
1407
1408   @raise errors.ProgrammerError: if any of the arguments are not valid
1409
1410   """
1411   if not os.path.isabs(file_name):
1412     raise errors.ProgrammerError("Path passed to WriteFile is not"
1413                                  " absolute: '%s'" % file_name)
1414
1415   if [fn, data].count(None) != 1:
1416     raise errors.ProgrammerError("fn or data required")
1417
1418   if [atime, mtime].count(None) == 1:
1419     raise errors.ProgrammerError("Both atime and mtime must be either"
1420                                  " set or None")
1421
1422   if backup and not dry_run and os.path.isfile(file_name):
1423     CreateBackup(file_name)
1424
1425   dir_name, base_name = os.path.split(file_name)
1426   fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1427   do_remove = True
1428   # here we need to make sure we remove the temp file, if any error
1429   # leaves it in place
1430   try:
1431     if uid != -1 or gid != -1:
1432       os.chown(new_name, uid, gid)
1433     if mode:
1434       os.chmod(new_name, mode)
1435     if callable(prewrite):
1436       prewrite(fd)
1437     if data is not None:
1438       os.write(fd, data)
1439     else:
1440       fn(fd)
1441     if callable(postwrite):
1442       postwrite(fd)
1443     os.fsync(fd)
1444     if atime is not None and mtime is not None:
1445       os.utime(new_name, (atime, mtime))
1446     if not dry_run:
1447       os.rename(new_name, file_name)
1448       do_remove = False
1449   finally:
1450     if close:
1451       os.close(fd)
1452       result = None
1453     else:
1454       result = fd
1455     if do_remove:
1456       RemoveFile(new_name)
1457
1458   return result
1459
1460
1461 def FirstFree(seq, base=0):
1462   """Returns the first non-existing integer from seq.
1463
1464   The seq argument should be a sorted list of positive integers. The
1465   first time the index of an element is smaller than the element
1466   value, the index will be returned.
1467
1468   The base argument is used to start at a different offset,
1469   i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1470
1471   Example: C{[0, 1, 3]} will return I{2}.
1472
1473   @type seq: sequence
1474   @param seq: the sequence to be analyzed.
1475   @type base: int
1476   @param base: use this value as the base index of the sequence
1477   @rtype: int
1478   @return: the first non-used index in the sequence
1479
1480   """
1481   for idx, elem in enumerate(seq):
1482     assert elem >= base, "Passed element is higher than base offset"
1483     if elem > idx + base:
1484       # idx is not used
1485       return idx + base
1486   return None
1487
1488
1489 def all(seq, pred=bool): # pylint: disable-msg=W0622
1490   "Returns True if pred(x) is True for every element in the iterable"
1491   for _ in itertools.ifilterfalse(pred, seq):
1492     return False
1493   return True
1494
1495
1496 def any(seq, pred=bool): # pylint: disable-msg=W0622
1497   "Returns True if pred(x) is True for at least one element in the iterable"
1498   for _ in itertools.ifilter(pred, seq):
1499     return True
1500   return False
1501
1502
1503 def SingleWaitForFdCondition(fdobj, event, timeout):
1504   """Waits for a condition to occur on the socket.
1505
1506   Immediately returns at the first interruption.
1507
1508   @type fdobj: integer or object supporting a fileno() method
1509   @param fdobj: entity to wait for events on
1510   @type event: integer
1511   @param event: ORed condition (see select module)
1512   @type timeout: float or None
1513   @param timeout: Timeout in seconds
1514   @rtype: int or None
1515   @return: None for timeout, otherwise occured conditions
1516
1517   """
1518   check = (event | select.POLLPRI |
1519            select.POLLNVAL | select.POLLHUP | select.POLLERR)
1520
1521   if timeout is not None:
1522     # Poller object expects milliseconds
1523     timeout *= 1000
1524
1525   poller = select.poll()
1526   poller.register(fdobj, event)
1527   try:
1528     # TODO: If the main thread receives a signal and we have no timeout, we
1529     # could wait forever. This should check a global "quit" flag or something
1530     # every so often.
1531     io_events = poller.poll(timeout)
1532   except select.error, err:
1533     if err[0] != errno.EINTR:
1534       raise
1535     io_events = []
1536   if io_events and io_events[0][1] & check:
1537     return io_events[0][1]
1538   else:
1539     return None
1540
1541
1542 class FdConditionWaiterHelper(object):
1543   """Retry helper for WaitForFdCondition.
1544
1545   This class contains the retried and wait functions that make sure
1546   WaitForFdCondition can continue waiting until the timeout is actually
1547   expired.
1548
1549   """
1550
1551   def __init__(self, timeout):
1552     self.timeout = timeout
1553
1554   def Poll(self, fdobj, event):
1555     result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1556     if result is None:
1557       raise RetryAgain()
1558     else:
1559       return result
1560
1561   def UpdateTimeout(self, timeout):
1562     self.timeout = timeout
1563
1564
1565 def WaitForFdCondition(fdobj, event, timeout):
1566   """Waits for a condition to occur on the socket.
1567
1568   Retries until the timeout is expired, even if interrupted.
1569
1570   @type fdobj: integer or object supporting a fileno() method
1571   @param fdobj: entity to wait for events on
1572   @type event: integer
1573   @param event: ORed condition (see select module)
1574   @type timeout: float or None
1575   @param timeout: Timeout in seconds
1576   @rtype: int or None
1577   @return: None for timeout, otherwise occured conditions
1578
1579   """
1580   if timeout is not None:
1581     retrywaiter = FdConditionWaiterHelper(timeout)
1582     result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1583                    args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1584   else:
1585     result = None
1586     while result is None:
1587       result = SingleWaitForFdCondition(fdobj, event, timeout)
1588   return result
1589
1590
1591 def partition(seq, pred=bool): # # pylint: disable-msg=W0622
1592   "Partition a list in two, based on the given predicate"
1593   return (list(itertools.ifilter(pred, seq)),
1594           list(itertools.ifilterfalse(pred, seq)))
1595
1596
1597 def UniqueSequence(seq):
1598   """Returns a list with unique elements.
1599
1600   Element order is preserved.
1601
1602   @type seq: sequence
1603   @param seq: the sequence with the source elements
1604   @rtype: list
1605   @return: list of unique elements from seq
1606
1607   """
1608   seen = set()
1609   return [i for i in seq if i not in seen and not seen.add(i)]
1610
1611
1612 def NormalizeAndValidateMac(mac):
1613   """Normalizes and check if a MAC address is valid.
1614
1615   Checks whether the supplied MAC address is formally correct, only
1616   accepts colon separated format. Normalize it to all lower.
1617
1618   @type mac: str
1619   @param mac: the MAC to be validated
1620   @rtype: str
1621   @return: returns the normalized and validated MAC.
1622
1623   @raise errors.OpPrereqError: If the MAC isn't valid
1624
1625   """
1626   mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1627   if not mac_check.match(mac):
1628     raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1629                                mac, errors.ECODE_INVAL)
1630
1631   return mac.lower()
1632
1633
1634 def TestDelay(duration):
1635   """Sleep for a fixed amount of time.
1636
1637   @type duration: float
1638   @param duration: the sleep duration
1639   @rtype: boolean
1640   @return: False for negative value, True otherwise
1641
1642   """
1643   if duration < 0:
1644     return False, "Invalid sleep duration"
1645   time.sleep(duration)
1646   return True, None
1647
1648
1649 def _CloseFDNoErr(fd, retries=5):
1650   """Close a file descriptor ignoring errors.
1651
1652   @type fd: int
1653   @param fd: the file descriptor
1654   @type retries: int
1655   @param retries: how many retries to make, in case we get any
1656       other error than EBADF
1657
1658   """
1659   try:
1660     os.close(fd)
1661   except OSError, err:
1662     if err.errno != errno.EBADF:
1663       if retries > 0:
1664         _CloseFDNoErr(fd, retries - 1)
1665     # else either it's closed already or we're out of retries, so we
1666     # ignore this and go on
1667
1668
1669 def CloseFDs(noclose_fds=None):
1670   """Close file descriptors.
1671
1672   This closes all file descriptors above 2 (i.e. except
1673   stdin/out/err).
1674
1675   @type noclose_fds: list or None
1676   @param noclose_fds: if given, it denotes a list of file descriptor
1677       that should not be closed
1678
1679   """
1680   # Default maximum for the number of available file descriptors.
1681   if 'SC_OPEN_MAX' in os.sysconf_names:
1682     try:
1683       MAXFD = os.sysconf('SC_OPEN_MAX')
1684       if MAXFD < 0:
1685         MAXFD = 1024
1686     except OSError:
1687       MAXFD = 1024
1688   else:
1689     MAXFD = 1024
1690   maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1691   if (maxfd == resource.RLIM_INFINITY):
1692     maxfd = MAXFD
1693
1694   # Iterate through and close all file descriptors (except the standard ones)
1695   for fd in range(3, maxfd):
1696     if noclose_fds and fd in noclose_fds:
1697       continue
1698     _CloseFDNoErr(fd)
1699
1700
1701 def Daemonize(logfile):
1702   """Daemonize the current process.
1703
1704   This detaches the current process from the controlling terminal and
1705   runs it in the background as a daemon.
1706
1707   @type logfile: str
1708   @param logfile: the logfile to which we should redirect stdout/stderr
1709   @rtype: int
1710   @return: the value zero
1711
1712   """
1713   # pylint: disable-msg=W0212
1714   # yes, we really want os._exit
1715   UMASK = 077
1716   WORKDIR = "/"
1717
1718   # this might fail
1719   pid = os.fork()
1720   if (pid == 0):  # The first child.
1721     os.setsid()
1722     # this might fail
1723     pid = os.fork() # Fork a second child.
1724     if (pid == 0):  # The second child.
1725       os.chdir(WORKDIR)
1726       os.umask(UMASK)
1727     else:
1728       # exit() or _exit()?  See below.
1729       os._exit(0) # Exit parent (the first child) of the second child.
1730   else:
1731     os._exit(0) # Exit parent of the first child.
1732
1733   for fd in range(3):
1734     _CloseFDNoErr(fd)
1735   i = os.open("/dev/null", os.O_RDONLY) # stdin
1736   assert i == 0, "Can't close/reopen stdin"
1737   i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1738   assert i == 1, "Can't close/reopen stdout"
1739   # Duplicate standard output to standard error.
1740   os.dup2(1, 2)
1741   return 0
1742
1743
1744 def DaemonPidFileName(name):
1745   """Compute a ganeti pid file absolute path
1746
1747   @type name: str
1748   @param name: the daemon name
1749   @rtype: str
1750   @return: the full path to the pidfile corresponding to the given
1751       daemon name
1752
1753   """
1754   return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
1755
1756
1757 def EnsureDaemon(name):
1758   """Check for and start daemon if not alive.
1759
1760   """
1761   result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
1762   if result.failed:
1763     logging.error("Can't start daemon '%s', failure %s, output: %s",
1764                   name, result.fail_reason, result.output)
1765     return False
1766
1767   return True
1768
1769
1770 def WritePidFile(name):
1771   """Write the current process pidfile.
1772
1773   The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1774
1775   @type name: str
1776   @param name: the daemon name to use
1777   @raise errors.GenericError: if the pid file already exists and
1778       points to a live process
1779
1780   """
1781   pid = os.getpid()
1782   pidfilename = DaemonPidFileName(name)
1783   if IsProcessAlive(ReadPidFile(pidfilename)):
1784     raise errors.GenericError("%s contains a live process" % pidfilename)
1785
1786   WriteFile(pidfilename, data="%d\n" % pid)
1787
1788
1789 def RemovePidFile(name):
1790   """Remove the current process pidfile.
1791
1792   Any errors are ignored.
1793
1794   @type name: str
1795   @param name: the daemon name used to derive the pidfile name
1796
1797   """
1798   pidfilename = DaemonPidFileName(name)
1799   # TODO: we could check here that the file contains our pid
1800   try:
1801     RemoveFile(pidfilename)
1802   except: # pylint: disable-msg=W0702
1803     pass
1804
1805
1806 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1807                 waitpid=False):
1808   """Kill a process given by its pid.
1809
1810   @type pid: int
1811   @param pid: The PID to terminate.
1812   @type signal_: int
1813   @param signal_: The signal to send, by default SIGTERM
1814   @type timeout: int
1815   @param timeout: The timeout after which, if the process is still alive,
1816                   a SIGKILL will be sent. If not positive, no such checking
1817                   will be done
1818   @type waitpid: boolean
1819   @param waitpid: If true, we should waitpid on this process after
1820       sending signals, since it's our own child and otherwise it
1821       would remain as zombie
1822
1823   """
1824   def _helper(pid, signal_, wait):
1825     """Simple helper to encapsulate the kill/waitpid sequence"""
1826     os.kill(pid, signal_)
1827     if wait:
1828       try:
1829         os.waitpid(pid, os.WNOHANG)
1830       except OSError:
1831         pass
1832
1833   if pid <= 0:
1834     # kill with pid=0 == suicide
1835     raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1836
1837   if not IsProcessAlive(pid):
1838     return
1839
1840   _helper(pid, signal_, waitpid)
1841
1842   if timeout <= 0:
1843     return
1844
1845   def _CheckProcess():
1846     if not IsProcessAlive(pid):
1847       return
1848
1849     try:
1850       (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1851     except OSError:
1852       raise RetryAgain()
1853
1854     if result_pid > 0:
1855       return
1856
1857     raise RetryAgain()
1858
1859   try:
1860     # Wait up to $timeout seconds
1861     Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1862   except RetryTimeout:
1863     pass
1864
1865   if IsProcessAlive(pid):
1866     # Kill process if it's still alive
1867     _helper(pid, signal.SIGKILL, waitpid)
1868
1869
1870 def FindFile(name, search_path, test=os.path.exists):
1871   """Look for a filesystem object in a given path.
1872
1873   This is an abstract method to search for filesystem object (files,
1874   dirs) under a given search path.
1875
1876   @type name: str
1877   @param name: the name to look for
1878   @type search_path: str
1879   @param search_path: location to start at
1880   @type test: callable
1881   @param test: a function taking one argument that should return True
1882       if the a given object is valid; the default value is
1883       os.path.exists, causing only existing files to be returned
1884   @rtype: str or None
1885   @return: full path to the object if found, None otherwise
1886
1887   """
1888   # validate the filename mask
1889   if constants.EXT_PLUGIN_MASK.match(name) is None:
1890     logging.critical("Invalid value passed for external script name: '%s'",
1891                      name)
1892     return None
1893
1894   for dir_name in search_path:
1895     # FIXME: investigate switch to PathJoin
1896     item_name = os.path.sep.join([dir_name, name])
1897     # check the user test and that we're indeed resolving to the given
1898     # basename
1899     if test(item_name) and os.path.basename(item_name) == name:
1900       return item_name
1901   return None
1902
1903
1904 def CheckVolumeGroupSize(vglist, vgname, minsize):
1905   """Checks if the volume group list is valid.
1906
1907   The function will check if a given volume group is in the list of
1908   volume groups and has a minimum size.
1909
1910   @type vglist: dict
1911   @param vglist: dictionary of volume group names and their size
1912   @type vgname: str
1913   @param vgname: the volume group we should check
1914   @type minsize: int
1915   @param minsize: the minimum size we accept
1916   @rtype: None or str
1917   @return: None for success, otherwise the error message
1918
1919   """
1920   vgsize = vglist.get(vgname, None)
1921   if vgsize is None:
1922     return "volume group '%s' missing" % vgname
1923   elif vgsize < minsize:
1924     return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1925             (vgname, minsize, vgsize))
1926   return None
1927
1928
1929 def SplitTime(value):
1930   """Splits time as floating point number into a tuple.
1931
1932   @param value: Time in seconds
1933   @type value: int or float
1934   @return: Tuple containing (seconds, microseconds)
1935
1936   """
1937   (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1938
1939   assert 0 <= seconds, \
1940     "Seconds must be larger than or equal to 0, but are %s" % seconds
1941   assert 0 <= microseconds <= 999999, \
1942     "Microseconds must be 0-999999, but are %s" % microseconds
1943
1944   return (int(seconds), int(microseconds))
1945
1946
1947 def MergeTime(timetuple):
1948   """Merges a tuple into time as a floating point number.
1949
1950   @param timetuple: Time as tuple, (seconds, microseconds)
1951   @type timetuple: tuple
1952   @return: Time as a floating point number expressed in seconds
1953
1954   """
1955   (seconds, microseconds) = timetuple
1956
1957   assert 0 <= seconds, \
1958     "Seconds must be larger than or equal to 0, but are %s" % seconds
1959   assert 0 <= microseconds <= 999999, \
1960     "Microseconds must be 0-999999, but are %s" % microseconds
1961
1962   return float(seconds) + (float(microseconds) * 0.000001)
1963
1964
1965 def GetDaemonPort(daemon_name):
1966   """Get the daemon port for this cluster.
1967
1968   Note that this routine does not read a ganeti-specific file, but
1969   instead uses C{socket.getservbyname} to allow pre-customization of
1970   this parameter outside of Ganeti.
1971
1972   @type daemon_name: string
1973   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1974   @rtype: int
1975
1976   """
1977   if daemon_name not in constants.DAEMONS_PORTS:
1978     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1979
1980   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1981   try:
1982     port = socket.getservbyname(daemon_name, proto)
1983   except socket.error:
1984     port = default_port
1985
1986   return port
1987
1988
1989 def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
1990                  multithreaded=False, syslog=constants.SYSLOG_USAGE):
1991   """Configures the logging module.
1992
1993   @type logfile: str
1994   @param logfile: the filename to which we should log
1995   @type debug: integer
1996   @param debug: if greater than zero, enable debug messages, otherwise
1997       only those at C{INFO} and above level
1998   @type stderr_logging: boolean
1999   @param stderr_logging: whether we should also log to the standard error
2000   @type program: str
2001   @param program: the name under which we should log messages
2002   @type multithreaded: boolean
2003   @param multithreaded: if True, will add the thread name to the log file
2004   @type syslog: string
2005   @param syslog: one of 'no', 'yes', 'only':
2006       - if no, syslog is not used
2007       - if yes, syslog is used (in addition to file-logging)
2008       - if only, only syslog is used
2009   @raise EnvironmentError: if we can't open the log file and
2010       syslog/stderr logging is disabled
2011
2012   """
2013   fmt = "%(asctime)s: " + program + " pid=%(process)d"
2014   sft = program + "[%(process)d]:"
2015   if multithreaded:
2016     fmt += "/%(threadName)s"
2017     sft += " (%(threadName)s)"
2018   if debug:
2019     fmt += " %(module)s:%(lineno)s"
2020     # no debug info for syslog loggers
2021   fmt += " %(levelname)s %(message)s"
2022   # yes, we do want the textual level, as remote syslog will probably
2023   # lose the error level, and it's easier to grep for it
2024   sft += " %(levelname)s %(message)s"
2025   formatter = logging.Formatter(fmt)
2026   sys_fmt = logging.Formatter(sft)
2027
2028   root_logger = logging.getLogger("")
2029   root_logger.setLevel(logging.NOTSET)
2030
2031   # Remove all previously setup handlers
2032   for handler in root_logger.handlers:
2033     handler.close()
2034     root_logger.removeHandler(handler)
2035
2036   if stderr_logging:
2037     stderr_handler = logging.StreamHandler()
2038     stderr_handler.setFormatter(formatter)
2039     if debug:
2040       stderr_handler.setLevel(logging.NOTSET)
2041     else:
2042       stderr_handler.setLevel(logging.CRITICAL)
2043     root_logger.addHandler(stderr_handler)
2044
2045   if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2046     facility = logging.handlers.SysLogHandler.LOG_DAEMON
2047     syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2048                                                     facility)
2049     syslog_handler.setFormatter(sys_fmt)
2050     # Never enable debug over syslog
2051     syslog_handler.setLevel(logging.INFO)
2052     root_logger.addHandler(syslog_handler)
2053
2054   if syslog != constants.SYSLOG_ONLY:
2055     # this can fail, if the logging directories are not setup or we have
2056     # a permisssion problem; in this case, it's best to log but ignore
2057     # the error if stderr_logging is True, and if false we re-raise the
2058     # exception since otherwise we could run but without any logs at all
2059     try:
2060       logfile_handler = logging.FileHandler(logfile)
2061       logfile_handler.setFormatter(formatter)
2062       if debug:
2063         logfile_handler.setLevel(logging.DEBUG)
2064       else:
2065         logfile_handler.setLevel(logging.INFO)
2066       root_logger.addHandler(logfile_handler)
2067     except EnvironmentError:
2068       if stderr_logging or syslog == constants.SYSLOG_YES:
2069         logging.exception("Failed to enable logging to file '%s'", logfile)
2070       else:
2071         # we need to re-raise the exception
2072         raise
2073
2074
2075 def IsNormAbsPath(path):
2076   """Check whether a path is absolute and also normalized
2077
2078   This avoids things like /dir/../../other/path to be valid.
2079
2080   """
2081   return os.path.normpath(path) == path and os.path.isabs(path)
2082
2083
2084 def PathJoin(*args):
2085   """Safe-join a list of path components.
2086
2087   Requirements:
2088       - the first argument must be an absolute path
2089       - no component in the path must have backtracking (e.g. /../),
2090         since we check for normalization at the end
2091
2092   @param args: the path components to be joined
2093   @raise ValueError: for invalid paths
2094
2095   """
2096   # ensure we're having at least one path passed in
2097   assert args
2098   # ensure the first component is an absolute and normalized path name
2099   root = args[0]
2100   if not IsNormAbsPath(root):
2101     raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2102   result = os.path.join(*args)
2103   # ensure that the whole path is normalized
2104   if not IsNormAbsPath(result):
2105     raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2106   # check that we're still under the original prefix
2107   prefix = os.path.commonprefix([root, result])
2108   if prefix != root:
2109     raise ValueError("Error: path joining resulted in different prefix"
2110                      " (%s != %s)" % (prefix, root))
2111   return result
2112
2113
2114 def TailFile(fname, lines=20):
2115   """Return the last lines from a file.
2116
2117   @note: this function will only read and parse the last 4KB of
2118       the file; if the lines are very long, it could be that less
2119       than the requested number of lines are returned
2120
2121   @param fname: the file name
2122   @type lines: int
2123   @param lines: the (maximum) number of lines to return
2124
2125   """
2126   fd = open(fname, "r")
2127   try:
2128     fd.seek(0, 2)
2129     pos = fd.tell()
2130     pos = max(0, pos-4096)
2131     fd.seek(pos, 0)
2132     raw_data = fd.read()
2133   finally:
2134     fd.close()
2135
2136   rows = raw_data.splitlines()
2137   return rows[-lines:]
2138
2139
2140 def _ParseAsn1Generalizedtime(value):
2141   """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2142
2143   @type value: string
2144   @param value: ASN1 GENERALIZEDTIME timestamp
2145
2146   """
2147   m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2148   if m:
2149     # We have an offset
2150     asn1time = m.group(1)
2151     hours = int(m.group(2))
2152     minutes = int(m.group(3))
2153     utcoffset = (60 * hours) + minutes
2154   else:
2155     if not value.endswith("Z"):
2156       raise ValueError("Missing timezone")
2157     asn1time = value[:-1]
2158     utcoffset = 0
2159
2160   parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2161
2162   tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2163
2164   return calendar.timegm(tt.utctimetuple())
2165
2166
2167 def GetX509CertValidity(cert):
2168   """Returns the validity period of the certificate.
2169
2170   @type cert: OpenSSL.crypto.X509
2171   @param cert: X509 certificate object
2172
2173   """
2174   # The get_notBefore and get_notAfter functions are only supported in
2175   # pyOpenSSL 0.7 and above.
2176   try:
2177     get_notbefore_fn = cert.get_notBefore
2178   except AttributeError:
2179     not_before = None
2180   else:
2181     not_before_asn1 = get_notbefore_fn()
2182
2183     if not_before_asn1 is None:
2184       not_before = None
2185     else:
2186       not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2187
2188   try:
2189     get_notafter_fn = cert.get_notAfter
2190   except AttributeError:
2191     not_after = None
2192   else:
2193     not_after_asn1 = get_notafter_fn()
2194
2195     if not_after_asn1 is None:
2196       not_after = None
2197     else:
2198       not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2199
2200   return (not_before, not_after)
2201
2202
2203 def SafeEncode(text):
2204   """Return a 'safe' version of a source string.
2205
2206   This function mangles the input string and returns a version that
2207   should be safe to display/encode as ASCII. To this end, we first
2208   convert it to ASCII using the 'backslashreplace' encoding which
2209   should get rid of any non-ASCII chars, and then we process it
2210   through a loop copied from the string repr sources in the python; we
2211   don't use string_escape anymore since that escape single quotes and
2212   backslashes too, and that is too much; and that escaping is not
2213   stable, i.e. string_escape(string_escape(x)) != string_escape(x).
2214
2215   @type text: str or unicode
2216   @param text: input data
2217   @rtype: str
2218   @return: a safe version of text
2219
2220   """
2221   if isinstance(text, unicode):
2222     # only if unicode; if str already, we handle it below
2223     text = text.encode('ascii', 'backslashreplace')
2224   resu = ""
2225   for char in text:
2226     c = ord(char)
2227     if char  == '\t':
2228       resu += r'\t'
2229     elif char == '\n':
2230       resu += r'\n'
2231     elif char == '\r':
2232       resu += r'\'r'
2233     elif c < 32 or c >= 127: # non-printable
2234       resu += "\\x%02x" % (c & 0xff)
2235     else:
2236       resu += char
2237   return resu
2238
2239
2240 def UnescapeAndSplit(text, sep=","):
2241   """Split and unescape a string based on a given separator.
2242
2243   This function splits a string based on a separator where the
2244   separator itself can be escape in order to be an element of the
2245   elements. The escaping rules are (assuming coma being the
2246   separator):
2247     - a plain , separates the elements
2248     - a sequence \\\\, (double backslash plus comma) is handled as a
2249       backslash plus a separator comma
2250     - a sequence \, (backslash plus comma) is handled as a
2251       non-separator comma
2252
2253   @type text: string
2254   @param text: the string to split
2255   @type sep: string
2256   @param text: the separator
2257   @rtype: string
2258   @return: a list of strings
2259
2260   """
2261   # we split the list by sep (with no escaping at this stage)
2262   slist = text.split(sep)
2263   # next, we revisit the elements and if any of them ended with an odd
2264   # number of backslashes, then we join it with the next
2265   rlist = []
2266   while slist:
2267     e1 = slist.pop(0)
2268     if e1.endswith("\\"):
2269       num_b = len(e1) - len(e1.rstrip("\\"))
2270       if num_b % 2 == 1:
2271         e2 = slist.pop(0)
2272         # here the backslashes remain (all), and will be reduced in
2273         # the next step
2274         rlist.append(e1 + sep + e2)
2275         continue
2276     rlist.append(e1)
2277   # finally, replace backslash-something with something
2278   rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2279   return rlist
2280
2281
2282 def CommaJoin(names):
2283   """Nicely join a set of identifiers.
2284
2285   @param names: set, list or tuple
2286   @return: a string with the formatted results
2287
2288   """
2289   return ", ".join([str(val) for val in names])
2290
2291
2292 def BytesToMebibyte(value):
2293   """Converts bytes to mebibytes.
2294
2295   @type value: int
2296   @param value: Value in bytes
2297   @rtype: int
2298   @return: Value in mebibytes
2299
2300   """
2301   return int(round(value / (1024.0 * 1024.0), 0))
2302
2303
2304 def CalculateDirectorySize(path):
2305   """Calculates the size of a directory recursively.
2306
2307   @type path: string
2308   @param path: Path to directory
2309   @rtype: int
2310   @return: Size in mebibytes
2311
2312   """
2313   size = 0
2314
2315   for (curpath, _, files) in os.walk(path):
2316     for filename in files:
2317       st = os.lstat(PathJoin(curpath, filename))
2318       size += st.st_size
2319
2320   return BytesToMebibyte(size)
2321
2322
2323 def GetFilesystemStats(path):
2324   """Returns the total and free space on a filesystem.
2325
2326   @type path: string
2327   @param path: Path on filesystem to be examined
2328   @rtype: int
2329   @return: tuple of (Total space, Free space) in mebibytes
2330
2331   """
2332   st = os.statvfs(path)
2333
2334   fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2335   tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2336   return (tsize, fsize)
2337
2338
2339 def RunInSeparateProcess(fn, *args):
2340   """Runs a function in a separate process.
2341
2342   Note: Only boolean return values are supported.
2343
2344   @type fn: callable
2345   @param fn: Function to be called
2346   @rtype: bool
2347   @return: Function's result
2348
2349   """
2350   pid = os.fork()
2351   if pid == 0:
2352     # Child process
2353     try:
2354       # In case the function uses temporary files
2355       ResetTempfileModule()
2356
2357       # Call function
2358       result = int(bool(fn(*args)))
2359       assert result in (0, 1)
2360     except: # pylint: disable-msg=W0702
2361       logging.exception("Error while calling function in separate process")
2362       # 0 and 1 are reserved for the return value
2363       result = 33
2364
2365     os._exit(result) # pylint: disable-msg=W0212
2366
2367   # Parent process
2368
2369   # Avoid zombies and check exit code
2370   (_, status) = os.waitpid(pid, 0)
2371
2372   if os.WIFSIGNALED(status):
2373     exitcode = None
2374     signum = os.WTERMSIG(status)
2375   else:
2376     exitcode = os.WEXITSTATUS(status)
2377     signum = None
2378
2379   if not (exitcode in (0, 1) and signum is None):
2380     raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2381                               (exitcode, signum))
2382
2383   return bool(exitcode)
2384
2385
2386 def LockedMethod(fn):
2387   """Synchronized object access decorator.
2388
2389   This decorator is intended to protect access to an object using the
2390   object's own lock which is hardcoded to '_lock'.
2391
2392   """
2393   def _LockDebug(*args, **kwargs):
2394     if debug_locks:
2395       logging.debug(*args, **kwargs)
2396
2397   def wrapper(self, *args, **kwargs):
2398     # pylint: disable-msg=W0212
2399     assert hasattr(self, '_lock')
2400     lock = self._lock
2401     _LockDebug("Waiting for %s", lock)
2402     lock.acquire()
2403     try:
2404       _LockDebug("Acquired %s", lock)
2405       result = fn(self, *args, **kwargs)
2406     finally:
2407       _LockDebug("Releasing %s", lock)
2408       lock.release()
2409       _LockDebug("Released %s", lock)
2410     return result
2411   return wrapper
2412
2413
2414 def LockFile(fd):
2415   """Locks a file using POSIX locks.
2416
2417   @type fd: int
2418   @param fd: the file descriptor we need to lock
2419
2420   """
2421   try:
2422     fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2423   except IOError, err:
2424     if err.errno == errno.EAGAIN:
2425       raise errors.LockError("File already locked")
2426     raise
2427
2428
2429 def FormatTime(val):
2430   """Formats a time value.
2431
2432   @type val: float or None
2433   @param val: the timestamp as returned by time.time()
2434   @return: a string value or N/A if we don't have a valid timestamp
2435
2436   """
2437   if val is None or not isinstance(val, (int, float)):
2438     return "N/A"
2439   # these two codes works on Linux, but they are not guaranteed on all
2440   # platforms
2441   return time.strftime("%F %T", time.localtime(val))
2442
2443
2444 def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2445   """Reads the watcher pause file.
2446
2447   @type filename: string
2448   @param filename: Path to watcher pause file
2449   @type now: None, float or int
2450   @param now: Current time as Unix timestamp
2451   @type remove_after: int
2452   @param remove_after: Remove watcher pause file after specified amount of
2453     seconds past the pause end time
2454
2455   """
2456   if now is None:
2457     now = time.time()
2458
2459   try:
2460     value = ReadFile(filename)
2461   except IOError, err:
2462     if err.errno != errno.ENOENT:
2463       raise
2464     value = None
2465
2466   if value is not None:
2467     try:
2468       value = int(value)
2469     except ValueError:
2470       logging.warning(("Watcher pause file (%s) contains invalid value,"
2471                        " removing it"), filename)
2472       RemoveFile(filename)
2473       value = None
2474
2475     if value is not None:
2476       # Remove file if it's outdated
2477       if now > (value + remove_after):
2478         RemoveFile(filename)
2479         value = None
2480
2481       elif now > value:
2482         value = None
2483
2484   return value
2485
2486
2487 class RetryTimeout(Exception):
2488   """Retry loop timed out.
2489
2490   """
2491
2492
2493 class RetryAgain(Exception):
2494   """Retry again.
2495
2496   """
2497
2498
2499 class _RetryDelayCalculator(object):
2500   """Calculator for increasing delays.
2501
2502   """
2503   __slots__ = [
2504     "_factor",
2505     "_limit",
2506     "_next",
2507     "_start",
2508     ]
2509
2510   def __init__(self, start, factor, limit):
2511     """Initializes this class.
2512
2513     @type start: float
2514     @param start: Initial delay
2515     @type factor: float
2516     @param factor: Factor for delay increase
2517     @type limit: float or None
2518     @param limit: Upper limit for delay or None for no limit
2519
2520     """
2521     assert start > 0.0
2522     assert factor >= 1.0
2523     assert limit is None or limit >= 0.0
2524
2525     self._start = start
2526     self._factor = factor
2527     self._limit = limit
2528
2529     self._next = start
2530
2531   def __call__(self):
2532     """Returns current delay and calculates the next one.
2533
2534     """
2535     current = self._next
2536
2537     # Update for next run
2538     if self._limit is None or self._next < self._limit:
2539       self._next = min(self._limit, self._next * self._factor)
2540
2541     return current
2542
2543
2544 #: Special delay to specify whole remaining timeout
2545 RETRY_REMAINING_TIME = object()
2546
2547
2548 def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2549           _time_fn=time.time):
2550   """Call a function repeatedly until it succeeds.
2551
2552   The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
2553   anymore. Between calls a delay, specified by C{delay}, is inserted. After a
2554   total of C{timeout} seconds, this function throws L{RetryTimeout}.
2555
2556   C{delay} can be one of the following:
2557     - callable returning the delay length as a float
2558     - Tuple of (start, factor, limit)
2559     - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2560       useful when overriding L{wait_fn} to wait for an external event)
2561     - A static delay as a number (int or float)
2562
2563   @type fn: callable
2564   @param fn: Function to be called
2565   @param delay: Either a callable (returning the delay), a tuple of (start,
2566                 factor, limit) (see L{_RetryDelayCalculator}),
2567                 L{RETRY_REMAINING_TIME} or a number (int or float)
2568   @type timeout: float
2569   @param timeout: Total timeout
2570   @type wait_fn: callable
2571   @param wait_fn: Waiting function
2572   @return: Return value of function
2573
2574   """
2575   assert callable(fn)
2576   assert callable(wait_fn)
2577   assert callable(_time_fn)
2578
2579   if args is None:
2580     args = []
2581
2582   end_time = _time_fn() + timeout
2583
2584   if callable(delay):
2585     # External function to calculate delay
2586     calc_delay = delay
2587
2588   elif isinstance(delay, (tuple, list)):
2589     # Increasing delay with optional upper boundary
2590     (start, factor, limit) = delay
2591     calc_delay = _RetryDelayCalculator(start, factor, limit)
2592
2593   elif delay is RETRY_REMAINING_TIME:
2594     # Always use the remaining time
2595     calc_delay = None
2596
2597   else:
2598     # Static delay
2599     calc_delay = lambda: delay
2600
2601   assert calc_delay is None or callable(calc_delay)
2602
2603   while True:
2604     try:
2605       # pylint: disable-msg=W0142
2606       return fn(*args)
2607     except RetryAgain:
2608       pass
2609
2610     remaining_time = end_time - _time_fn()
2611
2612     if remaining_time < 0.0:
2613       raise RetryTimeout()
2614
2615     assert remaining_time >= 0.0
2616
2617     if calc_delay is None:
2618       wait_fn(remaining_time)
2619     else:
2620       current_delay = calc_delay()
2621       if current_delay > 0.0:
2622         wait_fn(current_delay)
2623
2624
2625 class FileLock(object):
2626   """Utility class for file locks.
2627
2628   """
2629   def __init__(self, fd, filename):
2630     """Constructor for FileLock.
2631
2632     @type fd: file
2633     @param fd: File object
2634     @type filename: str
2635     @param filename: Path of the file opened at I{fd}
2636
2637     """
2638     self.fd = fd
2639     self.filename = filename
2640
2641   @classmethod
2642   def Open(cls, filename):
2643     """Creates and opens a file to be used as a file-based lock.
2644
2645     @type filename: string
2646     @param filename: path to the file to be locked
2647
2648     """
2649     # Using "os.open" is necessary to allow both opening existing file
2650     # read/write and creating if not existing. Vanilla "open" will truncate an
2651     # existing file -or- allow creating if not existing.
2652     return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2653                filename)
2654
2655   def __del__(self):
2656     self.Close()
2657
2658   def Close(self):
2659     """Close the file and release the lock.
2660
2661     """
2662     if hasattr(self, "fd") and self.fd:
2663       self.fd.close()
2664       self.fd = None
2665
2666   def _flock(self, flag, blocking, timeout, errmsg):
2667     """Wrapper for fcntl.flock.
2668
2669     @type flag: int
2670     @param flag: operation flag
2671     @type blocking: bool
2672     @param blocking: whether the operation should be done in blocking mode.
2673     @type timeout: None or float
2674     @param timeout: for how long the operation should be retried (implies
2675                     non-blocking mode).
2676     @type errmsg: string
2677     @param errmsg: error message in case operation fails.
2678
2679     """
2680     assert self.fd, "Lock was closed"
2681     assert timeout is None or timeout >= 0, \
2682       "If specified, timeout must be positive"
2683     assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2684
2685     # When a timeout is used, LOCK_NB must always be set
2686     if not (timeout is None and blocking):
2687       flag |= fcntl.LOCK_NB
2688
2689     if timeout is None:
2690       self._Lock(self.fd, flag, timeout)
2691     else:
2692       try:
2693         Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2694               args=(self.fd, flag, timeout))
2695       except RetryTimeout:
2696         raise errors.LockError(errmsg)
2697
2698   @staticmethod
2699   def _Lock(fd, flag, timeout):
2700     try:
2701       fcntl.flock(fd, flag)
2702     except IOError, err:
2703       if timeout is not None and err.errno == errno.EAGAIN:
2704         raise RetryAgain()
2705
2706       logging.exception("fcntl.flock failed")
2707       raise
2708
2709   def Exclusive(self, blocking=False, timeout=None):
2710     """Locks the file in exclusive mode.
2711
2712     @type blocking: boolean
2713     @param blocking: whether to block and wait until we
2714         can lock the file or return immediately
2715     @type timeout: int or None
2716     @param timeout: if not None, the duration to wait for the lock
2717         (in blocking mode)
2718
2719     """
2720     self._flock(fcntl.LOCK_EX, blocking, timeout,
2721                 "Failed to lock %s in exclusive mode" % self.filename)
2722
2723   def Shared(self, blocking=False, timeout=None):
2724     """Locks the file in shared mode.
2725
2726     @type blocking: boolean
2727     @param blocking: whether to block and wait until we
2728         can lock the file or return immediately
2729     @type timeout: int or None
2730     @param timeout: if not None, the duration to wait for the lock
2731         (in blocking mode)
2732
2733     """
2734     self._flock(fcntl.LOCK_SH, blocking, timeout,
2735                 "Failed to lock %s in shared mode" % self.filename)
2736
2737   def Unlock(self, blocking=True, timeout=None):
2738     """Unlocks the file.
2739
2740     According to C{flock(2)}, unlocking can also be a nonblocking
2741     operation::
2742
2743       To make a non-blocking request, include LOCK_NB with any of the above
2744       operations.
2745
2746     @type blocking: boolean
2747     @param blocking: whether to block and wait until we
2748         can lock the file or return immediately
2749     @type timeout: int or None
2750     @param timeout: if not None, the duration to wait for the lock
2751         (in blocking mode)
2752
2753     """
2754     self._flock(fcntl.LOCK_UN, blocking, timeout,
2755                 "Failed to unlock %s" % self.filename)
2756
2757
2758 def SignalHandled(signums):
2759   """Signal Handled decoration.
2760
2761   This special decorator installs a signal handler and then calls the target
2762   function. The function must accept a 'signal_handlers' keyword argument,
2763   which will contain a dict indexed by signal number, with SignalHandler
2764   objects as values.
2765
2766   The decorator can be safely stacked with iself, to handle multiple signals
2767   with different handlers.
2768
2769   @type signums: list
2770   @param signums: signals to intercept
2771
2772   """
2773   def wrap(fn):
2774     def sig_function(*args, **kwargs):
2775       assert 'signal_handlers' not in kwargs or \
2776              kwargs['signal_handlers'] is None or \
2777              isinstance(kwargs['signal_handlers'], dict), \
2778              "Wrong signal_handlers parameter in original function call"
2779       if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2780         signal_handlers = kwargs['signal_handlers']
2781       else:
2782         signal_handlers = {}
2783         kwargs['signal_handlers'] = signal_handlers
2784       sighandler = SignalHandler(signums)
2785       try:
2786         for sig in signums:
2787           signal_handlers[sig] = sighandler
2788         return fn(*args, **kwargs)
2789       finally:
2790         sighandler.Reset()
2791     return sig_function
2792   return wrap
2793
2794
2795 class SignalHandler(object):
2796   """Generic signal handler class.
2797
2798   It automatically restores the original handler when deconstructed or
2799   when L{Reset} is called. You can either pass your own handler
2800   function in or query the L{called} attribute to detect whether the
2801   signal was sent.
2802
2803   @type signum: list
2804   @ivar signum: the signals we handle
2805   @type called: boolean
2806   @ivar called: tracks whether any of the signals have been raised
2807
2808   """
2809   def __init__(self, signum):
2810     """Constructs a new SignalHandler instance.
2811
2812     @type signum: int or list of ints
2813     @param signum: Single signal number or set of signal numbers
2814
2815     """
2816     self.signum = set(signum)
2817     self.called = False
2818
2819     self._previous = {}
2820     try:
2821       for signum in self.signum:
2822         # Setup handler
2823         prev_handler = signal.signal(signum, self._HandleSignal)
2824         try:
2825           self._previous[signum] = prev_handler
2826         except:
2827           # Restore previous handler
2828           signal.signal(signum, prev_handler)
2829           raise
2830     except:
2831       # Reset all handlers
2832       self.Reset()
2833       # Here we have a race condition: a handler may have already been called,
2834       # but there's not much we can do about it at this point.
2835       raise
2836
2837   def __del__(self):
2838     self.Reset()
2839
2840   def Reset(self):
2841     """Restore previous handler.
2842
2843     This will reset all the signals to their previous handlers.
2844
2845     """
2846     for signum, prev_handler in self._previous.items():
2847       signal.signal(signum, prev_handler)
2848       # If successful, remove from dict
2849       del self._previous[signum]
2850
2851   def Clear(self):
2852     """Unsets the L{called} flag.
2853
2854     This function can be used in case a signal may arrive several times.
2855
2856     """
2857     self.called = False
2858
2859   # we don't care about arguments, but we leave them named for the future
2860   def _HandleSignal(self, signum, frame): # pylint: disable-msg=W0613
2861     """Actual signal handling function.
2862
2863     """
2864     # This is not nice and not absolutely atomic, but it appears to be the only
2865     # solution in Python -- there are no atomic types.
2866     self.called = True
2867
2868
2869 class FieldSet(object):
2870   """A simple field set.
2871
2872   Among the features are:
2873     - checking if a string is among a list of static string or regex objects
2874     - checking if a whole list of string matches
2875     - returning the matching groups from a regex match
2876
2877   Internally, all fields are held as regular expression objects.
2878
2879   """
2880   def __init__(self, *items):
2881     self.items = [re.compile("^%s$" % value) for value in items]
2882
2883   def Extend(self, other_set):
2884     """Extend the field set with the items from another one"""
2885     self.items.extend(other_set.items)
2886
2887   def Matches(self, field):
2888     """Checks if a field matches the current set
2889
2890     @type field: str
2891     @param field: the string to match
2892     @return: either None or a regular expression match object
2893
2894     """
2895     for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2896       return m
2897     return None
2898
2899   def NonMatching(self, items):
2900     """Returns the list of fields not matching the current set
2901
2902     @type items: list
2903     @param items: the list of fields to check
2904     @rtype: list
2905     @return: list of non-matching fields
2906
2907     """
2908     return [val for val in items if not self.Matches(val)]