import itertools
import select
import fcntl
+import resource
from cStringIO import StringIO
_re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
debug = False
+no_fork = False
+
class RunResult(object):
"""Simple class for holding the result of running external programs.
Returns: `RunResult` instance
"""
+ if no_fork:
+ raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
+
if isinstance(cmd, list):
cmd = [str(val) for val in cmd]
strcmd = " ".join(cmd)
return [tup[1] for tup in to_sort]
-def CheckDaemonAlive(pid_file, process_string):
- """Check wether the specified daemon is alive.
-
- Args:
- - pid_file: file to read the daemon pid from, the file is
- expected to contain only a single line containing
- only the PID
- - process_string: a substring that we expect to find in
- the command line of the daemon process
-
- Returns:
- - True if the daemon is judged to be alive (that is:
- - the PID file exists, is readable and contains a number
- - a process of the specified PID is running
- - that process contains the specified string in its
- command line
- - the process is not in state Z (zombie))
- - False otherwise
-
- """
- try:
- pid_file = file(pid_file, 'r')
- try:
- pid = int(pid_file.readline())
- finally:
- pid_file.close()
-
- cmdline_file_path = "/proc/%s/cmdline" % (pid)
- cmdline_file = open(cmdline_file_path, 'r')
- try:
- cmdline = cmdline_file.readline()
- finally:
- cmdline_file.close()
-
- if not process_string in cmdline:
- return False
-
- stat_file_path = "/proc/%s/stat" % (pid)
- stat_file = open(stat_file_path, 'r')
- try:
- process_state = stat_file.readline().split()[2]
- finally:
- stat_file.close()
-
- if process_state == 'Z':
- return False
-
- except (IndexError, IOError, ValueError):
- return False
-
- return True
-
-
def TryConvert(fn, val):
"""Try to convert a value ignoring errors.
raise
+def AddHostToEtcHosts(hostname):
+ """Wrapper around SetEtcHostsEntry.
+
+ """
+ hi = HostInfo(name=hostname)
+ SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
+
+
def RemoveEtcHostsEntry(file_name, hostname):
"""Removes a hostname from /etc/hosts.
raise
+def RemoveHostFromEtcHosts(hostname):
+ """Wrapper around RemoveEtcHostsEntry.
+
+ """
+ hi = HostInfo(name=hostname)
+ RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
+ RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
+
+
def CreateBackup(file_name):
"""Creates a backup of a file.
return ' '.join([ShellQuote(i) for i in args])
-
-def TcpPing(source, target, port, timeout=10, live_port_needed=False):
+def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
"""Simple ping implementation using TCP connect(2).
- Try to do a TCP connect(2) from the specified source IP to the specified
- target IP and the specified target port. If live_port_needed is set to true,
- requires the remote end to accept the connection. The timeout is specified
- in seconds and defaults to 10 seconds
+ Try to do a TCP connect(2) from an optional source IP to the
+ specified target IP and the specified target port. If the optional
+ parameter live_port_needed is set to true, requires the remote end
+ to accept the connection. The timeout is specified in seconds and
+ defaults to 10 seconds. If the source optional argument is not
+ passed, the source address selection is left to the kernel,
+ otherwise we try to connect using the passed address (failures to
+ bind other than EADDRNOTAVAIL will be ignored).
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sucess = False
- try:
- sock.bind((source, 0))
- except socket.error, (errcode, errstring):
- if errcode == errno.EADDRNOTAVAIL:
- success = False
+ if source is not None:
+ try:
+ sock.bind((source, 0))
+ except socket.error, (errcode, errstring):
+ if errcode == errno.EADDRNOTAVAIL:
+ success = False
sock.settimeout(timeout)
def WriteFile(file_name, fn=None, data=None,
mode=None, uid=-1, gid=-1,
- atime=None, mtime=None):
+ atime=None, mtime=None,
+ check_abspath=True, dry_run=False, backup=False):
"""(Over)write a file atomically.
The file_name and either fn (a function taking one argument, the
temporary file should be removed.
"""
- if not os.path.isabs(file_name):
+ if check_abspath and not os.path.isabs(file_name):
raise errors.ProgrammerError("Path passed to WriteFile is not"
" absolute: '%s'" % file_name)
raise errors.ProgrammerError("Both atime and mtime must be either"
" set or None")
+ if backup and not dry_run and os.path.isfile(file_name):
+ CreateBackup(file_name)
dir_name, base_name = os.path.split(file_name)
fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
os.fsync(fd)
if atime is not None and mtime is not None:
os.utime(new_name, (atime, mtime))
- os.rename(new_name, file_name)
+ if not dry_run:
+ os.rename(new_name, file_name)
finally:
os.close(fd)
RemoveFile(new_name)
"""
mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
return mac_check.match(mac) is not None
+
+
+def TestDelay(duration):
+ """Sleep for a fixed amount of time.
+
+ """
+ if duration < 0:
+ return False
+ time.sleep(duration)
+ return True
+
+
+def Daemonize(logfile, noclose_fds=None):
+ """Daemonize the current process.
+
+ This detaches the current process from the controlling terminal and
+ runs it in the background as a daemon.
+
+ """
+ UMASK = 077
+ WORKDIR = "/"
+ # Default maximum for the number of available file descriptors.
+ if 'SC_OPEN_MAX' in os.sysconf_names:
+ try:
+ MAXFD = os.sysconf('SC_OPEN_MAX')
+ if MAXFD < 0:
+ MAXFD = 1024
+ except OSError:
+ MAXFD = 1024
+ else:
+ MAXFD = 1024
+
+ # this might fail
+ pid = os.fork()
+ if (pid == 0): # The first child.
+ os.setsid()
+ # this might fail
+ pid = os.fork() # Fork a second child.
+ if (pid == 0): # The second child.
+ os.chdir(WORKDIR)
+ os.umask(UMASK)
+ else:
+ # exit() or _exit()? See below.
+ os._exit(0) # Exit parent (the first child) of the second child.
+ else:
+ os._exit(0) # Exit parent of the first child.
+ maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+ if (maxfd == resource.RLIM_INFINITY):
+ maxfd = MAXFD
+
+ # Iterate through and close all file descriptors.
+ for fd in range(0, maxfd):
+ if noclose_fds and fd in noclose_fds:
+ continue
+ try:
+ os.close(fd)
+ except OSError: # ERROR, fd wasn't open to begin with (ignored)
+ pass
+ os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
+ # Duplicate standard input to standard output and standard error.
+ os.dup2(0, 1) # standard output (1)
+ os.dup2(0, 2) # standard error (2)
+ return 0
+
+
+def FindFile(name, search_path, test=os.path.exists):
+ """Look for a filesystem object in a given path.
+
+ This is an abstract method to search for filesystem object (files,
+ dirs) under a given search path.
+
+ Args:
+ - name: the name to look for
+ - search_path: list of directory names
+ - test: the test which the full path must satisfy
+ (defaults to os.path.exists)
+
+ Returns:
+ - full path to the item if found
+ - None otherwise
+
+ """
+ for dir_name in search_path:
+ item_name = os.path.sep.join([dir_name, name])
+ if test(item_name):
+ return item_name
+ return None
+
+
+def CheckVolumeGroupSize(vglist, vgname, minsize):
+ """Checks if the volume group list is valid.
+
+ A non-None return value means there's an error, and the return value
+ is the error message.
+
+ """
+ vgsize = vglist.get(vgname, None)
+ if vgsize is None:
+ return "volume group '%s' missing" % vgname
+ elif vgsize < minsize:
+ return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
+ (vgname, minsize, vgsize))
+ return None