Пример #1
0
def Update(root = None, conf = None, handler = None):
    """
    Perform an update.  Calls CheckForUpdates() first, to see if
    there are any. If there are, then magic happens.
    """

    deleted_packages = []
    other_packages = []
    def UpdateHandler(op, pkg, old):
        if op == "delete":
            deleted_packages.append(pkg)
        else:
            other_packages.append((pkg, old))
        if handler is not None:
            handler(op, pkg, old)

    new_man = CheckForUpdates(root, UpdateHandler)
    if new_man is None:
        return

    # Now we have a list of deleted packages, and a list
    # of update/install packages.
    # The task is to delete the first set of packages,
    # and then run through the others to install/update.
    # First, however, we need to get the files.
    # We want to use the system configuration, unless one has been
    # specified -- we don't want to use the target root's.
    if conf is None:
        conf = Configuration.Configuration(root = root)

    for pkg in deleted_packages:
        print >> sys.stderr, "Want to delete package %s" % pkg.Name()
        #        if conf.PackageDB().RemovePackageContents(pkg) == False:
        #            print >> sys.stderr, "Unable to remove contents package %s" % pkg.Name()
        #            sys.exit(1)
        #        conf.PackageDB().RemovePackage(pkg.Name())

    process_packages = []
    for (pkg, old) in other_packages:
        process_packages.append(pkg)

    installer = Installer.Installer(manifest = new_man, root = root, config = conf)
    installer.GetPackages(process_packages)

    print "Packages = %s" % installer._packages
    return
Пример #2
0
def ApplyUpdate(directory, install_handler=None):
    """
    Apply the update in <directory>.  As with PendingUpdates(), it will
    have to verify the contents before it actually installs them, so
    it has the same behaviour with incomplete or invalid content.
    """
    rv = False
    conf = Configuration.Configuration()
    changes = PendingUpdates(directory)
    if changes is None:
        # That could have happened for multiple reasons.
        # PendingUpdates should probably throw an exception
        # on error
        return False
    # Do I have to worry about a race condition here?
    new_manifest = Manifest.Manifest()
    new_manifest.LoadPath(directory + "/MANIFEST")
    conf.SetPackageDir(directory)

    deleted_packages = []
    updated_packages = []
    for (pkg, op, old) in changes:
        if op == "delete":
            log.debug("Delete package %s" % pkg.Name())
            deleted_packages.append(pkg)
        elif op == "install":
            log.debug("Install package %s" % pkg.Name())
        else:
            log.debug("Upgrade package %s-%s to %s-%s" %
                      (old.Name(), old.Version(), pkg.Name(), pkg.Version()))
            updated_packages.append(pkg)

    if len(deleted_packages) == 0 and len(updated_packages) == 0:
        # The manifest may have other differences, so we should
        # probably do something.
        log.debug("New manifest has no package changes, what should we do?")
        RemoveUpdate(directory)
        return True

    # Now we start doing the update!
    clone_name = None
    mount_point = None
    try:
        clone_name = "%s-%s" % (Avatar(), new_manifest.Sequence())
        if CreateClone(clone_name) is False:
            s = "Unable to create boot-environment %s" % clone_name
            log.error(s)
            raise Exception(s)
        mount_point = MountClone(clone_name)
        if mount_point is None:
            s = "Unable to mount boot-environment %s" % clone_name
            log.error(s)
            DeleteClone(clone_name)
            raise Exception(s)

        # Remove any deleted packages
        for pkg in deleted_packages:
            log.debug("About to delete package %s" % pkg.Name())
            if conf.PackageDB(mount_point).RemovePackageContents(pkg) == False:
                s = "Unable to remove contents for packate %s" % pkg.Name()
                if mount_point:
                    UnmountClone(clone_name, mount_point)
                    mount_point = None
                    DeleteClone(clone_name)
                raise Exception(s)
            conf.PackageDB(mount_point).RemovePackage(pkg.Name())

        installer = Installer.Installer(manifest=new_manifest,
                                        root=mount_point,
                                        config=conf)
        installer.GetPackages(pkgList=updated_packages)
        log.debug("Installer got packages %s" % installer._packages)
        # Now to start installing them
        rv = False
        if installer.InstallPackages(handler=install_handler) is False:
            log.error("Unable to install packages")
            raise Exception("Unable to install packages")
        else:
            new_manifest.Save(mount_point)
            if mount_point:
                if UnmountClone(clone_name, mount_point) is False:
                    s = "Unable to unmount clone environment %s from mount point %s" % (
                        clone_name, mount_point)
                    log.error(s)
                    raise Exception(s)
                mount_point = None
                if ActivateClone(clone_name) is False:
                    s = "Unable to activate clone environment %s" % clone_name
                    log.error(s)
                    raise Exception(s)
                RemoveUpdate(directory)
                rv = True
                RunCommand("/sbin/zpool", ["scrub", "freenas-boot"])
    except BaseException as e:
        log.error("Update got exception during update: %s" % str(e))
        if mount_point:
            UnmountClone(clone_name, mount_point)
        if clone_name:
            DeleteClone(clone_name)
        raise e

    return rv
Пример #3
0
def Update(root=None,
           conf=None,
           train=None,
           check_handler=None,
           get_handler=None,
           install_handler=None,
           cache_dir=None):
    """
    Perform an update.  If cache_dir is set, and contains a valid
    set of files (MANIFEST exists, and is for the current train, and
    the sequence is not the same as the system sequence), it will
    use that, rather than downloading.  It calls CheckForUpdates() otherwise,
    to see if there are any. If there are, then magic happens.
    """

    log.debug("Update(root = %s, conf = %s, train = %s, cache_dir = %s)" %
              (root, conf, train, cache_dir))
    if conf is None:
        conf = Configuration.Configuration(root)

    deleted_packages = []
    process_packages = []

    def UpdateHandler(op, pkg, old):
        if op == "delete":
            deleted_packages.append(pkg)
        else:
            process_packages.append(pkg)
        if check_handler is not None:
            check_handler(op, pkg, old)

    new_man = None
    if cache_dir:
        if os.path.exists(cache_dir + "/MANIFEST"):
            try:
                cur = conf.SystemManifest()
                new_man = Manifest.Manifest()
                new_man.LoadPath(cache_dir + "/MANIFEST")
                if new_man.Train() != cur.Train():
                    new_man = None
                    cache_dir = None
                else:
                    diffs = Manifest.CompareManifests(cur, new_man)
                    for (pkg, op, old) in diffs:
                        UpdateHandler(op, pkg, old)
                    conf.SetPackageDir(cache_dir)
            except Exception as e:
                log.debug("Caught exception %s" % str(e))
                new_man = None
                cache_dir = None

    if new_man is None:
        new_man = CheckForUpdates(root, handler=UpdateHandler, train=train)

    if new_man is None:
        return

    if len(deleted_packages) == 0 and len(process_packages) == 0:
        # We have a case where a manifest was updated, but we
        # don't actually have any changes to the packages.  We
        # should install the new manifest, and be done -- it
        # may have new release notes, or other issues.
        # Right now, I'm not quite sure how to do this.
        log.debug("Updated manifest but no package differences")
        return

    # Now we have a list of deleted packages, and a list
    # of update/install packages.
    # The task is to delete the first set of packages,
    # and then run through the others to install/update.
    # First, however, we need to get the files.
    # We want to use the system configuration, unless one has been
    # specified -- we don't want to use the target root's.
    if conf is None:
        conf = Configuration.Configuration()

    # If root is None, then we will try to create a clone
    # environment.  (If the caller wants to install into the
    # current boot environment, set root = "" or "/".)
    clone_name = None
    mount_point = None
    try:
        if root is None:
            # We clone the existing boot environment to
            # "Avatar()-<sequence>"
            clone_name = "%s-%s" % (Avatar(), new_man.Sequence())
            if CreateClone(clone_name) is False:
                log.error("Unable to create boot-environment %s" % clone_name)
                raise Exception("Unable to create new boot-environment %s" %
                                clone_name)

            mount_point = MountClone(clone_name)
            if mount_point is None:
                log.error("Unable to mount boot-environment %s" % clone_name)
                DeleteClone(clone_name)
                raise Exception("Unable to mount boot-environment %s" %
                                clone_name)
            else:
                root = mount_point
        else:
            mount_point = None

        for pkg in deleted_packages:
            log.debug("Want to delete package %s" % pkg.Name())
            if conf.PackageDB(root).RemovePackageContents(pkg) == False:
                log.error("Unable to remove contents package %s" % pkg.Name())
                if mount_point:
                    UnmountClone(clone_name, mount_point)
                    mount_point = None
                    DeleteClone(clone_name)
                raise Exception("Unable to remove contents for package %s" %
                                pkg.Name())
            conf.PackageDB(root).RemovePackage(pkg.Name())

        installer = Installer.Installer(manifest=new_man,
                                        root=root,
                                        config=conf)
        installer.GetPackages(process_packages, handler=get_handler)

        log.debug("Packages = %s" % installer._packages)

        # Now let's actually install them.
        # Only change on success
        rv = False
        if installer.InstallPackages(handler=install_handler) is False:
            log.error("Unable to install packages")
        else:
            new_man.Save(root)
            if mount_point is not None:
                if UnmountClone(clone_name, mount_point) is False:
                    log.error("Unable to mount clone enivironment %s" %
                              clone_name)
                else:
                    mount_point = None
                    if ActivateClone(clone_name) is False:
                        log.error("Could not activate clone environment %s" %
                                  clone_name)
                    else:
                        # Downloaded package files are open-unlinked, so don't
                        # have to be cleaned up.  If there's a cache directory,
                        # however, we need to get rid of it.
                        if cache_dir:
                            import shutil
                            if os.path.exists(cache_dir):
                                try:
                                    shutil.rmtree(cache_dir)
                                except Exception as e:
                                    # If that doesn't work, for now at least we'll
                                    # simply ignore the error.
                                    log.debug(
                                        "Tried to remove cache directory %s, got exception %s"
                                        % (cache_dir, str(e)))
                        rv = True
                        # Start a scrub.  We ignore the return value.
                        RunCommand("/sbin/zpool", ["scrub", "freenas-boot"])

    except BaseException as e:
        log.error("Update got exception during update: %s" % str(e))
        if clone_name:
            if mount_point:
                # Ignore the error here
                UnmountClone(clone_name, mount_point)
            DeleteClone(clone_name)
        raise e

    # Clean up
    # That just leaves the clone, which
    # we should unmount, and destroy if necessary.
    # Unmounting attempts to delete the mount point that was created.
    if rv is False:
        if clone_name and DeleteClone(clone_name) is False:
            log.error("Unable to delete boot environment %s in failure case" %
                      clone_name)

    return rv
Пример #4
0
    root = args[0]

    config = Configuration.SystemConfiguration()
    if package_dir is not None:
        config.SetPackageDir(package_dir)

    if mani_file is None:
        manifest = config.SystemManifest()
    else:
        # We ignore the signature because freenas-install is
        # called from the ISO install, and the GUI install, which
        # have their own checksums elsewhere.
        manifest = Manifest.Manifest(config, require_signature=False)
        manifest.LoadPath(mani_file)

    installer = Installer.Installer(manifest=manifest,
                                    root=root,
                                    config=config)

    if installer.GetPackages() is not True:
        print("Huh, could not install and yet it returned", file=sys.stderr)

    with Installer.ProgressHandler() as pf:
        # For installation, we assume that we're running the same kernel as the new system.
        installer.trampoline = False
        installer.InstallPackages(progressFunc=pf.update,
                                  handler=install_handler)

    manifest.Save(root)
    sys.exit(0)
Пример #5
0
def ApplyUpdate(directory, install_handler=None, force_reboot=False):
    """
    Apply the update in <directory>.  As with PendingUpdates(), it will
    have to verify the contents before it actually installs them, so
    it has the same behaviour with incomplete or invalid content.
    """
    rv = False
    conf = Configuration.Configuration()
    # Note that PendingUpdates may raise an exception
    changes = PendingUpdatesChanges(directory)

    if changes is None:
        # This means no updates to apply, and so nothing to do.
        return None

    # Do I have to worry about a race condition here?
    new_manifest = Manifest.Manifest(require_signature=True)
    try:
        new_manifest.LoadPath(directory + "/MANIFEST")
    except ManifestInvalidSignature as e:
        log.error("Cached manifest has invalid signature: %s" % str(e))
        raise e

    conf.SetPackageDir(directory)

    # If we're here, then we have some change to make.
    # PendingUpdatesChanges always sets this, unless it returns None
    reboot = changes["Reboot"]
    if force_reboot:
        # Just in case
        reboot = True

    changes.pop("Reboot")
    if len(changes) == 0:
        # This shouldn't happen
        log.debug("ApplyUupdate:  changes only has Reboot key")
        return None

    deleted_packages = []
    updated_packages = []
    if "Packages" in changes:
        for (pkg, op, old) in changes["Packages"]:
            if op == "delete":
                log.debug("Delete package %s" % pkg.Name())
                deleted_packages.append(pkg)
                continue
            elif op == "install":
                log.debug("Install package %s" % pkg.Name())
                updated_packages.append(pkg)
            elif op == "upgrade":
                log.debug(
                    "Upgrade package %s-%s to %s-%s" %
                    (old.Name(), old.Version(), pkg.Name(), pkg.Version()))
                updated_packages.append(pkg)
            else:
                log.error("Unknown package operation %s for %s" %
                          (op, pkg.Name()))

    if new_manifest.Sequence().startswith(Avatar() + "-"):
        new_boot_name = new_manifest.Sequence()
    else:
        new_boot_name = "%s-%s" % (Avatar(), new_manifest.Sequence())

    log.debug("new_boot_name = %s, reboot = %s" % (new_boot_name, reboot))

    mount_point = None
    if reboot:
        # Need to create a new boot environment
        try:
            if CreateClone(new_boot_name) is False:
                log.debug("Failed to create BE %s" % new_boot_name)
                # It's possible the boot environment already exists.
                s = None
                clones = ListClones()
                if clones:
                    found = False
                    for c in clones:
                        if c["name"] == new_boot_name:
                            found = True
                            if c["mountpoint"] != "/":
                                if c["mountpoint"] != "-":
                                    mount_point = c["mountpoint"]
                        else:
                            s = "Cannot create boot-environment with same name as current boot-environment (%s)" % new_boot_name
                        break
                    if found is False:
                        s = "Unable to create boot-environment %s" % new_boot_name
                else:
                    log.debug("Unable to list clones after creation failure")
                    s = "Unable to create boot-environment %s" % new_boot_name
                    if s:
                        log.error(s)
                    raise UpdateBootEnvironmentException(s)
            if mount_point is None:
                mount_point = MountClone(new_boot_name)
        except:
            mount_point = None
            s = sys.exc_info()[0]
        if mount_point is None:
            s = "Unable to mount boot-environment %s" % new_boot_name
            log.error(s)
            DeleteClone(new_boot_name)
            raise UpdateBootEnvironmentException(s)
    else:
        # Need to do magic to move the current boot environment aside,
        # and assign the newname to the current boot environment.
        # Also need to make a snapshot of the current root so we can
        # clean up on error
        mount_point = None
        log.debug("We should try to do a non-rebooty update")
        root_dataset = GetRootDataset()
        if root_dataset is None:
            log.error("Unable to determine root environment name")
            raise UpdateBootEnvironmentException(
                "Unable to determine root environment name")
        # We also want the root name
        root_env = None
        clones = ListClones()
        if clones is None:
            log.error("Unable to determine root BE")
            raise UpdateBootEnvironmentException("Unable to determine root BE")
        for clone in clones:
            if clone["mountpoint"] == "/":
                root_env = clone
                break
        if root_env is None:
            log.error("Unable to find root BE!")
            raise UpdateBootEnvironmentException("Unable to find root BE!")

        # Now we want to snapshot the current boot environment,
        # so we can rollback as needed.
        snapshot_name = "%s@Pre-Uprgade-%s" % (root_dataset,
                                               new_manifest.Sequence())
        cmd = "/sbin/zfs"
        args = ["snapshot", "-r", snapshot_name]
        rv = RunCommand(cmd, args)
        if rv is False:
            log.error("Unable to create snapshot %s, bailing for now" %
                      snapshot_name)
            raise UpdateSnapshotException("Unable to create snapshot %s" %
                                          snapshot_name)
        # We need to remove the beadm:nickname property.  I hate knowing this much
        # about the implementation
        args = ["inherit", "-r", "beadm:nickname", snapshot_name]
        RunCommand(cmd, args)

        # At this point, we'd want to rename the boot environment to be the new
        # name, which would be new_manifest.Sequence()
        if CreateClone(new_boot_name, rename=root_env["name"]) is False:
            log.error("Unable to create new boot environment %s" %
                      new_boot_name)
            # Roll back and destroy the snapshot we took
            cmd = "/sbin/zfs"
            args = ["rollback", snapshot_name]
            RunCommand(cmd, args)
            args[0] = "destroy"
            RunCommand(cmd, args)
            # And set the beadm:nickname property back
            args = ["set", "beadm:nickname=%s" % root_env["name"]]
            RunCommand(cmd, args)

            raise UpdateBootEnvironmentException(
                "Unable to create new boot environment %s" % new_boot_nam)

    # Now we start doing the update!
    # If we have to reboot, then we need to
    # make a new boot environment, with the appropriate name.
    # If we are *not* rebooting, then we want to rename the
    # current one with the appropriate name, while at the same
    # time cloning the current one and keeping the existing name.
    # Easy peasy, right?

    try:
        # Remove any deleted packages
        for pkg in deleted_packages:
            log.debug("About to delete package %s" % pkg.Name())
            if conf.PackageDB(mount_point).RemovePackageContents(pkg) == False:
                s = "Unable to remove contents for packate %s" % pkg.Name()
                if mount_point:
                    UnmountClone(new_boot_name, mount_point)
                    mount_point = None
                    DeleteClone(new_boot_name)
                raise UpdatePackageException(s)
            conf.PackageDB(mount_point).RemovePackage(pkg.Name())

        installer = Installer.Installer(manifest=new_manifest,
                                        root=mount_point,
                                        config=conf)
        installer.GetPackages(pkgList=updated_packages)
        log.debug("Installer got packages %s" % installer._packages)
        # Now to start installing them
        rv = False
        if installer.InstallPackages(handler=install_handler) is False:
            log.error("Unable to install packages")
            raise UpdatePackageException("Unable to install packages")
        else:
            new_manifest.Save(mount_point)
            if mount_point:
                if UnmountClone(new_boot_name, mount_point) is False:
                    s = "Unable to unmount clone environment %s from mount point %s" % (
                        new_boot_name, mount_point)
                    log.error(s)
                    raise UpdateBootEnvironmentException(s)
                mount_point = None
            if reboot:
                if ActivateClone(new_boot_name) is False:
                    s = "Unable to activate clone environment %s" % new_boot_name
                    log.error(s)
                    raise UpdateBootEnvironmentException(s)
            if not reboot:
                # Clean up the emergency holographic snapshot
                cmd = "/sbin/zfs"
                args = ["destroy", "-r", snapshot_name]
                rv = RunCommand(cmd, args)
                if rv is False:
                    log.error("Unable to destroy snapshot %s" % snapshot_name)
            RemoveUpdate(directory)
            # RunCommand("/sbin/zpool", ["scrub", "freenas-boot"])
    except BaseException as e:
        # Cleanup code is entirely different for reboot vs non reboot
        log.error("Update got exception during update: %s" % str(e))
        if reboot:
            if mount_point:
                UnmountClone(new_boot_name, mount_point)
            if new_boot_name:
                DeleteClone(new_boot_name)
        else:
            # Need to roll back
            # We also need to delete the renamed clone of /,
            # and then rename / to the original name.
            # First, however, destroy the clone
            rv = DeleteClone(root_env["name"])
            if rv:
                # Next, rename the clone
                rv = RenameClone(new_boot_name, root_env["name"])
                if rv:
                    # Now roll back the snapshot, and set the beadm:nickname value
                    cmd = "/sbin/zfs"
                    args = ["rollback", "-r", snapshot_name]
                    rv = RunCommand(cmd, args)
                    if rv is False:
                        log.error("Unable to rollback %s" % snapshot_name)
                        # Don't know what to do then
                    args = [
                        "set",
                        "beadm:nickname=%s" % root_env["name"],
                        "freenas-boot/ROOT/%s" % root_env["name"]
                    ]
                    rv = RunCommand(cmd, args)
                    if rv is False:
                        log.error(
                            "Unable to set nickname, wonder what I did wrong")
                    args = ["destroy", "-r", snapshot_name]
                    rv = RunCommand(cmd, args)
                    if rv is False:
                        log.error("Unable to destroy snapshot %s" %
                                  snapshot_name)
        raise e

    return reboot
Пример #6
0
    if len(args) != 1:
        usage()

    root = args[0]

    config = Configuration.Configuration()
    if package_dir is not None:
        config.SetPackageDir(package_dir)

    if mani_file is None:
        manifest = config.SystemManifest()
    else:
        # We ignore the signature because freenas-install is
        # called from the ISO install, and the GUI install, which
        # have their own checksums elsewhere.
        manifest = Manifest.Manifest(config, require_signature=False)
        manifest.LoadPath(mani_file)

    installer = Installer.Installer(manifest=manifest,
                                    root=root,
                                    config=config)

    if installer.GetPackages() is not True:
        print("Huh, could not install and yet it returned", file=sys.stderr)

    # For installation, we assume that we're running the same kernel as the new system.
    installer.trampoline = False
    installer.InstallPackages(handler=install_handler)
    manifest.Save(root)
    sys.exit(0)
Пример #7
0
def ApplyUpdate(directory, install_handler=None):
    """
    Apply the update in <directory>.  As with PendingUpdates(), it will
    have to verify the contents before it actually installs them, so
    it has the same behaviour with incomplete or invalid content.
    """
    rv = False
    conf = Configuration.Configuration()
    changes = PendingUpdates(directory)
    if changes is None:
        # That could have happened for multiple reasons.
        # PendingUpdates should probably throw an exception
        # on error
        return False
    # Do I have to worry about a race condition here?
    new_manifest = Manifest.Manifest(require_signature=True)
    try:
        new_manifest.LoadPath(directory + "/MANIFEST")
    except ManifestInvalidSignature as e:
        log.error("Cached manifest has invalid signature: %s" % str(e))
        return False

    conf.SetPackageDir(directory)

    deleted_packages = []
    updated_packages = []
    for (pkg, op, old) in changes:
        if op == "delete":
            log.debug("Delete package %s" % pkg.Name())
            deleted_packages.append(pkg)
        elif op == "install":
            log.debug("Install package %s" % pkg.Name())
        else:
            log.debug("Upgrade package %s-%s to %s-%s" %
                      (old.Name(), old.Version(), pkg.Name(), pkg.Version()))
            updated_packages.append(pkg)

    if len(deleted_packages) == 0 and len(updated_packages) == 0:
        # The manifest may have other differences, so we should
        # probably do something.
        log.debug("New manifest has no package changes, what should we do?")
        RemoveUpdate(directory)
        return True

    # Now we start doing the update!
    clone_name = None
    mount_point = None
    try:
        # We clone the existing boot environment to
        # "Avatar()-<sequence>", unless it already does.
        if new_manifest.Sequence().startswith(Avatar() + "-"):
            clone_name = new_manifest.Sequence()
        else:
            clone_name = "%s-%s" % (Avatar(), new_manifest.Sequence())
        if CreateClone(clone_name) is False:
            log.debug("Failed to create BE %s" % clone_name)
            # It's possible the boot environment already exists.
            s = None
            clones = ListClones()
            if clones:
                found = False
                for c in clones:
                    if c["name"] == clone_name:
                        found = True
                        if c["mountpoint"] != "/":
                            if c["mountpoint"] != "-":
                                mount_point = c["mountpoint"]
                                # We also need to see if grub is mounted
                                # Note:  if mount_point or mount_point/boot/grub don't exist,
                                # this is going to throw an exception.
                                try:
                                    if os.lstat(
                                            mount_point).st_dev == os.lstat(
                                                mount_point + grub_dir).st_dev:
                                        if RunCommand("/sbin/mount", [
                                                "-t", "nullfs", grub_dir,
                                                mount_point + grub_dir
                                        ]) == False:
                                            s = "Unable to mount grub into already-existing boot environment %s" % clone_name
                                except:
                                    log.debug("Unable to check %s grub mount" %
                                              mount_point)
                                    s = "Unable to set up %s as an installable mount point" % mount_point
                        else:
                            s = "Cannot create boot-environment with same name as current boot-environment (%s)" % clone_name
                        break
                if found is False:
                    s = "Unable to create boot-environment %s" % clone_name
            else:
                log.debug("Unable to list clones after creation failure")
                s = "Unable to create boot-environment %s" % clone_name
            if s:
                log.error(s)
                raise Exception(s)
        if mount_point is None:
            mount_point = MountClone(clone_name)
        if mount_point is None:
            s = "Unable to mount boot-environment %s" % clone_name
            log.error(s)
            DeleteClone(clone_name)
            raise Exception(s)

        # Remove any deleted packages
        for pkg in deleted_packages:
            log.debug("About to delete package %s" % pkg.Name())
            if conf.PackageDB(mount_point).RemovePackageContents(pkg) == False:
                s = "Unable to remove contents for packate %s" % pkg.Name()
                if mount_point:
                    UnmountClone(clone_name, mount_point)
                    mount_point = None
                    DeleteClone(clone_name)
                raise Exception(s)
            conf.PackageDB(mount_point).RemovePackage(pkg.Name())

        installer = Installer.Installer(manifest=new_manifest,
                                        root=mount_point,
                                        config=conf)
        installer.GetPackages(pkgList=updated_packages)
        log.debug("Installer got packages %s" % installer._packages)
        # Now to start installing them
        rv = False
        if installer.InstallPackages(handler=install_handler) is False:
            log.error("Unable to install packages")
            raise Exception("Unable to install packages")
        else:
            new_manifest.Save(mount_point)
            if mount_point:
                if UnmountClone(clone_name, mount_point) is False:
                    s = "Unable to unmount clone environment %s from mount point %s" % (
                        clone_name, mount_point)
                    log.error(s)
                    raise Exception(s)
                mount_point = None
                if ActivateClone(clone_name) is False:
                    s = "Unable to activate clone environment %s" % clone_name
                    log.error(s)
                    raise Exception(s)
                RemoveUpdate(directory)
                rv = True
                # RunCommand("/sbin/zpool", ["scrub", "freenas-boot"])
    except BaseException as e:
        log.error("Update got exception during update: %s" % str(e))
        if mount_point:
            UnmountClone(clone_name, mount_point)
        if clone_name:
            DeleteClone(clone_name)
        raise e

    return rv
Пример #8
0
def Update(root=None, conf=None, check_handler=None, get_handler=None,
           install_handler=None):
    """
    Perform an update.  Calls CheckForUpdates() first, to see if
    there are any. If there are, then magic happens.
    """

    deleted_packages = []
    process_packages = []
    def UpdateHandler(op, pkg, old):
        if op == "delete":
            deleted_packages.append(pkg)
        else:
            process_packages.append(pkg)
        if check_handler is not None:
            check_handler(op, pkg, old)

    new_man = CheckForUpdates(root, UpdateHandler)
    if new_man is None:
        return

    if len(deleted_packages) == 0 and len(process_packages) == 0:
        # We have a case where a manifest was updated, but we
        # don't actually have any changes to the packages.  We
        # should install the new manifest, and be done -- it
        # may have new release notes, or other issues.
        # Right now, I'm not quite sure how to do this.
        # I should also learn how to log from python.
        log.debug("Updated manifest but no package differences")
        return

    # Now we have a list of deleted packages, and a list
    # of update/install packages.
    # The task is to delete the first set of packages,
    # and then run through the others to install/update.
    # First, however, we need to get the files.
    # We want to use the system configuration, unless one has been
    # specified -- we don't want to use the target root's.
    if conf is None:  
        conf = Configuration.Configuration()

    # If root is None, then we will try to create a clone
    # environment.  (If the caller wants to install into the
    # current boot environment, set root = "" or "/".)
    if root is None:
        # We clone the existing boot environment to
        # "FreeNAS-<sequence>"
        clone_name = "FreeNAS-%s" % new_man.Sequence()
        if CreateClone(clone_name) is False:
            log.error("Unable to create boot-environment %s" % clone_name)
            raise Exception("Unable to create new boot-environment %s" % clone_name)

        mount_point = MountClone(clone_name)
        if mount_point is None:
            log.error("Unable to mount boot-environment %s" % clone_name)
            raise Exception("Unable to mount boot-environment %s" % clone_name)
        else:
            root = mount_point
    else:
        mount_point = None

    for pkg in deleted_packages:
        log.debug("Want to delete package %s" % pkg.Name())
        if conf.PackageDB(root).RemovePackageContents(pkg) == False:
            log.error("Unable to remove contents package %s" % pkg.Name())
            UnmountClone(clone_name, mount_point)
            DestroyClone(clone_name)
            raise Exception("Unable to remove contents for package %s" % pkg.Name())
        conf.PackageDB(root).RemovePackage(pkg.Name())

    installer = Installer.Installer(manifest = new_man, root = root, config = conf)
    installer.GetPackages(process_packages, handler=get_handler)

    log.debug("Packages = %s" % installer._packages)

    # Now let's actually install them.
    # Only change on success
    rv = False
    if installer.InstallPackages(handler=install_handler) is False:
        log.error("Unable to install packages")
    else:
        new_man.Save(root)
        if mount_point is not None:
            if UnmountClone(clone_name, mount_point) is False:
                log.error("Unable to mount clone enivironment %s" % clone_name)
            else:
                if ActivateClone(clone_name) is False:
                    log.error("Could not activate clone environment %s" % clone_name)
                else:
                    rv = True

    # Clean up
    # The package files are open-unlinked, so should be removed
    # automatically under *nix.  That just leaves the clone, which
    # we should unmount, and destroy if necessary.
    # Unmounting attempts to delete the mount point that was created.
    if rv is False:
        if DeleteClone(clone_name) is False:
            log.error("Unable to delete boot environment %s in failure case" % clone_name)
    
    return rv
Пример #9
0
def Install(**kwargs):
    """
    This does the grunt-work of actually doing the install.
    The possible arguments are:
    - config	Object containing configuration.  This is where the download URL and
            	package directories will be specified.
    - interactive	Whether or not to be interactive.  If true (default), then
		bsd.Dialog will be used to for status and error messages.
    - disks	An array of Disk objects to install to.  If set, then the disks
    		will be partitioned and erased.  If NOT set, then the installer
		will create a new boot environment on the existing freenas-boot pool.
    - efi	Boolean indicating whether or not to use EFI (default is False).
    - upgrade_from	An unimported ZFSPool object to install to.  This must be set
    			when upgrading, and when creating a new BE on an existing pool.
    - upgrade	Boolean indicating whether or not to upgrade.  Requires upgrade_from to
		be valid.
    - data_dir	A string indicating the location of the /data.  Normally this will just
		be "/data", but if installing from something other than the ISO, it will
		be necessary to specify it.
    - password	A string indicating the root password.  Ignored for upgrades; may be None
    		(indicating no password, not recommended).
    - partitions	An array of Partition objects (see Utils).  Note that the OS
    			partition will always be installed last.
    - post_install	An array of callable objects, which will be called after installation,
    			as func(mount_point=/path, **kwargs).  MUST BE AN ARRAY.
    - package_handler	Call-back for the start of each package.  Arguments are
			(index [int], name [string], packages [array of package names])
    			be installed.
    - progress_handler	Call-back after each file/directory is installed.  Arguments are **kwargs,
    			will [currently] be either done=True (indicating the package is installed),
    			or (total=int [number of objects], index=int [current index], name=string
    			[name of object that was just installed]).
    - manifest	A manifest object.  Must be set.
    - package_directory	A path where the package files are located.  The package files must
    			already be located in this directory.
    - trampoline	A boolean indicating whether the post-install scripts should be run
    			on reboot (True, default) or during the install (False).
    """
    LogIt("Install({})".format(kwargs))
    orig_kwargs = kwargs.copy()

    config = kwargs.get("config", Configuration.SystemConfiguration())
    interactive = kwargs.get("interactive", True)
    disks = kwargs.get("disks", [])
    efi = kwargs.get("efi", False)
    upgrade_pool = kwargs.get("upgrade_from", None)
    upgrade = kwargs.get("upgrade", False)
    data_dir = kwargs.get("data_dir", "/data")
    password = kwargs.get("password", None)
    extra_partitions = kwargs.get("partitions", [])
    post_install = kwargs.get("post_install", [])
    package_notifier = kwargs.get("package_handler", None)
    progress_notifier = kwargs.get("progress_handler", None)
    manifest = kwargs.get("manifest", None)
    trampoline = kwargs.get("trampoline", True)
    # The default is based on ISO layout
    package_dir = kwargs.get("package_directory", "/.mount/{}/Packages".format(Project()))

    if type(post_install) != list:
        post_install = [post_install]
        
    if not manifest:
        if interactive:
            try:
                Dialog.MessageBox(Title(),
                                  "No manifest specified for the installation",
                                  height=7, width=45).run()
            except:
                pass
        raise InstallationError("No manifest specified for the installation")
                    
    config.SetPackageDir(package_dir)
    
    mount_point = tempfile.mkdtemp()
    
    # Quick sanity check
    if upgrade and upgrade_pool is None:
        if interactive:
            Dialog.MessageBox(Title(), "\nNo pool to upgrade from",
                              height=7, width=30).run()
        raise InstallationError("Upgrade selected but not previous boot pool selected")

    if disks is None and upgrade_pool is None:
        if interactive:
            Dialog.MessageBox(Title(), "\nNo disks or previous pool selected",
                              height=10, width=30).run()
        raise InstallationError("No disks or previous boot pool selected")
    
    if IsTruenas():
        # We use a 16g swap partition in TrueNAS.
        # Note that this is only used if the disks are being formatted.
        extra_partitions.append(Partition(type="swap", index="3", size=16*1024*1024*1024))
        def make_tn_swap(mount_point=None, **kwargs):
            # This uses the previously-defined variables, not kwargs
            if disks and mount_point:
                try:
                    RunCommand("/sbin/gmirror", "label", "-b", "prefer",
                               ["{}p3".format(disk.name) for disk in disks])
                    with open(os.path.join(mount_point, "data/fstab.swap"), "w") as swaptab:
                        print("/dev/mirror/swap.eli\tnone\tswap\tsw\t0\t0", file=swaptab)
                except RunCommandException as e:
                    LogIt("Could not create mirrored swap: {}".format(str(e)))
        post_install.append(make_tn_swap)
    # First step is to see if we're upgrading.
    # If so, we want to copy files from the active BE to
    # a location in /tmp, so we can copy them back later.
    # This will import, and then export, the freenas-boot pool.
    
    if upgrade_pool and upgrade:
        upgrade_dir = SaveConfiguration(interactive=interactive,
                                        pool=upgrade_pool)
    else:
        upgrade_dir = None

    # Second step is to see if we're formatting drives.
    # If so, we first will destroy the freenas-boot pool;
    # after that, we will partition the drives.  How we partition
    # depends on the boot method -- efi or bios.  We set the
    # BE name to "default" and create the freenas-boot pool, and
    # then the grub dataset.
    #
    # If we're NOT formatting the drive, we set the pool name
    # to time.strftime("default-%Y%m%d-%H%M%S")
    
    LogIt("disks = {}".format(disks))
    if disks:
        # This means we're formatting
        # We need to know what size and types to make the partitions.
        # If we're using EFI, then we need a 100mbyte msdosfs partition;
        # otherwise a 512k bios-boot.  If we have any extra partitions,
        # we'll take those into account as well.  For the big freebsd-zfs
        # partition, we'll take the minimum of the remaining space,
        # rounded down to the nearest gbyte.
        gByte = 1024 * 1024 * 1024
        if efi:
            # 100mbytes for efi partition
            used = 100 * 1024 * 1024
            boot_part = Partition(type="efi",
                                  index=1,
                                  size=used)
        else:
            # BIOS partition gets 512kbytes
            used = 512 * 1024
            boot_part = Partition(type="bios-boot",
                                  index=1,
                                  size=used)
        partitions = [boot_part]

        # For now, we always make the freenas-boot partition index 2, and place
        # it at the end of the disk.
        next_index = 3
        for part in (extra_partitions or []):
            # We will ignore the index given here.
            part.index = next_index
            used += part.size
            LogIt("Additional partition {}".format(part))
            partitions.append(part)
            next_index += 1

        # At this point, used is the sum of the partitions, in bytes.
        # This isn't really correct - we should be rounding the size up
        # to the blocksize of the disk.  But partitioning behaves strangely
        # sometimes with flash drives.  As a result, when we do the actual
        # partitioning, we use the smart-size (e.g., 1G), which rounds down.
        
        min_size = 0
        for disk in disks:
            # If the remaining space is too small, this installation won't work well.
            size = disk.size
            size = size - used
            if size < gByte:
                if size < 0:
                    fspace = "no free space after the other partitions"
                else:
                    fspace = "free space is {}, minimum is 1Gbyte".format(SmartSize(size))
                name = disk.name
                LogIt("Disk {} is too small {}".format(name, fspace))
                ssize = SmartSize(disk.size)
                if interactive:
                    Dialog.MessageBox(Title(),
                                      "Disk {} is too small ({})".format(name, ssize),
                                      height=10, width=25).run()
                raise InstallationException("Disk {} is too small ({})".format(name, ssize))
            if (size < min_size) or (not min_size):
                min_size = size
        if min_size == 0:
            if interactive:
                Dialog.MessageBox(Title(),
                                  "Unable to find the size of any of the selected disks",
                                  height=15, weidth=60).run()
            raise InstallationError("Unable to find disk size")
        
        # Round min_size down to a gbyte
        part_size = int(min_size / gByte) * gByte
        os_part = Partition(type="freebsd-zfs",
                            index=2,
                            size=part_size,
                            os=True)
        LogIt("OS partition {}".format(os_part))
        partitions.append(os_part)
                
        # We need to destroy any existing freenas-boot pool.
        # To do that, we may first need to import the pool.
        if upgrade_pool is None:
            try:
                old_pools = list(zfs.find_import(name="freenas-boot"))
            except libzfs.ZFSException as e:
                LogIt("Got ZFS error {} while trying to import freenas-boot for destruction".format(str(e)))
                old_pools = []
        else:
            old_pools = [upgrade_pool]
            # We'll be destroying it, so..
            upgrade_pool = None

        for pool in old_pools:
            try:
                dead_pool = zfs.import_pool(pool, "freenas-boot", {})
                if dead_pool is None:
                    dead_pool = zfs.get("freenas-boot")
                zfs.destroy("freenas-boot")
            except libzfs.ZFSException as e:
                LogIt("Trying to destroy a freenas-boot pool got error {}".format(str(e)))
            
        try:
            freenas_boot = FormatDisks(disks, partitions, interactive)
        except BaseException as e:
            LogIt("FormatDisks got exception {}".format(str(e)))
            raise
        
        bename = "freenas-boot/ROOT/default"
    else:
        # We need to import the pool (we exported it above if upgrade_pool)
        try:
            if upgrade_pool:
                freenas_boot = zfs.import_pool(upgrade_pool, "freenas-boot", {})
            else:
                freenas_boot = None
                pools = list(zfs.find_import(name="freenas-boot"))
                if len(pools) > 1:
                    raise InstallationError("There are multiple unimported freenas-boot pools")
                if len(pools) == 1:
                    freenas_boot = zfs.import_pool(upgrade_pool, "freenas-boot", {})
            if freenas_boot is None:
                freenas_boot = zfs.get("freenas-boot")
        except libzfs.ZFSException as e:
            LogIt("Got ZFS error {} while trying to import pool".format(str(e)))
            if interactive:
                Dialog.MessageBox("Error importing boot pool",
                                  "The {} Installer was unable to import the boot pool:\n\n\t{}".format(Project(), str(e)),
                                  height=25, width=60).run()
            raise InstallationError("Unable to import boot pool")

        bename = time.strftime("freenas-boot/ROOT/default-%Y%m%d-%H%M%S")
        
    # Next, we create the dataset, and mount it, and then mount
    # the grub dataset.
    # We also mount a devfs and tmpfs in the new environment.

    LogIt("BE name is {}".format(bename))
    try:
        freenas_boot.create(bename, fsopts={
            "mountpoint" : "legacy",
            "sync"       : "disabled",
        })
    except libzfs.ZFSException as e:
        LogIt("Could not create BE {}: {}".format(bename, str(e)))
        if interactive:
            Dialog.MessageBox(Title(),
                              "An error occurred creatint the installation boot environment\n" +
                              "\n\t{}".format(str(e)),
                              height=25, width=60).run()
        raise InstallationError("Could not create BE {}: {}".format(bename, str(e)))
    
    MountFilesystems(bename, mount_point)
    # After this, any exceptions need to have the filesystems unmounted
    try:
        # If upgrading, copy the stashed files back
        if upgrade_dir:
            RestoreConfiguration(save_path=upgrade_dir,
                                 interactive=interactive,
                                 destination=mount_point)
        else:
            if os.path.exists(data_dir):
                try:
                    copytree(data_dir, "{}/data".format(mount_point),
                             progress_callback=lambda src, dst: LogIt("Copying {} -> {}".format(src, dst)))
                except:
                    pass
            # 
            # We should also handle some FN9 stuff
            # In this case, we want the newer database file, for migration purposes
            # XXX -- this is a problem when installing from FreeBSD
            for dbfile in ["freenas-v1.db", "factory-v1.db"]:
                if os.path.exists("/data/{}".format(dbfile)):
                    copytree("/data/{}".format(dbfile), "{}/data/{}".format(mount_point, dbfile))

        # After that, we do the installlation.
        # This involves mounting the new BE,
        # and then running the install code on it.

        installer = Installer.Installer(manifest=manifest,
                                        root=mount_point,
                                        config=config)

        if installer.GetPackages() is not True:
            LogIt("Installer.GetPackages() failed")
            raise InstallationError("Unable to load packages")
        
        # This should only be true for the ISO installer.
        installer.trampoline = trampoline
        
        start_time = time.time()
        try:
            installer.InstallPackages(progressFunc=progress_notifier,
                                      handler=package_notifier)
        except BaseException as e:
            LogIt("InstallPackaages got exception {}".format(str(e)))
            raise InstallationError("Could not install packages")
        # Packages installed!
        if interactive:
            try:
                status = Dialog.MessageBox(Title(), "Preparing new boot environment",
                                           height=5, width=35, wait=False)
                status.clear()
                status.run()
            except:
                pass
        for f in ["{}/conf/default/etc/fstab".format(mount_point),
                  "{}/conf/base/etc/fstab".format(mount_point)
                  ]:
            try:
                os.remove(f)
            except:
                LogIt("Unable to remove {} -- ignoring".format(f))
                    
        try:
            with open("{}/etc/fstab".format(mount_point), "w") as fstab:
                print("freenas-boot/grub\t/boot/grub\tzfs\trw,noatime\t1\t0", file=fstab)
        except OSError as e:
            LogIt("Unable to create fstab: {}".format(str(e)))
            raise InstallationError("Unable to create filesystem table")
        try:
            os.link("{}/etc/fstab".format(mount_point),
                    "{}/conf/base/etc/fstab".format(mount_point))
        except OSError as e:
            LogIt("Unable to link /etc/fstab to /conf/base/etc/fstab: {}".format(str(e)))
            
        # Here, I should change module_path in boot/loader.conf, and get rid of the kernel line
        try:
            lines = []
            boot_config = "{}/boot/loader.conf".format(mount_point)
            with open(boot_config, "r") as bootfile:
                for line in bootfile:
                    line = line.rstrip()
                    if line.startswith("module_path="):
                        lines.append('module_path="/boot/kernel;/boot/modules;/usr/local/modules"')
                    elif line.startswith("kernel="):
                        lines.append('kernel="kernel"')
                    else:
                        lines.append(line)
            with open(boot_config, "w") as bootfile:
                for line in lines:
                    print(line, file=bootfile)
        except BaseException as e:
            LogIt("While modifying loader.conf, got exception {}".format(str(e)))
            # Otherwise I'll ignore it, I think
                        
        # This is to support Xen
        try:
            hvm = RunCommand("/usr/local/sbin/dmidecode", "-s", "system-product-name",
                             chroot=mount_point)
            if hvm == "HVM domU":
                with open(os.path.join(mount_point, "boot", "loader.conf.local"), "a") as f:
                    print('hint.hpet.0.clock="0"', file=f)
        except BaseException as e:
            LogIt("Got an exception trying to set XEN boot loader hint: {}".format(str(e)))
            
        # Now I have to mount a tmpfs on var
        try:
            LogIt("Mounting tmpfs on var")
            bsd.nmount(source="tmpfs",
                       fspath=os.path.join(mount_point, "var"),
                       fstype="tmpfs")
        except BaseException as e:
            LogIt("Got exception {} while trying to mount {}/var: {}".format(mount_point, str(e)))
            raise InstallationError("Unable to mount temporary space in newly-created BE")
        # Now we need to populate a data structure
        mtree_command = ["/usr/sbin/mtree", "-deUf" ]
        if os.path.exists("/usr/sbin/mtree"):
            mtree_command.append("{}/etc/mtree/BSD.var.dist".format(mount_point))
            mtree_command.extend(["-p", "{}/var".format(mount_point)])
            chroot=None
        else:
            mtree_command.extend(["/etc/mtree/BSD.var.dist", "-p", "/var"])
            chroot=mount_point

        try:
            RunCommand(*mtree_command,
                       chroot=chroot)
        except RunCommandException as e:
            LogIt("{} (chroot={}) failed: {}".format(mtree_command, chroot, str(e)))
            raise InstallationError("Unable to prepare new boot environment")

        try:
            # Now we need to install grub
            # We do this even if we didn't format the disks.
            # But if we didn't format the disks, we need to use the same type
            # of boot loader.
            if interactive:
                try:
                    status = Dialog.MessageBox(Title(), "Installing boot loader",
                                               height=5, width=35, wait=False)
                    status.clear()
                    status.run()
                except:
                    pass
            # We've just repartitioned, so rescan geom
            geom.scan()
            # Set the boot dataset
            freenas_boot.properties["bootfs"].value = bename
            LogIt("Set bootfs to {}".format(bename))
            # This is EXTREMELY ANNOYING.
            # I'd like to use libzfs to set the property here, but
            # I have to do it in the chrooted environment, because otherwise
            # zfs returns an error and doesn't set it.
            #freenas_boot.properties["cachefile"].value = "/boot/zfs/rpool.cache"
            try:
                RunCommand("/sbin/zpool",
                           "set", "cachefile=/boot/zfs/rpool.cache",
                           "freenas-boot",
                           chroot=mount_point)
            except RunCommandException as e:
                LogIt("Got exception {} while trying to set cachefile".format(str(e)))
                raise InstallationException("Could not set cachefile on boot pool")
            LogIt("Set cachefile to /boot/zfs/rpool.cache")
            # We need to set the serial port stuff in the database before running grub,
            # because it'll use that in the configuration file it generates.
            try:
                SaveSerialSettings(mount_point)
            except:
                raise InstallationError("Could not save serial console settings")

            try:
                # All boot pool disks are partitioned using the same type.
                # Or the darkness rises and squit once again rule the earth.
                # (It's happened.)
                use_efi = Utils.BootPartitionType(freenas_boot.disks[0]) == "efi"
                InstallGrub(chroot=mount_point,
                            disks=freenas_boot.disks,
                            bename=bename, efi=use_efi)
            except RunCommandException as e:
                LogIt("Command {} failed: {} (code {})".format(e.command, e.message, e.code))
                raise InstallationError("Boot loader installation failure")
            except BaseException as e:
                LogIt("InstallGrub got exception {}".format(str(e)))
                raise
    
            if interactive:
                try:
                    status = Dialog.MessageBox(Title(), "Finalizing installation",
                                               height=5, width=35, wait=False)
                    status.clear()
                    status.run()
                except BaseException as e:
                    LogIt("Finalizing got exception {}".format(str(e)))
                    
            # This is FN9 specific
            with open("{}/data/first-boot".format(mount_point), "wb"):
                pass
            if upgrade:
                for sentinel in ["/data/cd-upgrade", "/data/need-update"]:
                    with open(mount_point + sentinel, "wb") as f:
                        pass
            elif password is not None:
                if interactive:
                    try:
                        status = Dialog.MessageBox(Title(), "\nSetting root password",
                                                   height=7, width=35, wait=False)
                        status.clear()
                        status.run()
                    except:
                        pass
                try:
                    RunCommand("/etc/netcli", "reset_root_pw", password,
                               chroot=mount_point)
                except RunCommandException as e:
                    LogIt("Setting root password: {}".format(str(e)))
                    raise InstallationError("Unable to set root password")
        except BaseException as e:
            LogIt("Got exception {} during configuration".format(str(e)))
            if interactive:
                try:
                    Dialog.MessageBox(Title(),
                                      "Error during configuration",
                                      height=7, width=35).run()
                except:
                    pass
            raise

        # Let's turn sync back to default for the dataset
        try:
            ds = zfs.get_dataset(bename)
        except libzfs.ZFSException as e:
            LogIt("Got ZFS error {} while trying to get {} dataset".format(str(e), bename))
            raise InstallationError("Could not fid newly-created BE {}".format(bename))
        try:
            ds.properties["sync"].inherit()
        except BaseException as e:
            LogIt("Unable to set sync on {} to inherit: {}".format(bename, str(e)))
            # That's all I'm going to do for now

        # We save the manifest
        manifest.Save(mount_point)

        # Okay!  Now if there are any post-install functions, we call them
        for fp in post_install:
            fp(mount_point=mount_point, **kwargs)

        # And we're done!
        end_time = time.time()
    except InstallationError as e:
        # This is the outer try block -- it needs to ensure mountpoints are
        # cleaned up
        LogIt("Outer block got error {}".format(str(e)))
        if interactive:
            try:
                Dialog.MessageBox("{} Installation Error".format(Project()),
                                  e.message,
                                  height=25, width=50).run()
            except:
                pass
        raise
    except BaseException as e:
        LogIt("Outer block got base exception {}".format(str(e)))
        raise
    finally:
        if package_dir is None:
            LogIt("Removing downloaded packages directory {}".format(cache_dir))
            shutil.rmtree(cache_dir, ignore_errors=True)
        UnmountFilesystems(mount_point)

    LogIt("Exporting freenas-boot at end of installation")
    try:
        zfs.export_pool(freenas_boot)
    except libzfs.ZFSException as e:
        LogIt("Could not export freenas boot: {}".format(str(e)))
        raise

    if interactive:
        total_time = int(end_time - start_time)
        Dialog.MessageBox(Title(),
                          "The {} installer has finished the installation in {} seconds".format(Project(), total_time),
                          height=8, width=40).run()