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