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