summaryrefslogtreecommitdiff
path: root/cloudinit/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/util.py')
-rw-r--r--cloudinit/util.py165
1 files changed, 115 insertions, 50 deletions
diff --git a/cloudinit/util.py b/cloudinit/util.py
index a37172dc..08cdd8c8 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -26,6 +26,7 @@ from StringIO import StringIO
import contextlib
import copy as obj_copy
+import ctypes
import errno
import glob
import grp
@@ -36,6 +37,7 @@ import os.path
import platform
import pwd
import random
+import re
import shutil
import socket
import stat
@@ -72,31 +74,6 @@ FN_ALLOWED = ('_-.()' + string.digits + string.ascii_letters)
CONTAINER_TESTS = ['running-in-container', 'lxc-is-container']
-# Made to have same accessors as UrlResponse so that the
-# read_file_or_url can return this or that object and the
-# 'user' of those objects will not need to know the difference.
-class StringResponse(object):
- def __init__(self, contents, code=200):
- self.code = code
- self.headers = {}
- self.contents = contents
- self.url = None
-
- def ok(self, *args, **kwargs): # pylint: disable=W0613
- if self.code != 200:
- return False
- return True
-
- def __str__(self):
- return self.contents
-
-
-class FileResponse(StringResponse):
- def __init__(self, path, contents, code=200):
- StringResponse.__init__(self, contents, code=code)
- self.url = path
-
-
class ProcessExecutionError(IOError):
MESSAGE_TMPL = ('%(description)s\n'
@@ -392,11 +369,11 @@ def is_ipv4(instr):
return False
try:
- toks = [x for x in toks if (int(x) < 256 and int(x) > 0)]
+ toks = [x for x in toks if int(x) < 256 and int(x) >= 0]
except:
return False
- return (len(toks) == 4)
+ return len(toks) == 4
def get_cfg_option_bool(yobj, key, default=False):
@@ -608,18 +585,28 @@ def del_dir(path):
shutil.rmtree(path)
-def runparts(dirp, skip_no_exist=True):
+def runparts(dirp, skip_no_exist=True, exe_prefix=None):
if skip_no_exist and not os.path.isdir(dirp):
return
failed = []
attempted = []
+
+ if exe_prefix is None:
+ prefix = []
+ elif isinstance(exe_prefix, str):
+ prefix = [str(exe_prefix)]
+ elif isinstance(exe_prefix, list):
+ prefix = exe_prefix
+ else:
+ raise TypeError("exe_prefix must be None, str, or list")
+
for exe_name in sorted(os.listdir(dirp)):
exe_path = os.path.join(dirp, exe_name)
if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
attempted.append(exe_path)
try:
- subp([exe_path], capture=False)
+ subp(prefix + [exe_path], capture=False)
except ProcessExecutionError as e:
logexc(LOG, "Failed running %s [%s]", exe_path, e.exit_code)
failed.append(e)
@@ -639,8 +626,8 @@ def read_optional_seed(fill, base="", ext="", timeout=5):
fill['user-data'] = ud
fill['meta-data'] = md
return True
- except IOError as e:
- if e.errno == errno.ENOENT:
+ except url_helper.UrlError as e:
+ if e.code == url_helper.NOT_FOUND:
return False
raise
@@ -679,7 +666,7 @@ def fetch_ssl_details(paths=None):
def read_file_or_url(url, timeout=5, retries=10,
headers=None, data=None, sec_between=1, ssl_details=None,
- headers_cb=None):
+ headers_cb=None, exception_cb=None):
url = url.lstrip()
if url.startswith("/"):
url = "file://%s" % url
@@ -687,7 +674,14 @@ def read_file_or_url(url, timeout=5, retries=10,
if data:
LOG.warn("Unable to post data to file resource %s", url)
file_path = url[len("file://"):]
- return FileResponse(file_path, contents=load_file(file_path))
+ try:
+ contents = load_file(file_path)
+ except IOError as e:
+ code = e.errno
+ if e.errno == errno.ENOENT:
+ code = url_helper.NOT_FOUND
+ raise url_helper.UrlError(cause=e, code=code, headers=None)
+ return url_helper.FileResponse(file_path, contents=contents)
else:
return url_helper.readurl(url,
timeout=timeout,
@@ -696,7 +690,8 @@ def read_file_or_url(url, timeout=5, retries=10,
headers_cb=headers_cb,
data=data,
sec_between=sec_between,
- ssl_details=ssl_details)
+ ssl_details=ssl_details,
+ exception_cb=exception_cb)
def load_yaml(blob, default=None, allowed=(dict,)):
@@ -865,8 +860,8 @@ def get_fqdn_from_hosts(hostname, filename="/etc/hosts"):
IP_address canonical_hostname [aliases...]
Fields of the entry are separated by any number of blanks and/or tab
- characters. Text from a "#" character until the end of the line is a
- comment, and is ignored. Host names may contain only alphanumeric
+ characters. Text from a "#" character until the end of the line is a
+ comment, and is ignored. Host names may contain only alphanumeric
characters, minus signs ("-"), and periods ("."). They must begin with
an alphabetic character and end with an alphanumeric character.
Optional aliases provide for name changes, alternate spellings, shorter
@@ -950,7 +945,7 @@ def is_resolvable(name):
pass
_DNS_REDIRECT_IP = badips
if badresults:
- LOG.debug("detected dns redirection: %s" % badresults)
+ LOG.debug("detected dns redirection: %s", badresults)
try:
result = socket.getaddrinfo(name, None)
@@ -977,7 +972,7 @@ def gethostbyaddr(ip):
def is_resolvable_url(url):
"""determine if this url is resolvable (existing or ip)."""
- return (is_resolvable(urlparse.urlparse(url).hostname))
+ return is_resolvable(urlparse.urlparse(url).hostname)
def search_for_mirror(candidates):
@@ -1302,11 +1297,26 @@ def mounts():
mounted = {}
try:
# Go through mounts to see what is already mounted
- mount_locs = load_file("/proc/mounts").splitlines()
+ if os.path.exists("/proc/mounts"):
+ mount_locs = load_file("/proc/mounts").splitlines()
+ method = 'proc'
+ else:
+ (mountoutput, _err) = subp("mount")
+ mount_locs = mountoutput.splitlines()
+ method = 'mount'
+ mountre = r'^(/dev/[\S]+) on (/.*) \((.+), .+, (.+)\)$'
for mpline in mount_locs:
- # Format at: man fstab
+ # Linux: /dev/sda1 on /boot type ext4 (rw,relatime,data=ordered)
+ # FreeBSD: /dev/vtbd0p2 on / (ufs, local, journaled soft-updates)
try:
- (dev, mp, fstype, opts, _freq, _passno) = mpline.split()
+ if method == 'proc':
+ (dev, mp, fstype, opts, _freq, _passno) = mpline.split()
+ else:
+ m = re.search(mountre, mpline)
+ dev = m.group(1)
+ mp = m.group(2)
+ fstype = m.group(3)
+ opts = m.group(4)
except:
continue
# If the name of the mount point contains spaces these
@@ -1317,9 +1327,9 @@ def mounts():
'mountpoint': mp,
'opts': opts,
}
- LOG.debug("Fetched %s mounts from %s", mounted, "/proc/mounts")
+ LOG.debug("Fetched %s mounts from %s", mounted, method)
except (IOError, OSError):
- logexc(LOG, "Failed fetching mount points from /proc/mounts")
+ logexc(LOG, "Failed fetching mount points")
return mounted
@@ -1376,7 +1386,7 @@ def get_builtin_cfg():
def sym_link(source, link):
- LOG.debug("Creating symbolic link from %r => %r" % (link, source))
+ LOG.debug("Creating symbolic link from %r => %r", link, source)
os.symlink(source, link)
@@ -1404,12 +1414,27 @@ def time_rfc2822():
def uptime():
uptime_str = '??'
+ method = 'unknown'
try:
- contents = load_file("/proc/uptime").strip()
- if contents:
- uptime_str = contents.split()[0]
+ if os.path.exists("/proc/uptime"):
+ method = '/proc/uptime'
+ contents = load_file("/proc/uptime").strip()
+ if contents:
+ uptime_str = contents.split()[0]
+ else:
+ method = 'ctypes'
+ libc = ctypes.CDLL('/lib/libc.so.7')
+ size = ctypes.c_size_t()
+ buf = ctypes.c_int()
+ size.value = ctypes.sizeof(buf)
+ libc.sysctlbyname("kern.boottime", ctypes.byref(buf),
+ ctypes.byref(size), None, 0)
+ now = time.time()
+ bootup = buf.value
+ uptime_str = now - bootup
+
except:
- logexc(LOG, "Unable to read uptime from /proc/uptime")
+ logexc(LOG, "Unable to read uptime using method: %s" % method)
return uptime_str
@@ -1748,6 +1773,19 @@ def parse_mtab(path):
return None
+def parse_mount(path):
+ (mountoutput, _err) = subp("mount")
+ mount_locs = mountoutput.splitlines()
+ for line in mount_locs:
+ m = re.search(r'^(/dev/[\S]+) on (/.*) \((.+), .+, (.+)\)$', line)
+ devpth = m.group(1)
+ mount_point = m.group(2)
+ fs_type = m.group(3)
+ if mount_point == path:
+ return devpth, fs_type, mount_point
+ return None
+
+
def get_mount_info(path, log=LOG):
# Use /proc/$$/mountinfo to find the device where path is mounted.
# This is done because with a btrfs filesystem using os.stat(path)
@@ -1781,8 +1819,10 @@ def get_mount_info(path, log=LOG):
if os.path.exists(mountinfo_path):
lines = load_file(mountinfo_path).splitlines()
return parse_mount_info(path, lines, log)
- else:
+ elif os.path.exists("/etc/mtab"):
return parse_mtab(path)
+ else:
+ return parse_mount(path)
def which(program):
@@ -1795,7 +1835,7 @@ def which(program):
if is_exe(program):
return program
else:
- for path in os.environ["PATH"].split(os.pathsep):
+ for path in os.environ.get("PATH", "").split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
@@ -1849,3 +1889,28 @@ def expand_dotted_devname(dotted):
return toks
else:
return (dotted, None)
+
+
+def pathprefix2dict(base, required=None, optional=None, delim=os.path.sep):
+ # return a dictionary populated with keys in 'required' and 'optional'
+ # by reading files in prefix + delim + entry
+ if required is None:
+ required = []
+ if optional is None:
+ optional = []
+
+ missing = []
+ ret = {}
+ for f in required + optional:
+ try:
+ ret[f] = load_file(base + delim + f, quiet=False)
+ except IOError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ if f in required:
+ missing.append(f)
+
+ if len(missing):
+ raise ValueError("Missing required files: %s", ','.join(missing))
+
+ return ret