def _find_existing_installations(devicetree): """Find existing GNU/Linux installations on devices from the device tree. :param devicetree: a device tree to find existing installations in :return: roots of all found installations """ if not os.path.exists(util.getTargetPhysicalRoot()): blivet_util.makedirs(util.getTargetPhysicalRoot()) sysroot = util.getSysroot() roots = [] direct_devices = (dev for dev in devicetree.devices if dev.direct) for device in direct_devices: if not device.format.linux_native or not device.format.mountable or \ not device.controllable: continue try: device.setup() except Exception: # pylint: disable=broad-except log_exception_info(log.warning, "setup of %s failed", [device.name]) continue options = device.format.options + ",ro" try: device.format.mount(options=options, mountpoint=sysroot) except Exception: # pylint: disable=broad-except log_exception_info(log.warning, "mount of %s as %s failed", [device.name, device.format.type]) blivet_util.umount(mountpoint=sysroot) continue if not os.access(sysroot + "/etc/fstab", os.R_OK): blivet_util.umount(mountpoint=sysroot) device.teardown() continue try: (architecture, product, version) = get_release_string() except ValueError: name = _("Linux on %s") % device.name else: # I'd like to make this finer grained, but it'd be very difficult # to translate. if not product or not version or not architecture: name = _("Unknown Linux") elif "linux" in product.lower(): name = _("%(product)s %(version)s for %(arch)s") % \ {"product": product, "version": version, "arch": architecture} else: name = _("%(product)s Linux %(version)s for %(arch)s") % \ {"product": product, "version": version, "arch": architecture} (mounts, swaps) = _parse_fstab(devicetree, chroot=sysroot) blivet_util.umount(mountpoint=sysroot) if not mounts and not swaps: # empty /etc/fstab. weird, but I've seen it happen. continue roots.append(Root(mounts=mounts, swaps=swaps, name=name)) return roots
def sysroot_test(self): self.assertEqual(util.getTargetPhysicalRoot(), "/mnt/sysimage") self.assertEqual(util.getSysroot(), "/mnt/sysimage") util.setSysroot("/what/ever") self.assertEqual(util.getTargetPhysicalRoot(), "/mnt/sysimage") self.assertEqual(util.getSysroot(), "/what/ever") util.setSysroot(None) self.assertEqual(util.getTargetPhysicalRoot(), "/mnt/sysimage") self.assertEqual(util.getSysroot(), "/mnt/sysimage")
def mount_filesystems(self): root_path = util.getTargetPhysicalRoot() # Mount the root and the filesystems. self.fsset.mount_filesystems(root_path=root_path) # Set up the sysroot. util.setSysroot(root_path)
def root_device(self): for path in ["/", util.getTargetPhysicalRoot()]: for device in self.devices: try: mountpoint = device.format.mountpoint except AttributeError: mountpoint = None if mountpoint == path: return device
def write(self): """Write the bootloader configuration and install the bootloader.""" if self.skip_bootloader: return if self.update_only: self.update() return try: self.write_device_map() self.stage2_device.format.sync(root=util.getTargetPhysicalRoot()) os.sync() self.install() os.sync() self.stage2_device.format.sync(root=util.getTargetPhysicalRoot()) finally: self.write_config() os.sync() self.stage2_device.format.sync(root=util.getTargetPhysicalRoot())
def write(self): """ Write the bootloader configuration and install the bootloader. """ if self.skip_bootloader: # pylint: disable=no-member return if self.update_only: # pylint: disable=no-member self.update() return try: os.sync() self.stage2_device.format.sync(root=util.getTargetPhysicalRoot()) # pylint: disable=no-member self.install() finally: self.write_config() # pylint: disable=no-member
def _copyBootloaderData(self): # Copy bootloader data files from the deployment # checkout to the target root. See # https://bugzilla.gnome.org/show_bug.cgi?id=726757 This # happens once, at installation time. # extlinux ships its modules directly in the RPM in /boot. # For GRUB2, Anaconda installs device.map there. We may need # to add other bootloaders here though (if they can't easily # be fixed to *copy* data into /boot at install time, instead # of shipping it in the RPM). is_efi = isinstance(self.storage.bootloader, EFIBase) physboot = util.getTargetPhysicalRoot() + '/boot' ostree_boot_source = util.getSysroot() + '/usr/lib/ostree-boot' if not os.path.isdir(ostree_boot_source): ostree_boot_source = util.getSysroot() + '/boot' for fname in os.listdir(ostree_boot_source): srcpath = os.path.join(ostree_boot_source, fname) destpath = os.path.join(physboot, fname) # We're only copying directories if not os.path.isdir(srcpath): continue # Special handling for EFI; first, we only want to copy # the data if the system is actually EFI (simulating grub2-efi # being installed). Second, as it's a mount point that's # expected to already exist (so if we used copytree, we'd # traceback). If it doesn't, we're not on a UEFI system, # so we don't want to copy the data. if fname == 'efi': if is_efi: for subname in os.listdir(srcpath): sub_srcpath = os.path.join(srcpath, subname) sub_destpath = os.path.join(destpath, subname) self._safeExecWithRedirect( 'cp', ['-r', '-p', sub_srcpath, sub_destpath]) else: log.info("Copying bootloader data: " + fname) self._safeExecWithRedirect('cp', ['-r', '-p', srcpath, destpath]) # Unfortunate hack, see https://github.com/rhinstaller/anaconda/issues/1188 efi_grubenv_link = physboot + '/grub2/grubenv' if not is_efi and os.path.islink(efi_grubenv_link): os.unlink(efi_grubenv_link)
def _copy_bootloader_data(self): # Copy bootloader data files from the deployment # checkout to the target root. See # https://bugzilla.gnome.org/show_bug.cgi?id=726757 This # happens once, at installation time. # extlinux ships its modules directly in the RPM in /boot. # For GRUB2, Anaconda installs device.map there. We may need # to add other bootloaders here though (if they can't easily # be fixed to *copy* data into /boot at install time, instead # of shipping it in the RPM). is_efi = isinstance(self.storage.bootloader, EFIBase) physboot = util.getTargetPhysicalRoot() + '/boot' ostree_boot_source = util.getSysroot() + '/usr/lib/ostree-boot' if not os.path.isdir(ostree_boot_source): ostree_boot_source = util.getSysroot() + '/boot' for fname in os.listdir(ostree_boot_source): srcpath = os.path.join(ostree_boot_source, fname) destpath = os.path.join(physboot, fname) # We're only copying directories if not os.path.isdir(srcpath): continue # Special handling for EFI; first, we only want to copy # the data if the system is actually EFI (simulating grub2-efi # being installed). Second, as it's a mount point that's # expected to already exist (so if we used copytree, we'd # traceback). If it doesn't, we're not on a UEFI system, # so we don't want to copy the data. if fname == 'efi': if is_efi: for subname in os.listdir(srcpath): sub_srcpath = os.path.join(srcpath, subname) sub_destpath = os.path.join(destpath, subname) self._safe_exec_with_redirect('cp', ['-r', '-p', sub_srcpath, sub_destpath]) else: log.info("Copying bootloader data: %s", fname) self._safe_exec_with_redirect('cp', ['-r', '-p', srcpath, destpath]) # Unfortunate hack, see https://github.com/rhinstaller/anaconda/issues/1188 efi_grubenv_link = physboot + '/grub2/grubenv' if not is_efi and os.path.islink(efi_grubenv_link): os.unlink(efi_grubenv_link)
def create_swap_file(self, device, size): """Create and activate a swap file under storage root.""" filename = "/SWAP" count = 0 basedir = os.path.normpath( "%s/%s" % (util.getTargetPhysicalRoot(), device.format.mountpoint)) while os.path.exists("%s/%s" % (basedir, filename)) or \ self.devicetree.get_device_by_name(filename): count += 1 filename = "/SWAP-%d" % count dev = FileDevice(filename, size=size, parents=[device], fmt=get_format("swap", device=filename)) dev.create() dev.setup() dev.format.create() dev.format.setup() # nasty, nasty self.devicetree._add_device(dev)
def create_swap_file(self, device, size): """Create and activate a swap file under storage root.""" filename = "/SWAP" count = 0 basedir = os.path.normpath("%s/%s" % (util.getTargetPhysicalRoot(), device.format.mountpoint)) while os.path.exists("%s/%s" % (basedir, filename)) or \ self.devicetree.get_device_by_name(filename): count += 1 filename = "/SWAP-%d" % count dev = FileDevice(filename, size=size, parents=[device], fmt=get_format("swap", device=filename)) dev.create() dev.setup() dev.format.create() dev.format.setup() # nasty, nasty self.devicetree._add_device(dev)
def _setupInternalBindmount(self, src, dest=None, src_physical=True, bind_ro=False, recurse=True): """Internal API for setting up bind mounts between the physical root and sysroot, also ensures we track them in self._internal_mounts so we can cleanly unmount them. :param src: Source path, will be prefixed with physical or sysroot :param dest: Destination, will be prefixed with sysroot (defaults to same as src) :param src_physical: Prefix src with physical root :param bind_ro: Make mount read-only :param recurse: Use --rbind to recurse, otherwise plain --bind """ # Default to the same basename if dest is None: dest = src # Almost all of our mounts go from physical to sysroot if src_physical: src = util.getTargetPhysicalRoot() + src else: src = util.getSysroot() + src # Canonicalize dest to the full path dest = util.getSysroot() + dest if bind_ro: self._safeExecWithRedirect("mount", ["--bind", src, src]) self._safeExecWithRedirect( "mount", ["--bind", "-o", "remount,ro", src, src]) else: # Recurse for non-ro binds so we pick up sub-mounts # like /sys/firmware/efi/efivars. if recurse: bindopt = '--rbind' else: bindopt = '--bind' self._safeExecWithRedirect("mount", [bindopt, src, dest]) self._internal_mounts.append(src if bind_ro else dest)
def _setup_internal_bindmount(self, src, dest=None, src_physical=True, bind_ro=False, recurse=True): """Internal API for setting up bind mounts between the physical root and sysroot, also ensures we track them in self._internal_mounts so we can cleanly unmount them. :param src: Source path, will be prefixed with physical or sysroot :param dest: Destination, will be prefixed with sysroot (defaults to same as src) :param src_physical: Prefix src with physical root :param bind_ro: Make mount read-only :param recurse: Use --rbind to recurse, otherwise plain --bind """ # Default to the same basename if dest is None: dest = src # Almost all of our mounts go from physical to sysroot if src_physical: src = util.getTargetPhysicalRoot() + src else: src = util.getSysroot() + src # Canonicalize dest to the full path dest = util.getSysroot() + dest if bind_ro: self._safe_exec_with_redirect("mount", ["--bind", src, src]) self._safe_exec_with_redirect("mount", ["--bind", "-o", "remount,ro", src, src]) else: # Recurse for non-ro binds so we pick up sub-mounts # like /sys/firmware/efi/efivars. if recurse: bindopt = '--rbind' else: bindopt = '--bind' self._safe_exec_with_redirect("mount", [bindopt, src, dest]) self._internal_mounts.append(src if bind_ro else dest)
def mount_existing_system(storage, root_device, read_only=None, sysroot=None): """Mount filesystems specified in root_device's /etc/fstab file.""" root_path = sysroot or util.getTargetPhysicalRoot() read_only = "ro" if read_only else "" # Mount the root device. if root_device.protected and os.path.ismount("/mnt/install/isodir"): blivet_util.mount("/mnt/install/isodir", root_path, fstype=root_device.format.type, options="bind") else: root_device.setup() root_device.format.mount(chroot=root_path, mountpoint="/", options="%s,%s" % (root_device.format.options, read_only)) # Set up the sysroot. util.setSysroot(root_path) # Mount the filesystems. storage.fsset.parse_fstab(chroot=root_path) storage.fsset.mount_filesystems(root_path=root_path, read_only=read_only, skip_root=True) # Turn on swap. if not conf.target.is_image or not read_only: try: storage.fsset.turn_on_swap(root_path=sysroot) except StorageError as e: log.error("Error enabling swap: %s", str(e)) # Generate mtab. if not read_only: storage.make_mtab(chroot=root_path)
def install(self): mainctx = create_new_context() mainctx.push_thread_default() cancellable = None gi.require_version("OSTree", "1.0") gi.require_version("RpmOstree", "1.0") from gi.repository import OSTree, RpmOstree ostreesetup = self.data.ostreesetup log.info("executing ostreesetup=%r", ostreesetup) # Initialize the filesystem - this will create the repo as well self._safeExecWithRedirect("ostree", [ "admin", "--sysroot=" + util.getTargetPhysicalRoot(), "init-fs", util.getTargetPhysicalRoot() ]) # Here, we use the physical root as sysroot, because we haven't # yet made a deployment. sysroot_file = Gio.File.new_for_path(util.getTargetPhysicalRoot()) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) self._remoteOptions = {} if hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg: self._remoteOptions['gpg-verify'] = Variant('b', False) if flags.noverifyssl: self._remoteOptions['tls-permissive'] = Variant('b', True) repo.remote_change(None, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, ostreesetup.remote, ostreesetup.url, Variant('a{sv}', self._remoteOptions), cancellable) # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 ref = RpmOstree.varsubst_basearch(ostreesetup.ref) progressQ.send_message(_("Starting pull of %(branchName)s from %(source)s") % \ {"branchName": ref, "source": ostreesetup.remote}) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pullProgressCb) pull_opts = {'refs': Variant('as', [ref])} # If we're doing a kickstart, we can at least use the content as a reference: # See <https://github.com/rhinstaller/anaconda/issues/1117> # The first path here is used by <https://pagure.io/fedora-lorax-templates> # and the second by <https://github.com/projectatomic/rpm-ostree-toolbox/> if OSTree.check_version(2017, 8): for path in ['/ostree/repo', '/install/ostree/repo']: if os.path.isdir(path + '/objects'): pull_opts['localcache-repos'] = Variant('as', [path]) break try: repo.pull_with_options(ostreesetup.remote, Variant('a{sv}', pull_opts), progress, cancellable) except GError as e: exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) log.info("ostree pull: " + (progress.get_status() or "")) progressQ.send_message(_("Preparing deployment of %s") % (ref, )) # Now that we have the data pulled, delete the remote for now. # This will allow a remote configuration defined in the tree # (if any) to override what's in the kickstart. Otherwise, # we'll re-add it in post. Ideally, ostree would support a # pull without adding a remote, but that would get quite # complex. repo.remote_delete(self.data.ostreesetup.remote, None) self._safeExecWithRedirect("ostree", [ "admin", "--sysroot=" + util.getTargetPhysicalRoot(), "os-init", ostreesetup.osname ]) admin_deploy_args = [ "admin", "--sysroot=" + util.getTargetPhysicalRoot(), "deploy", "--os=" + ostreesetup.osname ] admin_deploy_args.append(ostreesetup.remote + ':' + ref) log.info("ostree admin deploy starting") progressQ.send_message(_("Deployment starting: %s") % (ref, )) self._safeExecWithRedirect("ostree", admin_deploy_args) log.info("ostree admin deploy complete") progressQ.send_message(_("Deployment complete: %s") % (ref, )) # Reload now that we've deployed, find the path to the new deployment sysroot.load(None) deployments = sysroot.get_deployments() assert len(deployments) > 0 deployment = deployments[0] deployment_path = sysroot.get_deployment_directory(deployment) util.setSysroot(deployment_path.get_path()) try: self._copyBootloaderData() except (OSError, RuntimeError) as e: exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) mainctx.pop_thread_default()
def install(self): mainctx = create_new_context() mainctx.push_thread_default() cancellable = None gi.require_version("OSTree", "1.0") gi.require_version("RpmOstree", "1.0") from gi.repository import OSTree, RpmOstree ostreesetup = self.data.ostreesetup log.info("executing ostreesetup=%r", ostreesetup) # Initialize the filesystem - this will create the repo as well self._safe_exec_with_redirect("ostree", ["admin", "--sysroot=" + util.getTargetPhysicalRoot(), "init-fs", util.getTargetPhysicalRoot()]) # Here, we use the physical root as sysroot, because we haven't # yet made a deployment. sysroot_file = Gio.File.new_for_path(util.getTargetPhysicalRoot()) sysroot = OSTree.Sysroot.new(sysroot_file) sysroot.load(cancellable) repo = sysroot.get_repo(None)[1] # We don't support resuming from interrupted installs repo.set_disable_fsync(True) self._remoteOptions = {} if hasattr(ostreesetup, 'nogpg') and ostreesetup.nogpg: self._remoteOptions['gpg-verify'] = Variant('b', False) if flags.noverifyssl: self._remoteOptions['tls-permissive'] = Variant('b', True) repo.remote_change(None, OSTree.RepoRemoteChange.ADD_IF_NOT_EXISTS, ostreesetup.remote, ostreesetup.url, Variant('a{sv}', self._remoteOptions), cancellable) # Variable substitute the ref: https://pagure.io/atomic-wg/issue/299 ref = RpmOstree.varsubst_basearch(ostreesetup.ref) progressQ.send_message(_("Starting pull of %(branchName)s from %(source)s") % {"branchName": ref, "source": ostreesetup.remote}) progress = OSTree.AsyncProgress.new() progress.connect('changed', self._pull_progress_cb) pull_opts = {'refs': Variant('as', [ref])} # If we're doing a kickstart, we can at least use the content as a reference: # See <https://github.com/rhinstaller/anaconda/issues/1117> # The first path here is used by <https://pagure.io/fedora-lorax-templates> # and the second by <https://github.com/projectatomic/rpm-ostree-toolbox/> if OSTree.check_version(2017, 8): for path in ['/ostree/repo', '/install/ostree/repo']: if os.path.isdir(path + '/objects'): pull_opts['localcache-repos'] = Variant('as', [path]) break try: repo.pull_with_options(ostreesetup.remote, Variant('a{sv}', pull_opts), progress, cancellable) except GError as e: exn = PayloadInstallError("Failed to pull from repository: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) log.info("ostree pull: %s", progress.get_status() or "") progressQ.send_message(_("Preparing deployment of %s") % (ref, )) # Now that we have the data pulled, delete the remote for now. # This will allow a remote configuration defined in the tree # (if any) to override what's in the kickstart. Otherwise, # we'll re-add it in post. Ideally, ostree would support a # pull without adding a remote, but that would get quite # complex. repo.remote_delete(self.data.ostreesetup.remote, None) self._safe_exec_with_redirect("ostree", ["admin", "--sysroot=" + util.getTargetPhysicalRoot(), "os-init", ostreesetup.osname]) admin_deploy_args = ["admin", "--sysroot=" + util.getTargetPhysicalRoot(), "deploy", "--os=" + ostreesetup.osname] admin_deploy_args.append(ostreesetup.remote + ':' + ref) log.info("ostree admin deploy starting") progressQ.send_message(_("Deployment starting: %s") % (ref, )) self._safe_exec_with_redirect("ostree", admin_deploy_args) log.info("ostree admin deploy complete") progressQ.send_message(_("Deployment complete: %s") % (ref, )) # Reload now that we've deployed, find the path to the new deployment sysroot.load(None) deployments = sysroot.get_deployments() assert len(deployments) > 0 deployment = deployments[0] deployment_path = sysroot.get_deployment_directory(deployment) util.setSysroot(deployment_path.get_path()) try: self._copy_bootloader_data() except (OSError, RuntimeError) as e: exn = PayloadInstallError("Failed to copy bootloader data: %s" % e) log.error(str(exn)) if errors.errorHandler.cb(exn) == errors.ERROR_RAISE: progressQ.send_quit(1) util.ipmi_abort(scripts=self.data.scripts) sys.exit(1) mainctx.pop_thread_default()
def sysroot_test(self): self.assertEqual(util.getTargetPhysicalRoot(), "/mnt/sysimage") self.assertEqual(util.getSysroot(), "/mnt/sysroot")
def _calculate_free_space(self): """Calculate the available space.""" stat = os.statvfs(util.getTargetPhysicalRoot()) return Size(stat.f_bsize * stat.f_bfree)