1# 2# Copyright 2006-2009, 2013, 2014 Red Hat, Inc. 3# 4# This work is licensed under the GNU GPLv2 or later. 5# See the COPYING file in the top-level directory. 6 7import os 8 9from . import urldetect 10from . import urlfetcher 11from .installerinject import perform_initrd_injections 12from .. import progress 13from ..devices import DeviceDisk 14from ..logger import log 15from ..osdict import OSDB 16 17 18# Enum of the various install media types we can have 19(MEDIA_DIR, 20 MEDIA_ISO, 21 MEDIA_URL, 22 MEDIA_KERNEL) = range(1, 5) 23 24 25def _is_url(url): 26 return (url.startswith("http://") or 27 url.startswith("https://") or 28 url.startswith("ftp://")) 29 30 31class _LocationData(object): 32 def __init__(self, os_variant, kernel_pairs, os_media, os_tree): 33 self.os_variant = os_variant 34 self.kernel_pairs = kernel_pairs 35 self.os_media = os_media 36 self.os_tree = os_tree 37 38 self.kernel_url_arg = None 39 if self.os_variant: 40 osobj = OSDB.lookup_os(self.os_variant) 41 self.kernel_url_arg = osobj.get_kernel_url_arg() 42 43 44class InstallerTreeMedia(object): 45 """ 46 Class representing --location Tree media. Can be one of 47 48 - A network URL: http://dl.fedoraproject.org/... 49 - A local directory 50 - A local .iso file, which will be accessed with isoinfo 51 """ 52 53 @staticmethod 54 def validate_path(conn, path): 55 try: 56 dev = DeviceDisk(conn) 57 dev.device = dev.DEVICE_CDROM 58 dev.set_source_path(path) 59 dev.validate() 60 return dev.get_source_path() 61 except Exception as e: 62 log.debug("Error validating install location", exc_info=True) 63 if path.startswith("nfs:"): 64 log.warning("NFS URL installs are no longer supported. " 65 "Access your install media over an alternate transport " 66 "like HTTP, or manually mount the NFS share and install " 67 "from the local directory mount point.") 68 69 msg = (_("Validating install media '%(media)s' failed: %(error)s") % 70 {"media": str(path), "error": str(e)}) 71 raise ValueError(msg) from None 72 73 @staticmethod 74 def get_system_scratchdir(guest): 75 """ 76 Return the tmpdir that's accessible by VMs on system libvirt URIs 77 """ 78 if guest.conn.is_xen(): 79 return "/var/lib/xen" 80 return "/var/lib/libvirt/boot" 81 82 @staticmethod 83 def make_scratchdir(guest): 84 """ 85 Determine the scratchdir for this URI, create it if necessary. 86 scratchdir is the directory that's accessible by VMs 87 """ 88 user_scratchdir = os.path.join( 89 guest.conn.get_app_cache_dir(), "boot") 90 system_scratchdir = InstallerTreeMedia.get_system_scratchdir(guest) 91 92 # If we are a session URI, or we don't have access to the system 93 # scratchdir, make sure the session scratchdir exists and use that. 94 if (guest.conn.is_unprivileged() or 95 not os.path.exists(system_scratchdir) or 96 not os.access(system_scratchdir, os.W_OK)): 97 os.makedirs(user_scratchdir, 0o751, exist_ok=True) 98 return user_scratchdir 99 100 return system_scratchdir # pragma: no cover 101 102 def __init__(self, conn, location, location_kernel, location_initrd, 103 install_kernel, install_initrd, install_kernel_args): 104 self.conn = conn 105 self.location = location 106 self._location_kernel = location_kernel 107 self._location_initrd = location_initrd 108 self._install_kernel = install_kernel 109 self._install_initrd = install_initrd 110 self._install_kernel_args = install_kernel_args 111 self._initrd_injections = [] 112 self._extra_args = [] 113 114 if location_kernel or location_initrd: 115 if not location: 116 raise ValueError(_("location kernel/initrd may only " 117 "be specified with a location URL/path")) 118 if not (location_kernel and location_initrd): 119 raise ValueError(_("location kernel/initrd must be " 120 "be specified as a pair")) 121 122 self._cached_fetcher = None 123 self._cached_data = None 124 125 self._tmpfiles = [] 126 127 if self._install_kernel or self._install_initrd: 128 self._media_type = MEDIA_KERNEL 129 elif (not self.conn.is_remote() and 130 os.path.exists(self.location) and 131 os.path.isdir(self.location)): 132 self.location = os.path.abspath(self.location) 133 self._media_type = MEDIA_DIR 134 elif _is_url(self.location): 135 self._media_type = MEDIA_URL 136 else: 137 self._media_type = MEDIA_ISO 138 139 if (self.conn.is_remote() and 140 not self._media_type == MEDIA_URL and 141 not self._media_type == MEDIA_KERNEL): 142 raise ValueError(_("Cannot access install tree on remote " 143 "connection: %s") % self.location) 144 145 if self._media_type == MEDIA_ISO: 146 InstallerTreeMedia.validate_path(self.conn, self.location) 147 148 149 ######################## 150 # Install preparations # 151 ######################## 152 153 def _get_fetcher(self, guest, meter): 154 meter = progress.ensure_meter(meter) 155 156 if not self._cached_fetcher: 157 scratchdir = InstallerTreeMedia.make_scratchdir(guest) 158 159 if self._media_type == MEDIA_KERNEL: 160 self._cached_fetcher = urlfetcher.DirectFetcher( 161 None, scratchdir, meter) 162 else: 163 self._cached_fetcher = urlfetcher.fetcherForURI( 164 self.location, scratchdir, meter) 165 166 self._cached_fetcher.meter = meter 167 return self._cached_fetcher 168 169 def _get_cached_data(self, guest, fetcher): 170 if self._cached_data: 171 return self._cached_data 172 173 store = None 174 os_variant = None 175 os_media = None 176 os_tree = None 177 kernel_paths = [] 178 has_location_kernel = bool( 179 self._location_kernel and self._location_initrd) 180 181 if self._media_type == MEDIA_KERNEL: 182 kernel_paths = [ 183 (self._install_kernel, self._install_initrd)] 184 else: 185 store = urldetect.getDistroStore(guest, fetcher, 186 skip_error=has_location_kernel) 187 188 if store: 189 kernel_paths = store.get_kernel_paths() 190 os_variant = store.get_osdict_info() 191 os_media = store.get_os_media() 192 os_tree = store.get_os_tree() 193 if has_location_kernel: 194 kernel_paths = [ 195 (self._location_kernel, self._location_initrd)] 196 197 self._cached_data = _LocationData(os_variant, kernel_paths, 198 os_media, os_tree) 199 return self._cached_data 200 201 def _prepare_kernel_url(self, guest, cache, fetcher): 202 ignore = guest 203 204 def _check_kernel_pairs(): 205 for kpath, ipath in cache.kernel_pairs: 206 if fetcher.hasFile(kpath) and fetcher.hasFile(ipath): 207 return kpath, ipath 208 raise RuntimeError( # pragma: no cover 209 _("Couldn't find kernel for install tree.")) 210 211 kernelpath, initrdpath = _check_kernel_pairs() 212 kernel = fetcher.acquireFile(kernelpath) 213 self._tmpfiles.append(kernel) 214 initrd = fetcher.acquireFile(initrdpath) 215 self._tmpfiles.append(initrd) 216 217 perform_initrd_injections(initrd, 218 self._initrd_injections, 219 fetcher.scratchdir) 220 221 return kernel, initrd 222 223 224 ############## 225 # Public API # 226 ############## 227 228 def _prepare_unattended_data(self, scripts): 229 if not scripts: 230 return 231 232 for script in scripts: 233 expected_filename = script.get_expected_filename() 234 scriptpath = script.write() 235 self._tmpfiles.append(scriptpath) 236 self._initrd_injections.append((scriptpath, expected_filename)) 237 238 def _prepare_kernel_url_arg(self, guest, cache): 239 os_variant = cache.os_variant or guest.osinfo.name 240 osobj = OSDB.lookup_os(os_variant) 241 return osobj.get_kernel_url_arg() 242 243 def _prepare_kernel_args(self, guest, cache, unattended_scripts): 244 install_args = None 245 if unattended_scripts: 246 args = [] 247 for unattended_script in unattended_scripts: 248 cmdline = unattended_script.generate_cmdline() 249 if cmdline: 250 args.append(cmdline) 251 install_args = (" ").join(args) 252 log.debug("Generated unattended cmdline: %s", install_args) 253 elif self.is_network_url(): 254 kernel_url_arg = self._prepare_kernel_url_arg(guest, cache) 255 if kernel_url_arg: 256 install_args = "%s=%s" % (kernel_url_arg, self.location) 257 258 if install_args: 259 self._extra_args.append(install_args) 260 261 if self._install_kernel_args: 262 ret = self._install_kernel_args 263 else: 264 ret = " ".join(self._extra_args) 265 266 if self._media_type == MEDIA_DIR and not ret: 267 log.warning(_("Directory tree installs typically do not work " 268 "unless extra kernel args are passed to point the " 269 "installer at a network accessible install tree.")) 270 return ret 271 272 def prepare(self, guest, meter, unattended_scripts): 273 fetcher = self._get_fetcher(guest, meter) 274 cache = self._get_cached_data(guest, fetcher) 275 276 self._prepare_unattended_data(unattended_scripts) 277 kernel_args = self._prepare_kernel_args(guest, cache, unattended_scripts) 278 279 kernel, initrd = self._prepare_kernel_url(guest, cache, fetcher) 280 return kernel, initrd, kernel_args 281 282 def cleanup(self, guest): 283 ignore = guest 284 for f in self._tmpfiles: 285 log.debug("Removing %s", str(f)) 286 os.unlink(f) 287 288 self._tmpfiles = [] 289 290 def set_initrd_injections(self, initrd_injections): 291 self._initrd_injections = initrd_injections 292 293 def set_extra_args(self, extra_args): 294 self._extra_args = extra_args 295 296 def cdrom_path(self): 297 if self._media_type in [MEDIA_ISO]: 298 return self.location 299 300 def is_network_url(self): 301 if self._media_type in [MEDIA_URL]: 302 return self.location 303 304 def detect_distro(self, guest): 305 fetcher = self._get_fetcher(guest, None) 306 cache = self._get_cached_data(guest, fetcher) 307 return cache.os_variant 308 309 def get_os_media(self, guest, meter): 310 fetcher = self._get_fetcher(guest, meter) 311 cache = self._get_cached_data(guest, fetcher) 312 return cache.os_media 313 314 def get_os_tree(self, guest, meter): 315 fetcher = self._get_fetcher(guest, meter) 316 cache = self._get_cached_data(guest, fetcher) 317 return cache.os_tree 318 319 def requires_internet(self, guest, meter): 320 if self._media_type in [MEDIA_URL, MEDIA_DIR]: 321 return True 322 323 os_media = self.get_os_media(guest, meter) 324 if os_media: 325 return os_media.is_netinst() 326 return False 327