7004610 Update Text Installer to use CUD
7039676 TransferCPIO checkpoint sometimes won't calculate install size correctly
7043391 Wrong definition for VFSTAB_FILE variable in target controller
--- a/usr/src/Makefile.master Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/Makefile.master Wed May 11 15:30:11 2011 -0700
@@ -98,7 +98,6 @@
ROOTPYTHONVENDORINSTALL= $(ROOTPYTHONVENDOR)/osol_install
ROOTPYTHONVENDORINSTALLAI= $(ROOTPYTHONVENDORINSTALL)/auto_install
ROOTPYTHONVENDORINSTALLPROF= $(ROOTPYTHONVENDORINSTALL)/profile
-ROOTPYTHONVENDORINSTALLTI= $(ROOTPYTHONVENDORINSTALL)/text_install
ROOTPYTHONVENDORSOLINSTALL= $(ROOTPYTHONVENDOR)/solaris_install
ROOTPYTHONVENDORSOLINSTALLBOOT = $(ROOTPYTHONVENDORSOLINSTALL)/boot
ROOTPYTHONVENDORSOLINSTALLDATACACHE= $(ROOTPYTHONVENDORSOLINSTALL)/data_object
@@ -125,6 +124,7 @@
ROOTPYTHONVENDORSOLINSTALLTARGETLIBDISKMGT = $(ROOTPYTHONVENDORSOLINSTALLTARGET)/libdiskmgt
ROOTPYTHONVENDORSOLINSTALLTARGETLIBNVPAIR = $(ROOTPYTHONVENDORSOLINSTALLTARGET)/libnvpair
ROOTPYTHONVENDORSOLINSTALLTARGETSHADOW = $(ROOTPYTHONVENDORSOLINSTALLTARGET)/shadow
+ROOTPYTHONVENDORSOLINSTALLTI= $(ROOTPYTHONVENDORSOLINSTALL)/text_install
ROOTPYTHONVENDORINSTALLTRANSFER = $(ROOTPYTHONVENDORSOLINSTALL)/transfer
ROOTPYTHONVENDORBOOTMGMT= $(ROOTPYTHONVENDOR)/bootmgmt
ROOTPYTHONVENDORBOOTMGMTBKND= $(ROOTPYTHONVENDORBOOTMGMT)/backend
--- a/usr/src/Targetdirs Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/Targetdirs Wed May 11 15:30:11 2011 -0700
@@ -65,7 +65,6 @@
/usr/lib/python2.6/vendor-packages/osol_install \
/usr/lib/python2.6/vendor-packages/osol_install/auto_install \
/usr/lib/python2.6/vendor-packages/osol_install/profile \
- /usr/lib/python2.6/vendor-packages/osol_install/text_install \
/usr/lib/python2.6/vendor-packages/solaris_install/auto_install \
/usr/lib/python2.6/vendor-packages/solaris_install/auto_install/checkpoints \
/usr/lib/python2.6/vendor-packages/solaris_install/distro_const \
@@ -89,6 +88,7 @@
/usr/lib/python2.6/vendor-packages/solaris_install/target/libdiskmgt \
/usr/lib/python2.6/vendor-packages/solaris_install/target/libnvpair \
/usr/lib/python2.6/vendor-packages/solaris_install/target/shadow \
+ /usr/lib/python2.6/vendor-packages/solaris_install/text_install \
/usr/lib/python2.6/vendor-packages/solaris_install/transfer \
/usr/lib/python2.6/vendor-packages/terminalui \
/usr/sbin \
--- a/usr/src/cmd/Makefile.targ Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/cmd/Makefile.targ Wed May 11 15:30:11 2011 -0700
@@ -92,7 +92,7 @@
$(ROOTPYTHONVENDORINSTALLPROF):
$(INS.dir)
-$(ROOTPYTHONVENDORINSTALLTI):
+$(ROOTPYTHONVENDORSOLINSTALLTI):
$(INS.dir)
$(ROOTPYTHONVENDORSOLINSTALLJS2AI):
@@ -126,7 +126,7 @@
$(ROOTPYTHONVENDORINSTALLAI)/%: %
$(CP_P.file)
-$(ROOTPYTHONVENDORINSTALLTI)/%: %
+$(ROOTPYTHONVENDORSOLINSTALLTI)/%: %
$(CP_P.file)
$(ROOTPYTHONVENDORSCI)/%: %
--- a/usr/src/cmd/distro_const/checkpoints/Makefile Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/cmd/distro_const/checkpoints/Makefile Wed May 11 15:30:11 2011 -0700
@@ -30,7 +30,7 @@
clobber:= TARGET= clobber
install:= TARGET= install
-SUBDIRS= defaultfiles
+SUBDIRS= defaultfiles xslt
PYMODULES= __init__.py \
ai_publish_pkg.py \
--- a/usr/src/cmd/distro_const/checkpoints/boot_archive_configure.py Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/cmd/distro_const/checkpoints/boot_archive_configure.py Wed May 11 15:30:11 2011 -0700
@@ -34,6 +34,10 @@
from pkg.cfgfiles import UserattrFile
from solaris_install.data_object.data_dict import DataObjectDict
+from solaris_install.transfer.info import Software, Source, Destination, \
+ CPIOSpec, Dir
+from solaris_install.transfer.media_transfer import TRANSFER_ROOT, \
+ TRANSFER_MISC, INSTALL_TARGET_VAR
from solaris_install.distro_const import DC_LABEL
from solaris_install.engine import InstallEngine
from solaris_install.engine.checkpoint import AbstractCheckpoint as Checkpoint
@@ -164,6 +168,16 @@
if not os.path.islink("opt"):
os.symlink("mnt/misc/opt", "opt")
+ tr_uninstall = CPIOSpec()
+ tr_uninstall.action = CPIOSpec.UNINSTALL
+ tr_uninstall.contents = ["opt"]
+
+ root_tr_software_node = self.doc.persistent.get_descendants(\
+ name=TRANSFER_ROOT, class_type=Software,
+ not_found_is_err=True)[0]
+
+ root_tr_software_node.insert_children(tr_uninstall)
+
# copy the SMF repository from pkg_image_path to ba_build
pkg_img_path_repo = os.path.join(self.pkg_img_path,
"etc/svc/repository.db")
@@ -235,6 +249,7 @@
# change to the pkg_img_path directory
os.chdir(self.pkg_img_path)
+ misc_symlinks = [] #keep track of all the symlinks created
for rootdir in ["etc", "var"]:
for root, dirs, files in os.walk(rootdir):
for f in files:
@@ -261,6 +276,42 @@
os.chdir(cwd)
+ misc_symlinks.append(os.path.join(root, f))
+
+ tr_uninstall = CPIOSpec()
+ tr_uninstall.action = CPIOSpec.UNINSTALL
+ tr_uninstall.contents = misc_symlinks
+
+ # Add that into the software transfer list. The list of files
+ # to uninstall MUST go before the contents to be installed
+ # from /mnt/misc
+ root_tr_software_node = self.doc.persistent.get_descendants( \
+ name=TRANSFER_ROOT, class_type=Software, not_found_is_err=True)[0]
+
+ root_tr_software_node.insert_children(tr_uninstall)
+
+ #add Software node to install items from /mnt/misc
+
+ src_path = Dir("/mnt/misc")
+ src = Source()
+ src.insert_children(src_path)
+
+ dst_path = Dir(INSTALL_TARGET_VAR)
+ dst = Destination()
+ dst.insert_children(dst_path)
+
+ tr_install_misc = CPIOSpec()
+ tr_install_misc.action = CPIOSpec.INSTALL
+ tr_install_misc.contents = ["."]
+ # must use the following cpio option instead of the default "-pdum"
+ tr_install_misc.cpio_args = "-pdm"
+
+ misc_software_node = Software(TRANSFER_MISC, type="CPIO")
+ misc_software_node.insert_children([src, dst, tr_install_misc])
+ self.doc.persistent.insert_children(misc_software_node)
+
+ self.logger.debug(str(self.doc.persistent))
+
def parse_doc(self):
""" class method for parsing data object cache (DOC) objects for use by
the checkpoint.
@@ -275,6 +326,40 @@
except KeyError:
raise RuntimeError("Error retrieving a value from the DOC")
+ def add_root_transfer_to_doc(self):
+ """ Adds the list of files of directories to be transferred
+ to the DOC
+ """
+ if self.doc is None:
+ self.doc = InstallEngine.get_instance().data_object_cache
+
+ src_path = Dir("/")
+ src = Source()
+ src.insert_children(src_path)
+
+ dst_path = Dir(INSTALL_TARGET_VAR)
+ dst = Destination()
+ dst.insert_children(dst_path)
+
+ dot_node = CPIOSpec()
+ dot_node.action = CPIOSpec.INSTALL
+ dot_node.contents = ["."]
+
+ usr_node = CPIOSpec()
+ usr_node.action = CPIOSpec.INSTALL
+ usr_node.contents = ["usr"]
+
+ dev_node = CPIOSpec()
+ dev_node.action = CPIOSpec.INSTALL
+ dev_node.contents = ["dev"]
+
+ software_node = Software(TRANSFER_ROOT, type="CPIO")
+ software_node.insert_children([src, dst, dot_node, usr_node, dev_node])
+
+ self.doc.persistent.insert_children(software_node)
+
+ self.logger.debug(str(self.doc.persistent))
+
def execute(self, dry_run=False):
""" Primary execution method used by the Checkpoint parent class.
dry_run is not used in DC
@@ -284,6 +369,8 @@
self.parse_doc()
+ self.add_root_transfer_to_doc()
+
# configure various boot archive files
self.configure_system()
@@ -314,6 +401,8 @@
self.parse_doc()
+ self.add_root_transfer_to_doc()
+
# configure various boot archive files
self.configure_system()
@@ -420,6 +509,8 @@
self.parse_doc()
+ self.add_root_transfer_to_doc()
+
# configure various boot archive files
self.configure_system()
--- a/usr/src/cmd/distro_const/checkpoints/pkg_img_mod.py Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/cmd/distro_const/checkpoints/pkg_img_mod.py Wed May 11 15:30:11 2011 -0700
@@ -39,6 +39,11 @@
from solaris_install.distro_const import DC_LABEL
from solaris_install.engine import InstallEngine
from solaris_install.engine.checkpoint import AbstractCheckpoint as Checkpoint
+from solaris_install.transfer.info import Software, Source, Destination, \
+ CPIOSpec, Dir
+from solaris_install.transfer.media_transfer import TRANSFER_MEDIA, \
+ INSTALL_TARGET_VAR, MEDIA_DIR_VAR, TRANSFER_MANIFEST_NAME
+from solaris_install.manifest.writer import ManifestWriter
# load a table of common unix cli calls
import solaris_install.distro_const.cli as cli
@@ -258,8 +263,36 @@
shutil.rmtree(os.path.join(self.pkg_img_path, "usr"),
ignore_errors=True)
- def create_livecd_content_file(self):
- """ class method to create the .livecd-cdrom-content file
+ def add_content_list_to_doc(self, content_list):
+ src_path = Dir(MEDIA_DIR_VAR)
+ src = Source()
+ src.insert_children(src_path)
+
+ dst_path = Dir(INSTALL_TARGET_VAR)
+ dst = Destination()
+ dst.insert_children(dst_path)
+
+ media_install = CPIOSpec()
+ media_install.action = CPIOSpec.INSTALL
+ media_install.contents = content_list
+
+ media_soft_node = Software(TRANSFER_MEDIA, type="CPIO")
+ media_soft_node.insert_children([src, dst, media_install])
+
+ # Add that into the software transfer list.
+ self.doc.persistent.insert_children(media_soft_node)
+
+ # call manifest writer to write out the content of
+ # the transfer manifest
+ manifest_out = os.path.join(self.pkg_img_path, TRANSFER_MANIFEST_NAME)
+ xslt_name = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "xslt", "doc2_media_transfer.xslt")
+ manifest_writer = ManifestWriter("manifest-writer",
+ manifest_out, xslt_file=xslt_name)
+ manifest_writer.write(self.doc)
+
+ def populate_livecd_content(self):
+ """ class method to populate content of live media's root into DOC
"""
# save the current working directory
cwd = os.getcwd()
@@ -272,22 +305,20 @@
for f in files:
if not f.endswith(".zlib") and not f.endswith(".image_info") \
and not f.endswith("boot_archive") and not \
- f.endswith(".livecd-cdrom-content"):
+ f.endswith(".media-transfer.xml"):
content_list.append(os.path.join(root, f))
for d in dirs:
content_list.append(os.path.join(root, d))
- with open(".livecd-cdrom-content", "w") as fh:
- for entry in content_list:
- fh.write(entry + "\n")
+ self.add_content_list_to_doc(content_list)
os.chdir(cwd)
-
- def create_save_list(self):
+
+ def populate_save_list(self):
'''Store a list of files under the 'save' directory. Net-booted
text installer uses this list to determine what files it needs from
the boot server
-
+
'''
save_files = []
save_dir = os.path.join(self.pkg_img_path, "save")
@@ -297,11 +328,8 @@
start=self.pkg_img_path)
save_files.append(relpath)
- save_list = os.path.join(self.pkg_img_path, "save_list")
- with open(save_list, "w") as save_fh:
- for entry in save_files:
- save_fh.write(entry + "\n")
-
+ self.add_content_list_to_doc(save_files)
+
def execute(self, dry_run=False):
"""Customize the pkg_image area. Assumes that a populated pkg_image
area exists and that the boot_archive has been built
@@ -319,9 +347,7 @@
# create the /mnt/misc archive
self.create_misc_archive()
-
- self.create_save_list()
-
+
class LiveCDPkgImgMod(PkgImgMod, Checkpoint):
""" LiveCDPkgImgMod - class to modify the pkg_image directory after the
@@ -380,8 +406,8 @@
# strip the /platform directory
self.strip_platform()
- # create the .livecd-cdrom-content file
- self.create_livecd_content_file()
+ # populate live cd's content into DOC
+ self.populate_livecd_content()
class TextPkgImgMod(PkgImgMod, Checkpoint):
@@ -422,23 +448,56 @@
self.strip_x86_platform()
else:
self.strip_sparc_platform()
-
- # create the .livecd-cdrom-content file
- self.create_livecd_content_file()
+
+ # populate live cd's content into DOC
+ self.populate_livecd_content()
finally:
# return to the initial directory
os.chdir(cwd)
- self.create_save_list()
-
-# Currently, no difference between AIPkgImgMod and TextPkgImgMod.
-# Defined as an empty subclass here so that manifests can
-# reference AIPkgImgMod now, and if the classes diverge,
-# old manifests won't need updating
-
class AIPkgImgMod(TextPkgImgMod):
""" AIPkgImgMod - class to modify the pkg_image directory after the boot
archive is built for AI distributions
"""
- pass
+
+ DEFAULT_ARG = {"compression_type": "gzip"}
+
+ def __init__(self, name, arg=DEFAULT_ARG):
+ super(AIPkgImgMod, self).__init__(name, arg)
+
+ def execute(self, dry_run=False):
+ """ Customize the pkg_image area. Assumes that a populated pkg_image
+ area exists and that the boot_archive has been built
+ """
+ self.logger.info("=== Executing Pkg Image Modification Checkpoint ===")
+
+ self.parse_doc()
+
+ # clean up the root of the package image path
+ self.strip_root()
+
+ # create the /usr archive
+ self.create_usr_archive()
+
+ # create the /mnt/misc archive
+ self.create_misc_archive()
+
+ # get the platform of the system
+ arch = platform.processor()
+
+ # save the current working directory
+ cwd = os.getcwd()
+ try:
+ # clean up the package image path based on the platform
+ if arch == "i386":
+ self.strip_x86_platform()
+ else:
+ self.strip_sparc_platform()
+
+ # populate the value from the save directory into the DOC
+ self.populate_save_list()
+
+ finally:
+ # return to the initial directory
+ os.chdir(cwd)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/distro_const/checkpoints/xslt/Makefile Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,53 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+
+include ../../../Makefile.cmd
+
+all:= TARGET= all
+clean:= TARGET= clean
+clobber:= TARGET= clobber
+install:= TARGET= install
+
+XSLT_FILES= doc2_media_transfer.xslt
+
+ROOTXSLT_DIR = $(ROOTPYTHONVENDORINSTALLDCCHKPT)/xslt
+ROOTXSLT_FILES= $(XSLT_FILES:%=$(ROOTXSLT_DIR)/%)
+
+all:
+
+clean:
+
+clobber: clean
+
+$(ROOTXSLT_DIR):
+ $(INS.dir)
+
+$(ROOTXSLT_DIR)/%: %
+ $(INS.file)
+
+install: $(ROOTXSLT_DIR) .WAIT $(ROOTXSLT_FILES)
+
+include ../../../Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/distro_const/checkpoints/xslt/doc2_media_transfer.xslt Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ CDDL HEADER START
+
+ The contents of this file are subject to the terms of the
+ Common Development and Distribution License (the "License").
+ You may not use this file except in compliance with the License.
+
+ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ or http://www.opensolaris.org/os/licensing.
+ See the License for the specific language governing permissions
+ and limitations under the License.
+
+ When distributing Covered Code, include this CDDL HEADER in each
+ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ If applicable, add the following below this CDDL HEADER, with the
+ fields enclosed by brackets "[]" replaced with your own identifying
+ information: Portions Copyright [yyyy] [name of copyright owner]
+
+ CDDL HEADER END
+
+ Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+-->
+
+<!-- Extract the Software nodes we constructed from the varies -->
+<!-- checkpoints into a XML file to be stored in the image. -->
+<!-- The software node names we specified here must match the ones -->
+<!-- used during the image construction process -->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:output method="xml" indent="yes" encoding="UTF-8" doctype-system="file:///usr/share/install/media-transfer.dtd"/>
+
+ <xsl:template match="/">
+ <media_transfer>
+ <xsl:copy-of select="//software[@name='transfer-root']"/>
+ <xsl:copy-of select="//software[@name='transfer-misc']"/>
+ <xsl:copy-of select="//software[@name='transfer-media']"/>
+ </media_transfer>
+ </xsl:template>
+</xsl:stylesheet>
--- a/usr/src/cmd/slim-install/svc/net-assembly Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/cmd/slim-install/svc/net-assembly Wed May 11 15:30:11 2011 -0700
@@ -60,10 +60,6 @@
# Image Info file (needed for x86 AI, and both for Text Installs)
IMAGE_INFO=".image_info"
-# Save folder information (needed for Text Installs)
-SAVE="save"
-SAVE_LIST="save_list"
-
# Sparc installation configuration
INSTALL_CONF_FILE="install.conf"
INSTALL_CONF_SPEC="/tmp/$INSTALL_CONF_FILE"
@@ -251,11 +247,11 @@
for file in $SOLARIS_ZLIB $SOLARISMISC_ZLIB $IMAGE_INFO $config_file; do
print "Downloading $file" > /dev/msglog
- $WGET --continue --tries=$TRIES --waitretry=$WAITRETRY \
- --timeout=$TIMEOUT ${url}/$file -O /tmp/$file >/dev/msglog 2>&1
+ $WGET --continue --tries=$TRIES --waitretry=$WAITRETRY \
+ --timeout=$TIMEOUT ${url}/$file -O /tmp/$file >/dev/msglog 2>&1
- # if wget exits with failure, then exit
- if [ $? -ne 0 ]; then
+ # if wget exits with failure, then exit
+ if [ $? -ne 0 ]; then
print "Could not obtain $url/$file from install server" \
> /dev/msglog
print "Please verify that the install server is correctly" \
@@ -337,29 +333,6 @@
#
if [ "$TEXT_FLAG" == "true" ]; then
- # Retrieve the 'save' files (needed by remove_livecd_environment ICT)
- cd /tmp
- $WGET -x -i $url/$SAVE_LIST > /dev/msglog 2>&1
- if [ "$?" != "0" ]; then
- echo "ERROR: Failed to download save folder contents" > /dev/msglog
- exit $SMF_EXIT_ERR_FATAL
- fi
- save_folder=$(print $url | $SED 's#.*//##')/$SAVE
- cd -
-
- # When net-booted, only the contents of /.cdrom/save
- # (catalogued in the save_list)
- # are relevant to the '.livecd-cdrom-content' file
- $WGET $url/$SAVE_LIST -O /tmp/.livecd-cdrom-content
-
-
- # Link all of the downloaded files to where they should be in order to mimic CD
- ln -s /tmp/solarismisc.zlib /.cdrom/solarismisc.zlib
- ln -s /.volsetid /.cdrom/.volsetid
- ln -s /tmp/.image_info /.cdrom/.image_info
- ln -s /tmp/.livecd-cdrom-content /.cdrom/.livecd-cdrom-content
- ln -s /tmp/$save_folder /.cdrom/save
-
$SVCADM disable -s $CLI_LOGIN
if [ "$?" != "0" ]; then
echo "svcadm failed to disable client login" > /dev/msglog
--- a/usr/src/cmd/system-config/xslt/doc2sc_profile.xslt Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/cmd/system-config/xslt/doc2sc_profile.xslt Wed May 11 15:30:11 2011 -0700
@@ -33,8 +33,7 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8" doctype-system="/usr/share/lib/xml/dtd/service_bundle.dtd.1"/>
- <xsl:template match="//service_bundle">
- <xsl:copy-of select="."/>
- </xsl:template>
-
+ <xsl:template match="/">
+ <xsl:copy-of select="//service_bundle[@name='sysconfig']"/>
+ </xsl:template>
</xsl:stylesheet>
--- a/usr/src/cmd/text-install/Makefile Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/cmd/text-install/Makefile Wed May 11 15:30:11 2011 -0700
@@ -19,8 +19,7 @@
# CDDL HEADER END
#
#
-# Copyright 2010 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
#
include ../Makefile.cmd
@@ -30,17 +29,65 @@
clobber:= TARGET= clobber
install:= TARGET= install
-SUBDIRS= osol_install/text_install osol_install/profile svc helpfiles
+SUBDIRS= svc helpfiles
+
+MSG_DOMAIN = textinstall
+
+PROGS= text-install
+
+PYMODULES= __init__.py \
+ disk_selection.py \
+ disk_window.py \
+ fdisk_partitions.py \
+ install_progress.py \
+ install_status.py \
+ log_viewer.py \
+ partition_edit_screen.py \
+ progress.py \
+ summary.py \
+ ti_install.py \
+ ti_install_utils.py \
+ ti_target_utils.py \
+ welcome.py
+
+PYCMODULES= $(PYMODULES:%.py=%.pyc)
+
+ROOTPROGS= $(PROGS:%=$(ROOTUSRBIN)/%)
+
+ROOTPYMODULES= $(PYMODULES:%=$(ROOTPYTHONVENDORSOLINSTALLTI)/%)
+
+ROOTPYCMODULES= $(PYCMODULES:%=$(ROOTPYTHONVENDORSOLINSTALLTI)/%)
+
+MSGFILES = $(PYMODULES)
+
+.PARALLEL: $(SUBDIRS)
.KEEP_STATE:
-all: $(SUBDIRS)
+all: python $(PROGS) $(SUBDIRS)
-clean: $(SUBDIRS)
+clean: $(SUBDIRS)
+ rm -f *.pyc $(MSG_DOMAIN).po*
clobber: clean
-install: all $(SUBDIRS)
+install: all .WAIT $(SUBDIRS) .WAIT $(ROOTPROGS) \
+ $(ROOTPYTHONVENDOR) \
+ $(ROOTPYTHONVENDORSOLINSTALL) \
+ $(ROOTPYTHONVENDORSOLINSTALLTI) \
+ $(ROOTPYMODULES) \
+ $(ROOTPYCMODULES) \
+ .WAIT msgs
+
+python:
+ $(PYTHON) -m compileall -l $(@D)
+
+msgs: $(MSG_DOMAIN).po
+
+$(MSG_DOMAIN).po: $(PYMODULES)
+ @echo "Making messages file $(MSG_DOMAIN).po"
+ $(GNUXGETTEXT) $(GNUXGETFLAGS) -d $(MSG_DOMAIN) \
+ $(MSGFILES)
$(SUBDIRS): FRC
cd $@; pwd; echo $(TARGET); $(MAKE) $(TARGET)
@@ -48,4 +95,3 @@
FRC:
include ../Makefile.targ
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/__init__.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,410 @@
+#!/usr/bin/python2.6
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Text / (n)Curses based UI for installing Oracle Solaris
+'''
+
+import gettext
+
+# Variables used by modules this module imports
+# Defined here to avoid circular import errors
+_ = gettext.translation("textinstall", "/usr/share/locale",
+ fallback=True).ugettext
+RELEASE = {"release": _("Oracle Solaris")}
+TUI_HELP = "/usr/share/text-install/help"
+
+# Names of registered checkpoints.
+TARGET_DISCOVERY = "TargetDiscovery"
+TARGET_INIT = "TargetInitialization"
+TRANSFER_PREP = "PrepareTransfer"
+CLEANUP_CPIO_INSTALL = "cleanup-cpio-install"
+INIT_SMF = "initialize-smf"
+BOOT_CONFIG = "boot-configuration"
+DUMP_ADMIN = "update-dump-admin"
+FLUSH_IPS = "flush-ips-content"
+DEVICE_CONFIG = "device-config"
+APPLY_SYSCONFIG = "apply-sysconfig"
+BOOT_ARCHIVE = "boot-archive"
+CREATE_SNAPSHOT = "create-snapshot"
+
+import curses
+import locale
+import logging
+import os
+import platform
+import signal
+import subprocess
+import sys
+import traceback
+
+from optparse import OptionParser
+
+import solaris_install.sysconfig as sysconfig
+
+from solaris_install import Popen, CalledProcessError
+from solaris_install.engine import InstallEngine
+from solaris_install.logger import INSTALL_LOGGER_NAME, FileHandler
+from solaris_install.target.controller import TargetController
+from solaris_install.target.libbe.be import be_list
+from solaris_install.target.size import Size
+from solaris_install.text_install.disk_selection import DiskScreen
+from solaris_install.text_install.fdisk_partitions import FDiskPart
+from solaris_install.text_install.install_progress import InstallProgress
+from solaris_install.text_install.install_status import InstallStatus, \
+ RebootException
+from solaris_install.text_install.log_viewer import LogViewer
+from solaris_install.text_install.partition_edit_screen import PartEditScreen
+from solaris_install.text_install.summary import SummaryScreen
+from solaris_install.text_install.ti_install_utils import InstallData
+from solaris_install.text_install.welcome import WelcomeScreen
+from solaris_install.transfer.media_transfer import TRANSFER_ROOT, \
+ TRANSFER_MISC, TRANSFER_MEDIA
+import terminalui
+from terminalui import LOG_LEVEL_INPUT, LOG_NAME_INPUT
+from terminalui.action import Action
+from terminalui.base_screen import BaseScreen
+from terminalui.help_screen import HelpScreen
+from terminalui.i18n import get_encoding, set_wrap_on_whitespace
+from terminalui.main_window import MainWindow
+from terminalui.screen_list import ScreenList
+
+
+LOG_LOCATION_FINAL = "/var/sadm/system/logs/install_log"
+DEFAULT_LOG_LOCATION = "/tmp/install_log"
+DEFAULT_LOG_LEVEL = "info"
+DEBUG_LOG_LEVEL = "debug"
+REBOOT = "/usr/sbin/reboot"
+
+LOGGER = None
+
+
+def exit_text_installer(logname=None, errcode=0):
+ '''Close out the logger and exit with errcode'''
+ LOGGER.info("**** END ****")
+ LOGGER.close()
+ if logname is not None:
+ print _("Exiting Text Installer. Log is available at:\n%s") % logname
+ if isinstance(errcode, unicode):
+ errcode = errcode.encode(get_encoding())
+ sys.exit(errcode)
+
+
+def setup_logging(logname, log_level):
+ '''setup the logger, logging to logname at log_level'''
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ log_level = log_level.upper()
+ if hasattr(logging, log_level):
+ log_level = getattr(logging, log_level.upper())
+ elif log_level == LOG_NAME_INPUT:
+ log_level = LOG_LEVEL_INPUT
+ else:
+ raise IOError(2, "Invalid --log-level parameter", log_level.lower())
+
+ LOGGER.setLevel(log_level)
+ LOGGER.transfer_log(destination=logname)
+
+ LOGGER.info("**** START ****")
+
+
+def make_screen_list(main_win, target_controller, install_data):
+ '''Initialize the screen list. On x86, add screens for editing slices
+ within a partition. Also, trigger the target discovery thread.
+
+ '''
+
+ result = []
+ result.append(WelcomeScreen(main_win, install_data))
+ disk_screen = DiskScreen(main_win, target_controller)
+ disk_screen.start_discovery()
+ result.append(disk_screen)
+ result.append(FDiskPart(main_win, target_controller))
+ result.append(PartEditScreen(main_win, target_controller))
+ if platform.processor() == "i386":
+ result.append(FDiskPart(main_win, target_controller,
+ x86_slice_mode=True))
+ result.append(PartEditScreen(main_win, target_controller,
+ x86_slice_mode=True))
+ result.extend(sysconfig.get_all_screens(main_win))
+
+ result.append(SummaryScreen(main_win))
+ result.append(InstallProgress(main_win, install_data, target_controller))
+ result.append(InstallStatus(main_win, install_data))
+ result.append(LogViewer(main_win, install_data))
+ return result
+
+
+def _reboot_cmds(is_x86):
+ '''Generate list of cmds to try fast rebooting'''
+ cmds = list()
+
+ all_be = be_list()
+
+ for be_name, be_pool, root_ds, is_active in all_be:
+ if is_active:
+ if is_x86:
+ cmds.append([REBOOT, "-f", "--", root_ds])
+ else:
+ # SPARC requires "-Z" before the root dataset
+ cmds.append([REBOOT, "-f", "--", "-Z", root_ds])
+
+ # Fallback reboot. If the subprocess.call(..) command above fails,
+ # simply do a standard reboot.
+ cmds.append([REBOOT])
+ return cmds
+
+
+def reboot(is_x86):
+ '''Reboot the machine, attempting fast reboot first if available'''
+ cmds = _reboot_cmds(is_x86)
+ for cmd in cmds:
+ try:
+ Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=LOGGER)
+ except (CalledProcessError):
+ LOGGER.warn("Reboot failed:\n\t'%s'", " ".join(cmd))
+ else:
+ LOGGER.warn("Reboot failed:\n\t'%s'.\nWill attempt"
+ " standard reboot", " ".join(cmd))
+
+
+def prepare_engine(options):
+ ''' Instantiate the engine, setup logging, and register all
+ the checkpoints to be used for doing the install.
+ '''
+
+ eng = InstallEngine(debug=options.debug)
+
+ # setup_logging() must be called after the engine is initialized.
+ setup_logging(options.logname, options.log_level)
+
+ terminalui.init_logging(INSTALL_LOGGER_NAME)
+
+ # Information regarding checkpoints used for the Text Installer.
+ # The values specified are used as arguments for registering the
+ # checkpoint. If function signature for any of the checkpoints are
+ # is modified, these values need to be modified as well.
+
+ eng.register_checkpoint(TARGET_DISCOVERY,
+ "solaris_install/target/discovery",
+ "TargetDiscovery")
+
+ eng.register_checkpoint(TRANSFER_PREP,
+ "solaris_install/transfer/media_transfer",
+ "init_prepare_media_transfer")
+
+ eng.register_checkpoint(TARGET_INIT,
+ "solaris_install/target/instantiation",
+ "TargetInstantiation")
+
+ # The following 3 are transfer checkpoints
+ eng.register_checkpoint(TRANSFER_ROOT,
+ "solaris_install/transfer/cpio",
+ "TransferCPIO")
+
+ eng.register_checkpoint(TRANSFER_MISC,
+ "solaris_install/transfer/cpio",
+ "TransferCPIO")
+
+ eng.register_checkpoint(TRANSFER_MEDIA,
+ "solaris_install/transfer/cpio",
+ "TransferCPIO")
+
+ # sys config checkpoint must be registered after transfer checkpoints
+ sysconfig.register_checkpoint()
+
+ # rest of the checkpoints are for finishing up the install process
+ eng.register_checkpoint(CLEANUP_CPIO_INSTALL,
+ "solaris_install/ict/cleanup_cpio_install",
+ "CleanupCPIOInstall")
+
+ eng.register_checkpoint(INIT_SMF,
+ "solaris_install/ict/initialize_smf",
+ "InitializeSMF")
+
+ eng.register_checkpoint(BOOT_CONFIG,
+ "solaris_install/boot/boot",
+ "SystemBootMenu")
+
+ eng.register_checkpoint(DUMP_ADMIN,
+ "solaris_install/ict/update_dumpadm",
+ "UpdateDumpAdm")
+
+ eng.register_checkpoint(FLUSH_IPS,
+ "solaris_install/ict/ips",
+ "SetFlushContentCache")
+
+ eng.register_checkpoint(DEVICE_CONFIG,
+ "solaris_install/ict/device_config",
+ "DeviceConfig")
+
+ eng.register_checkpoint(APPLY_SYSCONFIG,
+ "solaris_install/ict/apply_sysconfig",
+ "ApplySysConfig")
+
+ eng.register_checkpoint(BOOT_ARCHIVE,
+ "solaris_install/ict/boot_archive",
+ "BootArchive")
+
+ eng.register_checkpoint(CREATE_SNAPSHOT,
+ "solaris_install/ict/create_snapshot",
+ "CreateSnapshot")
+
+
+def init_locale():
+
+ locale.setlocale(locale.LC_ALL, "")
+ gettext.install("textinstall", "/usr/share/locale", unicode=True)
+ set_wrap_on_whitespace(_("DONT_TRANSLATE_BUT_REPLACE_msgstr_WITH_True_"
+ "OR_False: Should wrap text on whitespace in"
+ " this language"))
+ BaseScreen.set_default_quit_text(_("Confirm: Quit the Installer?"),
+ _("Do you want to quit the Installer?"),
+ _("Cancel"),
+ _("Quit"))
+
+
+def main():
+ init_locale()
+
+ if os.getuid() != 0:
+ sys.exit(_("The %(release)s Text Installer must be run with "
+ "root privileges") % RELEASE)
+ usage = "usage: %prog [-l FILE] [-v LEVEL] [-d] [-n]"
+ parser = OptionParser(usage=usage, version="%prog 1.1")
+ parser.add_option("-l", "--log-location", dest="logname",
+ help=_("Set log location to FILE (default: %default)"),
+ metavar="FILE", default=DEFAULT_LOG_LOCATION)
+ parser.add_option("-v", "--log-level", dest="log_level",
+ default=None,
+ help=_("Set log verbosity to LEVEL. In order of "
+ "increasing verbosity, valid values are 'error' "
+ "'warn' 'info' 'debug' or 'input'\n[default:"
+ " %default]"),
+ choices=["error", "warn", "info", "debug", "input"],
+ metavar="LEVEL")
+ parser.add_option("-d", "--debug", action="store_true", dest="debug",
+ default=False, help=_("Enable debug mode. Sets "
+ "logging level to 'input' and enables CTRL-C for "
+ "killing the program\n"))
+ parser.add_option("-b", "--no-color", action="store_true", dest="force_bw",
+ default=False, help=_("Force the installer to run in "
+ "black and white. This may be useful on some SPARC "
+ "machines with unsupported frame buffers\n"))
+ parser.add_option("-n", "--no-install", action="store_true",
+ dest="no_install", default=False,
+ help=_("Runs in 'no installation' mode. When run"
+ " in 'no installation' mode, no persistent changes are"
+ " made to the disks and booted environment\n"))
+ options, args = parser.parse_args()
+ if options.log_level is None:
+ if options.debug:
+ options.log_level = DEBUG_LOG_LEVEL
+ else:
+ options.log_level = DEFAULT_LOG_LEVEL
+ try:
+ prepare_engine(options)
+ except IOError, err:
+ parser.error("%s '%s'" % (err.strerror, err.filename))
+ LOGGER.debug("CLI options: log location = %s, verbosity = %s, debug "
+ "mode = %s, no install = %s, force_bw = %s",
+ options.logname, options.log_level, options.debug,
+ options.no_install, options.force_bw)
+
+ install_data = InstallData()
+ install_data.log_location = options.logname
+ install_data.log_final = LOG_LOCATION_FINAL
+ install_data.no_install_mode = options.no_install
+
+ try:
+ with terminalui as initscr:
+ win_size_y, win_size_x = initscr.getmaxyx()
+ if win_size_y < 24 or win_size_x < 80:
+ msg = _(" Terminal too small. Min size is 80x24."
+ " Current size is %(x)ix%(y)i.") % \
+ {'x': win_size_x, 'y': win_size_y}
+ exit_text_installer(errcode=msg)
+
+ screen_list = ScreenList()
+ actions = [Action(curses.KEY_F2, _("Continue"),
+ screen_list.get_next),
+ Action(curses.KEY_F3, _("Back"),
+ screen_list.previous_screen),
+ Action(curses.KEY_F6, _("Help"), screen_list.show_help),
+ Action(curses.KEY_F9, _("Quit"), screen_list.quit)]
+
+ main_win = MainWindow(initscr, screen_list, actions,
+ force_bw=options.force_bw)
+ screen_list.help = HelpScreen(main_win, _("Help Topics"),
+ _("Help Index"),
+ _("Select a topic and press "
+ "Continue."))
+ doc = InstallEngine.get_instance().doc
+
+ debug_tc = False
+ if options.debug:
+ debug_tc = True
+ target_controller = TargetController(doc, debug=debug_tc)
+
+ win_list = make_screen_list(main_win, target_controller,
+ install_data)
+ screen_list.help.setup_help_data(win_list)
+ screen_list.screen_list = win_list
+ screen = screen_list.get_next()
+ ctrl_c = None
+ while screen is not None:
+ LOGGER.debug("Displaying screen: %s", type(screen))
+ screen = screen.show()
+ if not options.debug and ctrl_c is None:
+ # This prevents the user from accidentally hitting
+ # ctrl-c halfway through the install. Ctrl-C is left
+ # available through the first screen in case terminal
+ # display issues make it impossible for the user to
+ # quit gracefully
+ ctrl_c = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ errcode = 0
+ except RebootException:
+ reboot(platform.processor() == "i386")
+ except SystemExit:
+ raise
+ except:
+ LOGGER.info(str(InstallEngine.get_instance().doc))
+ LOGGER.info(str(install_data))
+ LOGGER.exception("Install failed")
+ exc_type, exc_value = sys.exc_info()[:2]
+ print _("An unhandled exception occurred.")
+ if str(exc_value):
+ print '\t%s: "%s"' % (exc_type.__name__, str(exc_value))
+ else:
+ print "\t%s" % exc_type.__name__
+ print _("Full traceback data is in the installation log")
+ print _("Please file a bug at http://defect.opensolaris.org")
+ errcode = 1
+ exit_text_installer(options.logname, errcode)
+
+if __name__ == '__main__':
+ main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/disk_selection.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,451 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Screens and functions to display a list of disks to the user.
+'''
+
+import curses
+import logging
+import platform
+
+import osol_install.errsvc as errsvc
+import osol_install.liberrsvc as liberrsvc
+from solaris_install.text_install import _, RELEASE, TUI_HELP, \
+ TARGET_DISCOVERY, TRANSFER_PREP
+from solaris_install.text_install.disk_window import DiskWindow, \
+ get_minimum_size, get_recommended_size
+from solaris_install.engine import InstallEngine
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.target import Target
+from solaris_install.target.controller import FALLBACK_IMAGE_SIZE
+from solaris_install.target.physical import Disk, Partition, Slice
+from solaris_install.target.size import Size
+from solaris_install.text_install.ti_target_utils import MAX_VTOC
+from solaris_install.transfer.media_transfer import get_image_size
+from terminalui.base_screen import BaseScreen, QuitException, UIMessage
+from terminalui.i18n import fit_text_truncate, textwidth, ljust_columns
+from terminalui.list_item import ListItem
+from terminalui.scroll_window import ScrollWindow
+from terminalui.window_area import WindowArea
+
+LOGGER = None
+
+
+class TargetDiscoveryError(StandardError):
+ '''Class for target discovery related errors'''
+ pass
+
+
+class DiskScreen(BaseScreen):
+ '''
+ Allow the user to select a (valid) disk target for installation
+ Display the partition/slice table for the highlighted disk
+
+ '''
+
+ HEADER_TEXT = _("Disks")
+ PARAGRAPH = _("Where should %(release)s be installed?") % RELEASE
+ SIZE_TEXT = _("Recommended size: %(recommend).1fGB "
+ "Minimum size: %(min).1fGB")
+ DISK_SEEK_TEXT = _("Seeking disks on system")
+ FOUND_x86 = _("The following partitions were found on the disk.")
+ FOUND_SPARC = _("The following slices were found on the disk.")
+ PROPOSED_x86 = _("A partition table was not found. The following is "
+ "proposed.")
+ PROPOSED_SPARC = _("A VTOC label was not found. The following "
+ "is proposed.")
+ PROPOSED_GPT = _("A GPT labeled disk was found. The following is "
+ "proposed.")
+ TOO_SMALL = _("Too small")
+ TOO_BIG_WARN = _("Limited to %.1f TB")
+ GPT_LABELED = _("GPT labeled disk")
+ NO_DISKS = _("No disks found. Additional device drivers may "
+ "be needed.")
+ NO_TARGETS = _("%(release)s cannot be installed on any disk") % RELEASE
+ TGT_ERROR = _("An error occurred while searching for installation"
+ " targets. Please check the install log and file a bug"
+ " at defect.opensolaris.org.")
+
+ DISK_HEADERS = [(8, _("Type")),
+ (10, _("Size(GB)")),
+ (6, _("Boot")),
+ (9, _("Device")),
+ (15, _("Manufacturer")),
+ (22, _("Notes"))]
+ SPINNER = ["\\", "|", "/", "-"]
+
+ DISK_WARNING_HEADER = _("Warning")
+ DISK_WARNING_TOOBIG = _("Only the first %.1fTB can be used.")
+ DISK_WARNING_GPT = _("You have chosen a GPT labeled disk. Installing "
+ "onto a GPT labeled disk will cause the loss "
+ "of all existing data and the disk will be "
+ "relabeled as SMI.")
+
+ CANCEL_BUTTON = _("Cancel")
+ CONTINUE_BUTTON = _("Continue")
+
+ HELP_DATA = (TUI_HELP + "/%s/disks.txt", _("Disks"))
+
+ def __init__(self, main_win, target_controller):
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ super(DiskScreen, self).__init__(main_win)
+ if platform.processor() == "i386":
+ self.found_text = DiskScreen.FOUND_x86
+ self.proposed_text = DiskScreen.PROPOSED_x86
+ else:
+ self.found_text = DiskScreen.FOUND_SPARC
+ self.proposed_text = DiskScreen.PROPOSED_SPARC
+
+ disk_header_text = []
+ for header in DiskScreen.DISK_HEADERS:
+ header_str = fit_text_truncate(header[1], header[0] - 1,
+ just="left")
+ disk_header_text.append(header_str)
+ self.disk_header_text = " ".join(disk_header_text)
+ max_note_size = DiskScreen.DISK_HEADERS[5][0]
+ self.too_small_text = DiskScreen.TOO_SMALL[:max_note_size]
+ max_disk_size = (Size(MAX_VTOC)).get(Size.tb_units)
+ too_big_warn = DiskScreen.TOO_BIG_WARN % max_disk_size
+ self.too_big_warn = too_big_warn[:max_note_size]
+ self.disk_warning_too_big = \
+ DiskScreen.DISK_WARNING_TOOBIG % max_disk_size
+
+ self.disks = []
+ self.existing_pools = []
+ self.disk_win = None
+ self.disk_detail = None
+ self.num_targets = 0
+ self.td_handle = None
+ self._size_line = None
+ self.selected_disk_index = 0
+ self._minimum_size = None
+ self._recommended_size = None
+
+ self.engine = InstallEngine.get_instance()
+ self.doc = self.engine.data_object_cache
+ self.tc = target_controller
+ self._target_discovery_completed = False
+ self._target_discovery_status = InstallEngine.EXEC_SUCCESS
+
+ def determine_minimum(self):
+ '''Returns minimum install size, fetching first if needed'''
+ self.determine_size_data()
+ return self._minimum_size
+
+ minimum_size = property(determine_minimum)
+
+ def determine_recommended(self):
+ '''Returns recommended install size, fetching first if needed'''
+ self.determine_size_data()
+ return self._recommended_size
+
+ recommended_size = property(determine_recommended)
+
+ def determine_size_data(self):
+ '''Retrieve the minimum and recommended sizes and generate the string
+ to present that information.
+
+ '''
+ if self._minimum_size is None or self._recommended_size is None:
+ self._recommended_size = get_recommended_size(self.tc)
+ self._minimum_size = get_minimum_size(self.tc)
+
+ def get_size_line(self):
+ '''Returns the line of text displaying the min/recommended sizes'''
+ if self._size_line is None:
+ rec_size = self.recommended_size.get(Size.gb_units)
+ min_size = self.minimum_size.get(Size.gb_units)
+ size_dict = {"recommend": rec_size, "min": min_size}
+ self._size_line = DiskScreen.SIZE_TEXT % size_dict
+ return self._size_line
+
+ size_line = property(get_size_line)
+
+ def wait_for_disks(self):
+ '''Block while waiting for libtd to finish. Catch F9 and quit
+ if needed
+
+ '''
+ self.main_win.actions.pop(curses.KEY_F2, None)
+ self.main_win.actions.pop(curses.KEY_F6, None)
+ self.main_win.actions.pop(curses.KEY_F3, None)
+ self.main_win.show_actions()
+
+ self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1,
+ self.win_size_x - 3)
+ self.main_win.do_update()
+ offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2
+ spin_index = 0
+ self.center_win.window.timeout(250)
+
+ while not self._target_discovery_completed:
+ input_key = self.main_win.getch()
+ if input_key == curses.KEY_F9:
+ if self.confirm_quit():
+ raise QuitException
+ self.center_win.add_text(DiskScreen.SPINNER[spin_index], 5, offset)
+ self.center_win.no_ut_refresh()
+ self.main_win.do_update()
+ spin_index = (spin_index + 1) % len(DiskScreen.SPINNER)
+
+ self.center_win.window.timeout(-1)
+ self.center_win.clear()
+
+ # check the result of target discovery
+ if self._target_discovery_status is not InstallEngine.EXEC_SUCCESS:
+ err_data = (errsvc.get_errors_by_mod_id(TARGET_DISCOVERY))[0]
+ LOGGER.error("Target discovery failed")
+ err = err_data.error_data[liberrsvc.ES_DATA_EXCEPTION]
+ LOGGER.error(err)
+ raise TargetDiscoveryError(("Unexpected error (%s) during target "
+ "discovery. See log for details.") % err)
+
+ def _td_callback(self, status, errsvc):
+ '''Callback function for Target Discovery checkpoint execution.
+ If there's no error from executing the checkpoint,
+ this will call the _display_disks() function to display disk
+ information.
+
+ '''
+
+ try:
+ image_size = Size(str(get_image_size(LOGGER)) + Size.mb_units)
+ LOGGER.debug("Image_size: %s", image_size)
+ except:
+ # Unable to get the image size for some reason, allow
+ # the target controller to use it's default size.
+ LOGGER.debug("Unable to get image size")
+ image_size = FALLBACK_IMAGE_SIZE
+
+ self.tc.initialize(image_size=image_size)
+ self._target_discovery_status = status
+ self._target_discovery_completed = True
+
+ def _show(self):
+ '''Create a list of disks to choose from and create the window
+ for displaying the partition/slice information from the selected
+ disk
+
+ '''
+
+ self.wait_for_disks()
+
+ discovered_target = self.doc.persistent.get_first_child( \
+ name=Target.DISCOVERED)
+
+ LOGGER.debug(discovered_target)
+ if discovered_target is None:
+ self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1,
+ max_x=(self.win_size_x - 1))
+ return
+
+ self.disks = discovered_target.get_children(class_type=Disk)
+ if not self.disks:
+ self.center_win.add_paragraph(DiskScreen.NO_TARGETS, 1, 1,
+ max_x=(self.win_size_x - 1))
+ return
+
+ # Go through all the disks found and find ones that have
+ # enough space for installation. At the same time, see if any
+ # existing disk is the boot disk. If a boot disk is found, move
+ # it to the front of the list
+ num_usable_disks = 0
+ boot_disk = None
+ for disk in self.disks:
+ LOGGER.debug("size: %s, min: %s" % \
+ (disk.disk_prop.dev_size, self.minimum_size))
+ if disk.disk_prop.dev_size >= self.minimum_size:
+ if disk.is_boot_disk():
+ boot_disk = disk
+ num_usable_disks += 1
+ if boot_disk is not None:
+ self.disks.remove(boot_disk)
+ self.disks.insert(0, boot_disk)
+
+ if num_usable_disks == 0:
+ self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1,
+ max_x=(self.win_size_x - 1))
+ return
+
+ self.main_win.reset_actions()
+ self.main_win.show_actions()
+
+ y_loc = 1
+ self.center_win.add_text(DiskScreen.PARAGRAPH, y_loc, 1)
+
+ y_loc += 1
+ self.center_win.add_text(self.size_line, y_loc, 1)
+
+ y_loc += 2
+ self.center_win.add_text(self.disk_header_text, y_loc, 1)
+
+ y_loc += 1
+ self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1,
+ curses.ACS_HLINE,
+ textwidth(self.disk_header_text))
+
+ y_loc += 1
+ disk_win_area = WindowArea(4, textwidth(self.disk_header_text) + 2,
+ y_loc, 0)
+ disk_win_area.scrollable_lines = len(self.disks) + 1
+ self.disk_win = ScrollWindow(disk_win_area,
+ window=self.center_win)
+
+ disk_item_area = WindowArea(1, disk_win_area.columns - 2, 0, 1)
+ disk_index = 0
+ len_type = DiskScreen.DISK_HEADERS[0][0] - 1
+ len_size = DiskScreen.DISK_HEADERS[1][0] - 1
+ len_boot = DiskScreen.DISK_HEADERS[2][0] - 1
+ len_dev = DiskScreen.DISK_HEADERS[3][0] - 1
+ len_mftr = DiskScreen.DISK_HEADERS[4][0] - 1
+ for disk in self.disks:
+ disk_text_fields = []
+ type_field = disk.disk_prop.dev_type[:len_type]
+ type_field = ljust_columns(type_field, len_type)
+ disk_text_fields.append(type_field)
+ disk_size = disk.disk_prop.dev_size.get(Size.gb_units)
+ size_field = "%*.1f" % (len_size, disk_size)
+ disk_text_fields.append(size_field)
+ if disk.is_boot_disk():
+ bootable_field = "+".center(len_boot)
+ else:
+ bootable_field = " " * (len_boot)
+ disk_text_fields.append(bootable_field)
+ device_field = disk.ctd[:len_dev]
+ device_field = ljust_columns(device_field, len_dev)
+ disk_text_fields.append(device_field)
+ vendor = disk.disk_prop.dev_vendor
+ if vendor is not None:
+ mftr_field = vendor[:len_mftr]
+ mftr_field = ljust_columns(mftr_field, len_mftr)
+ else:
+ mftr_field = " " * len_mftr
+ disk_text_fields.append(mftr_field)
+ selectable = True
+ if disk.disk_prop.dev_size < self.minimum_size:
+ note_field = self.too_small_text
+ selectable = False
+ elif disk_size > Size(MAX_VTOC).get(Size.gb_units):
+ note_field = self.too_big_warn
+ else:
+ note_field = ""
+ disk_text_fields.append(note_field)
+ disk_text = " ".join(disk_text_fields)
+ disk_item_area.y_loc = disk_index
+ disk_list_item = ListItem(disk_item_area, window=self.disk_win,
+ text=disk_text, add_obj=selectable)
+ disk_list_item.on_make_active = on_activate
+ disk_list_item.on_make_active_kwargs["disk"] = disk
+ disk_list_item.on_make_active_kwargs["disk_select"] = self
+ disk_index += 1
+
+ self.disk_win.no_ut_refresh()
+
+ y_loc += 7
+ disk_detail_area = WindowArea(6, 70, y_loc, 1)
+
+ self.disk_detail = DiskWindow(disk_detail_area, self.disks[0],
+ target_controller=self.tc,
+ window=self.center_win)
+
+ self.main_win.do_update()
+ self.center_win.activate_object(self.disk_win)
+ self.disk_win.activate_object(self.selected_disk_index)
+
+ def on_change_screen(self):
+ ''' Save the index of the current selected object
+ in case the user returns to this screen later
+
+ '''
+
+ # Save the index of the selected object
+ self.selected_disk_index = self.disk_win.active_object
+
+ LOGGER.debug("disk_selection.on_change_screen, saved_index: %s",
+ self.selected_disk_index)
+ LOGGER.debug(self.doc.persistent)
+
+ def start_discovery(self):
+
+ # start target discovery
+ if not self._target_discovery_completed:
+ errsvc.clear_error_list()
+
+ self.engine.execute_checkpoints(pause_before=TRANSFER_PREP,
+ callback=self._td_callback)
+
+ def validate(self):
+ '''Validate the size of the disk.'''
+
+ warning_txt = []
+
+ disk = self.disk_detail.ui_obj.doc_obj
+ disk_size_gb = disk.disk_prop.dev_size.get(Size.gb_units)
+ max_size_gb = Size(MAX_VTOC).get(Size.gb_units)
+ if disk_size_gb > max_size_gb:
+ warning_txt.append(self.disk_warning_too_big)
+ warning_txt = " ".join(warning_txt)
+
+ if warning_txt:
+ # warn the user and give user a chance to change
+ result = self.main_win.pop_up(DiskScreen.DISK_WARNING_HEADER,
+ warning_txt,
+ DiskScreen.CANCEL_BUTTON,
+ DiskScreen.CONTINUE_BUTTON)
+
+ if not result:
+ raise UIMessage() # let user select different disk
+
+ # if user didn't quit it is always OK to ignore disk size,
+ # that will be forced less than the maximum in partitioning.
+
+
+def on_activate(disk=None, disk_select=None):
+ '''When a disk is selected, pass its data to the disk_select_screen'''
+ max_x = disk_select.win_size_x - 1
+
+ LOGGER.debug("on activate..disk=%s", disk)
+
+ # See whether the disk is blank
+ if platform.processor() == "i386":
+ parts = disk.get_children(class_type=Partition)
+ else:
+ parts = disk.get_children(class_type=Slice)
+
+ if not parts:
+ display_text = disk_select.proposed_text
+ else:
+ display_text = disk_select.found_text
+
+ # Add the selected disk to the target controller so appropriate defaults
+ # can be filled in, if necessary
+ selected_disk = (disk_select.tc.select_disk(disk))[0]
+ selected_disk.whole_disk = False # assume we don't want to use whole disk
+
+ disk_select.center_win.add_paragraph(display_text, 11, 1, max_x=max_x)
+ disk_select.disk_detail.set_disk_info(disk_info=selected_disk)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/disk_window.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,864 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+
+'''
+UI component for displaying (and editing) partition & slice data
+'''
+
+
+import curses
+import logging
+import platform
+
+from copy import deepcopy
+
+from solaris_install.engine import InstallEngine
+from solaris_install.text_install import _
+from solaris_install.text_install.ti_target_utils import UIDisk, UIPartition, \
+ dump_doc, get_desired_target_disk, get_solaris_partition, ROOT_POOL, \
+ UI_PRECISION
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.target.libadm.const import FD_NUMPART as MAX_PRIMARY_PARTS
+from solaris_install.target.libadm.const import MAX_EXT_PARTS
+from solaris_install.target.physical import Disk, Partition, Slice
+from solaris_install.target.size import Size
+from solaris_install.target.libadm.const import V_ROOT
+
+from terminalui.base_screen import UIMessage
+from terminalui.edit_field import EditField
+from terminalui.i18n import fit_text_truncate, textwidth
+from terminalui.inner_window import InnerWindow, no_action
+from terminalui.list_item import ListItem
+from terminalui.scroll_window import ScrollWindow
+from terminalui.window_area import WindowArea
+
+LOGGER = None
+
+
+class DiskWindow(InnerWindow):
+ '''Display and edit disk information, including partitions and slices'''
+
+ STATIC_PARTITION_HEADERS = [(12, _("Primary"), _("Logical")),
+ (9, _("Size(GB)"), _("Size(GB)"))]
+
+ EDIT_PARTITION_HEADERS = [(13, _("Primary"), _("Logical")),
+ (9, _("Size(GB)"), _("Size(GB)")),
+ (7, _(" Avail"), _(" Avail"))]
+
+ STATIC_SLICE_HEADERS = [(13, _("Slice"), _("Slice")),
+ (2, "#", "#"),
+ (9, _("Size(GB)"), _("Size(GB)"))]
+
+ EDIT_SLICE_HEADERS = [(13, _("Slice"), _("Slice")),
+ (2, "#", "#"),
+ (9, _("Size(GB)"), _("Size(GB)")),
+ (7, _(" Avail"), _(" Avail"))]
+
+ ADD_KEYS = {curses.KEY_LEFT: no_action,
+ curses.KEY_RIGHT: no_action}
+
+ DEAD_ZONE = 3
+ SCROLL_PAD = 2
+
+ MIN_SIZE = None
+ REC_SIZE = None
+
+ SIZE_PRECISION = Size(UI_PRECISION).get(Size.gb_units)
+
+ DESTROYED_MARK = EditField.ASTERISK_CHAR
+
+ def __init__(self, area, disk_info, editable=False,
+ error_win=None, target_controller=None, **kwargs):
+ '''See also InnerWindow.__init__
+
+ disk_info (required) - Either a Disk or Partition object
+ containing the data to be represented. If a Partition objects is
+ provided, it will be used for displaying slice
+ data within that partition. If Disk has partition(s), those are
+ displayed. If not, but it has slices, then those are displayed. If
+ neither partition data nor slice data are available, a ValueError is
+ raised.
+
+ headers (required) - List of tuples to populate the header of this
+ window with. The first item in each tuple should be the width of the
+ header, the second item should be the left side header.
+
+ editable (optional) - If True, the window will be created such that
+ data is editable.
+
+ target_controller(optional) - Target controller
+
+ '''
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ self.headers = None
+ self.orig_ext_part_field = None
+ self.orig_logicals_active = False
+ self.ext_part_field = None
+ self.error_win = error_win
+ self.editable = editable
+ self.win_width = None
+ self.left_win = None
+ self.right_win = None
+ self.list_area = None
+ self.edit_area = None
+ super(DiskWindow, self).__init__(area, add_obj=editable, **kwargs)
+ self.left_header_string = None
+ self.right_header_string = None
+ self._orig_data = None
+ self.disk_info = None
+ self.has_partition_data = False
+ self.key_dict[curses.KEY_LEFT] = self.on_arrow_key
+ self.key_dict[curses.KEY_RIGHT] = self.on_arrow_key
+ if self.editable:
+ self.key_dict[curses.KEY_F5] = self.change_type
+
+ self.tc = target_controller
+ self._ui_obj = None
+ self.ui_obj = disk_info
+
+ self.set_disk_info(ui_obj=self.ui_obj)
+
+ LOGGER.debug(self.ui_obj)
+
+ if platform.processor() == "sparc":
+ self.is_x86 = False
+ else:
+ self.is_x86 = True
+
+ @property
+ def ui_obj(self):
+ return self._ui_obj
+
+ @ui_obj.setter
+ def ui_obj(self, part):
+ ''' create and set the value for ui_obj depending on type '''
+ if isinstance(part, Disk):
+ self._ui_obj = UIDisk(self.tc, parent=None, doc_obj=part)
+ elif isinstance(part, Partition):
+ self._ui_obj = UIPartition(self.tc, parent=None, doc_obj=part)
+ else:
+ # Must be a either a Disk or Partition. It's an error to be here
+ raise RuntimeError("disk_info object is invalid")
+
+ def _init_win(self, window):
+ '''Require at least 70 columns and 6 lines to fit current needs for
+ display of partitions and slices. Builds two inner ScrollWindows for
+ displaying/editing the data.
+
+ '''
+ if self.area.columns < 70:
+ raise ValueError("Insufficient space - area.columns < 70")
+ if self.area.lines < 6:
+ raise ValueError("Insufficient space - area.lines < 6")
+ self.win_width = (self.area.columns - DiskWindow.DEAD_ZONE
+ + DiskWindow.SCROLL_PAD) / 2
+
+ super(DiskWindow, self)._init_win(window)
+
+ win_area = WindowArea(self.area.lines - 1, self.win_width, 2, 0)
+ win_area.scrollable_lines = self.area.lines - 2
+ self.left_win = ScrollWindow(win_area, window=self, add_obj=False)
+ self.left_win.color = None
+ self.left_win.highlight_color = None
+ win_area.x_loc = self.win_width + DiskWindow.DEAD_ZONE
+ win_area.scrollable_lines = 2 * MAX_EXT_PARTS
+ self.right_win = ScrollWindow(win_area, window=self, add_obj=False)
+ self.right_win.color = None
+ self.right_win.highlight_color = None
+
+ def set_disk_info(self, ui_obj=None, disk_info=None, no_part_ok=False):
+ '''Set up this DiskWindow to represent disk_info'''
+
+ if ui_obj is not None:
+ disk_info = ui_obj.doc_obj
+ elif disk_info is not None:
+ self.ui_obj = disk_info
+ else:
+ # Should never be this case
+ raise RuntimeError("Unable to find ui_obj or disk_info")
+
+ part_list = disk_info.get_children(class_type=Partition)
+ if part_list:
+ self.has_partition_data = True
+ else:
+ slice_list = disk_info.get_children(class_type=Slice)
+ if slice_list:
+ self.has_partition_data = False
+ else:
+ # No partitions and no slices
+ if no_part_ok:
+ if self.is_x86:
+ self.has_partition_data = True
+ else:
+ self.has_partition_data = False
+ else:
+ return
+
+ if self.has_partition_data:
+ if self.editable:
+ self.headers = DiskWindow.EDIT_PARTITION_HEADERS
+ self.list_area = WindowArea(1, self.headers[0][0] +
+ self.headers[1][0],
+ 0, DiskWindow.SCROLL_PAD)
+ self.edit_area = WindowArea(1, self.headers[1][0], 0,
+ self.headers[0][0])
+ else:
+ self.headers = DiskWindow.STATIC_PARTITION_HEADERS
+ else:
+ if self.editable:
+ self.headers = DiskWindow.EDIT_SLICE_HEADERS
+ self.list_area = WindowArea(1, self.headers[0][0] +
+ self.headers[1][0] +
+ self.headers[2][0],
+ 0, DiskWindow.SCROLL_PAD)
+ self.edit_area = WindowArea(1, self.headers[2][0], 0,
+ self.headers[0][0] +
+ self.headers[1][0])
+ else:
+ self.headers = DiskWindow.STATIC_SLICE_HEADERS
+
+ LOGGER.debug("have_partition: %s", self.has_partition_data)
+ LOGGER.debug(self.ui_obj)
+
+ self.ui_obj.add_unused_parts(no_part_ok=no_part_ok)
+
+ self.left_win.clear()
+ self.right_win.clear()
+ self.window.erase()
+ self.print_headers()
+
+ if self.editable:
+ self.active_object = None
+ self.build_edit_fields()
+ self.right_win.bottom = max(0, len(self.right_win.all_objects) -
+ self.right_win.area.lines)
+ if self.has_partition_data:
+ self.orig_ext_part_field = None
+ for obj in self.left_win.objects:
+ if (obj.data_obj.is_extended()):
+ self.orig_ext_part_field = obj
+ self.orig_logicals_active = True
+ break
+ else:
+ self.print_data()
+
+ def print_headers(self):
+ '''Print the headers for the displayed data.
+
+ header[0] - The width of this column. header[1] and header[2] are
+ trimmed to this size
+ header[1] - The internationalized text for the left window
+ header[2] - The internationalized text for the right window
+
+ '''
+ self.left_header_string = []
+ self.right_header_string = []
+ for header in self.headers:
+ left_header_str = header[1]
+ right_header_str = header[2]
+ # Trim the header to fit in the column width,
+ # splitting columns with at least 1 space
+ # Pad with extra space(s) to align the columns
+ left_header_str = fit_text_truncate(left_header_str,
+ header[0] - 1, just="left")
+ self.left_header_string.append(left_header_str)
+ right_header_str = fit_text_truncate(right_header_str,
+ header[0] - 1, just="left")
+ self.right_header_string.append(right_header_str)
+ self.left_header_string = " ".join(self.left_header_string)
+ self.right_header_string = " ".join(self.right_header_string)
+ LOGGER.debug(self.left_header_string)
+ self.add_text(self.left_header_string, 0, DiskWindow.SCROLL_PAD)
+ right_win_offset = (self.win_width + DiskWindow.DEAD_ZONE +
+ DiskWindow.SCROLL_PAD)
+ self.add_text(self.right_header_string, 0, right_win_offset)
+ self.window.hline(1, DiskWindow.SCROLL_PAD, curses.ACS_HLINE,
+ textwidth(self.left_header_string))
+ self.window.hline(1, right_win_offset, curses.ACS_HLINE,
+ textwidth(self.right_header_string))
+ self.no_ut_refresh()
+
+ def print_data(self):
+ '''Print static (non-editable) data.
+
+ Slices - fill the left side, then remaining slices on the right side.
+ If for some reason not all slices fit, indicate how many more slices
+ there area
+
+ Partitions - Put standard partitions on the left, logical partitions
+ on the right
+
+ '''
+
+ part_index = 0
+ data = self.ui_obj.get_parts_in_use()
+
+ if len(data) == 0:
+ return # should never be this case
+
+ if self.has_partition_data:
+ max_parts = MAX_PRIMARY_PARTS
+ else:
+ max_parts = min(len(data), self.left_win.area.lines)
+
+ win = self.left_win
+ y_loc = 0
+ for next_data in data:
+ LOGGER.debug("next_data: %s", next_data)
+ if y_loc >= max_parts:
+ if win is self.left_win:
+ win = self.right_win
+ y_loc = 0
+ max_parts = win.area.lines
+ else:
+ num_extra = len(data) - part_index
+ if self.has_partition_data:
+ more_parts_txt = _("%d more partitions") % num_extra
+ else:
+ more_parts_txt = _("%d more slices") % num_extra
+ win.add_text(more_parts_txt, win.area.lines, 3)
+ break
+ x_loc = DiskWindow.SCROLL_PAD
+ field = 0
+ win.add_text(next_data.get_description(), y_loc, x_loc,
+ self.headers[field][0] - 1)
+ x_loc += self.headers[field][0]
+ field += 1
+ if not self.has_partition_data:
+ win.add_text(str(next_data.name), y_loc, x_loc,
+ self.headers[field][0] - 1)
+ x_loc += self.headers[field][0]
+ field += 1
+ win.add_text("%*.1f" % (self.headers[field][0] - 1,
+ next_data.size.get(Size.gb_units)),
+ y_loc, x_loc,
+ self.headers[field][0] - 1)
+ x_loc += self.headers[field][0]
+ y_loc += 1
+ field += 1
+ part_index += 1
+ self.right_win.use_vert_scroll_bar = False
+ self.no_ut_refresh()
+
+ def build_edit_fields(self):
+ '''Build subwindows for editing partition sizes
+
+ For slices, fill the left side, then the right (right side scrolling as
+ needed, though this shouldn't happen unless the number of slices on
+ disk exceeds 8 for some reason)
+
+ For partitions, fill the left side up to MAX_PRIMARY_PARTS,
+ and place all logical partitions on the right.
+
+ '''
+
+ data = self.ui_obj.get_parts_in_use()
+
+ if self.has_partition_data:
+ max_left_parts = MAX_PRIMARY_PARTS
+ else:
+ if len(data) == 0:
+ return # should never be this case
+ max_left_parts = min(len(data), self.left_win.area.lines)
+
+ part_iter = iter(data)
+ try:
+ next_part = part_iter.next()
+ self.objects.append(self.left_win)
+ for y_loc in range(max_left_parts):
+ self.list_area.y_loc = y_loc
+ self.create_list_item(next_part, self.left_win, self.list_area)
+ next_part = part_iter.next()
+ self.objects.append(self.right_win)
+ for y_loc in range(self.right_win.area.scrollable_lines):
+ self.list_area.y_loc = y_loc
+ self.create_list_item(next_part, self.right_win,
+ self.list_area)
+ next_part = part_iter.next()
+ if len(data) > max_left_parts:
+ self.right_win.use_vert_scroll_bar = True
+ except StopIteration:
+ if len(self.right_win.all_objects) <= self.right_win.area.lines:
+ self.right_win.use_vert_scroll_bar = False
+ self.right_win.no_ut_refresh()
+ else:
+ raise ValueError("Could not fit all partitions in DiskWindow")
+ self.no_ut_refresh()
+
+ def create_list_item(self, next_part, win, list_area):
+ '''Add an entry for next_part (a Partition or Slice) to
+ the DiskWindow
+
+ '''
+ list_item = ListItem(list_area, window=win, data_obj=next_part)
+ list_item.key_dict.update(DiskWindow.ADD_KEYS)
+ edit_field = EditField(self.edit_area, window=list_item,
+ numeric_pad=" ",
+ validate=decimal_valid,
+ on_exit=on_exit_edit,
+ error_win=self.error_win,
+ add_obj=False,
+ data_obj=next_part)
+ edit_field.right_justify = True
+ edit_field.validate_kwargs["disk_win"] = self
+ edit_field.on_exit_kwargs["disk_win"] = self
+ edit_field.key_dict.update(DiskWindow.ADD_KEYS)
+ self.update_part(part_field=list_item)
+ return list_item
+
+ def update_part(self, part_info=None, part_field=None):
+ '''Sync changed partition data to the screen.'''
+ if part_field is None:
+ if part_info is None:
+ raise ValueError("Must supply either part_info or part_field")
+ part_field = self.find_part_field(part_info)[1]
+ elif part_info is None:
+ part_info = part_field.data_obj
+ elif part_field.data_obj is not part_info:
+ raise ValueError("part_field must be a ListItem associated with "
+ "part_info")
+ if not isinstance(part_field, ListItem):
+ raise TypeError("part_field must be a ListItem associated with "
+ "part_info")
+ if self.has_partition_data:
+ desc_text = part_info.get_description()
+ else:
+ desc_length = self.headers[0][0] - 1
+ desc_text = "%-*.*s %s" % (desc_length, desc_length,
+ part_info.get_description(),
+ part_info.name)
+ part_field.set_text(desc_text)
+ edit_field = part_field.all_objects[0]
+ edit_field.set_text("%.1f" % part_info.size.get(Size.gb_units))
+ self.mark_if_destroyed(part_field)
+ self._update_edit_field(part_info, part_field, edit_field)
+
+ self.update_avail_space(part_info=part_info)
+ if self.has_partition_data:
+ if part_info.is_extended():
+ self.ext_part_field = part_field
+
+ def _update_edit_field(self, part_info, part_field, edit_field):
+ '''If the partition/slice is editable, add it to the .objects list.
+ If it's also the part_field that's currently selected, then activate
+ the edit field.
+
+ '''
+ if part_info.editable():
+ part_field.objects = [edit_field]
+ active_win = self.get_active_object()
+ if active_win is not None:
+ if active_win.get_active_object() is part_field:
+ part_field.activate_object(edit_field)
+ else:
+ edit_field.make_inactive()
+ part_field.objects = []
+ part_field.active_object = None
+
+ def mark_if_destroyed(self, part_field):
+ '''Determine if the partition/slice represented by part_field has
+ changed such that its contents will be destroyed.
+
+ '''
+ part_info = part_field.data_obj
+ destroyed = part_info.modified()
+ self.mark_destroyed(part_field, destroyed)
+
+ def mark_destroyed(self, part_field, destroyed):
+ '''If destroyed is True, add an asterisk indicating that the
+ partition or slice's content will be destroyed during installation.
+ Otherwise, clear the asterisk
+
+ '''
+ y_loc = part_field.area.y_loc
+ x_loc = part_field.area.x_loc - 1
+ if part_field in self.right_win.objects:
+ win = self.right_win
+ else:
+ win = self.left_win
+ if destroyed:
+ win.window.addch(y_loc, x_loc, DiskWindow.DESTROYED_MARK,
+ win.color_theme.inactive)
+ else:
+ win.window.addch(y_loc, x_loc, InnerWindow.BKGD_CHAR)
+
+ def update_avail_space(self, part_number=None, part_info=None):
+ '''Update the 'Avail' column for the specified slice or partition.
+ If no number is given, all avail columns are updated
+
+ '''
+ if part_number is None and part_info is None:
+ self._update_all_avail_space()
+ else:
+ self._update_avail_space(part_number, part_info)
+
+ def _update_all_avail_space(self):
+ '''Update the 'Avail' column for all slices or partitions.'''
+ idx = 0
+ for item in self.left_win.objects:
+ self.update_avail_space(idx)
+ idx += 1
+ for item in self.right_win.objects:
+ self.update_avail_space(idx)
+ idx += 1
+ y_loc = idx - len(self.left_win.objects)
+ if self.has_partition_data:
+ x_loc = self.headers[0][0] + self.headers[1][0] + 1
+ field = 2
+ else:
+ x_loc = (self.headers[0][0] + self.headers[1][0] +
+ self.headers[2][0] + 1)
+ field = 3
+ if y_loc > 0:
+ self.right_win.add_text(" " * self.headers[field][0],
+ y_loc, x_loc)
+
+ def _update_avail_space(self, part_number=None, part_info=None):
+ '''Update the 'Avail' column for the specified slice or partition.'''
+ if part_number is None:
+ win, item = self.find_part_field(part_info)
+ elif part_number < len(self.left_win.objects):
+ win = self.left_win
+ item = win.objects[part_number]
+ else:
+ win = self.right_win
+ item = win.objects[part_number - len(self.left_win.objects)]
+ if self.has_partition_data:
+ x_loc = self.headers[0][0] + self.headers[1][0] + 1
+ field = 2
+ else:
+ x_loc = (self.headers[0][0] + self.headers[1][0] +
+ self.headers[2][0] + 1)
+ field = 3
+ y_loc = item.area.y_loc
+ part = item.data_obj
+ max_space = part.get_max_size()
+ max_space = "%*.1f" % (self.headers[field][0],
+ max_space.get(Size.gb_units))
+ win.add_text(max_space, y_loc, x_loc)
+
+ def find_part_field(self, part_info):
+ '''Given a PartitionInfo or SliceInfo object, find the associated
+ ListItem. This search compares by reference, and will only succeed
+ if you have a handle to the exact object referenced by the ListItem
+
+ '''
+ for win in [self.left_win, self.right_win]:
+ for item in win.objects:
+ if item.data_obj is part_info:
+ return win, item
+ raise ValueError("Part field not found")
+
+ def reset(self, dummy=None):
+ '''Reset ui_obj to value found from Target Discovery.
+ Meaningful only for editable DiskWindows
+
+ '''
+ if not self.editable:
+ return
+ doc = InstallEngine.get_instance().doc
+
+ # "reset" the desired target
+ reset_obj = None
+ if isinstance(self.ui_obj, UIDisk):
+ reset_obj = (self.tc.reset_layout(disk=self.ui_obj.doc_obj))[0]
+ else:
+ # reset the partition by removing the modified Partition, and
+ # resetting it with the partition found during target discovery.
+
+ discovered_obj = self.ui_obj.discovered_doc_obj
+
+ desired_disk = get_desired_target_disk(doc)
+ desired_part = get_solaris_partition(doc)
+
+ desired_disk.delete_partition(desired_part)
+ part_copy = deepcopy(discovered_obj)
+ desired_disk.insert_children(part_copy)
+
+ # get the updated reference
+ reset_obj = get_solaris_partition(doc)
+
+ dump_doc("After doing reset")
+
+ self.set_disk_info(disk_info=reset_obj)
+ self.activate_solaris_data()
+
+ def activate_solaris_data(self):
+ '''Find the Solaris Partition / ZFS Root Pool Slice and activate it.
+
+ '''
+
+ if self.editable:
+ solaris_part = self.ui_obj.get_solaris_data()
+ if solaris_part is None:
+ LOGGER.debug("No Solaris data, activating default")
+ self.activate_object()
+ self.right_win.scroll(scroll_to_line=0)
+ return
+ disk_order = self.ui_obj.get_parts_in_use().index(solaris_part)
+ LOGGER.debug("solaris disk at disk_order = %s", disk_order)
+ self.activate_index(disk_order)
+
+ def make_active(self):
+ '''On activate, select the solaris partition or ZFS root pool,
+ instead of defaulting to 0
+
+ '''
+ self.set_color(self.highlight_color)
+ self.activate_solaris_data()
+
+ def on_arrow_key(self, input_key):
+ '''
+ On curses.KEY_LEFT: Move from the right win to the left win
+ On curses.KEY_RIGHT: Move from the left to the right
+
+ '''
+ if (input_key == curses.KEY_LEFT and
+ self.get_active_object() is self.right_win and
+ len(self.left_win.objects) > 0):
+
+ active_object = self.right_win.get_active_object().area.y_loc
+ if (active_object >= len(self.left_win.objects)):
+ active_object = len(self.left_win.objects) - 1
+ self.activate_object(self.left_win)
+ self.left_win.activate_object(active_object)
+ return None
+ elif (input_key == curses.KEY_RIGHT and
+ self.get_active_object() is self.left_win and
+ len(self.right_win.objects) > 0):
+ active_line = (self.left_win.active_object +
+ self.right_win.current_line[0])
+ active_object = None
+ force_to_top = False
+ for obj in self.right_win.objects:
+ if obj.area.y_loc >= active_line:
+ active_object = obj
+ off_screen = (self.right_win.current_line[0] +
+ self.right_win.area.lines)
+ if active_object.area.y_loc > off_screen:
+ force_to_top = True
+ break
+ if active_object is None:
+ active_object = 0
+ self.left_win.activate_object(-1, loop=True)
+ self.activate_object(self.right_win)
+ self.right_win.activate_object_force(active_object,
+ force_to_top=force_to_top)
+ return None
+ return input_key
+
+ def no_ut_refresh(self, abs_y=None, abs_x=None):
+ '''Refresh self, left win and right win explicitly'''
+ super(DiskWindow, self).no_ut_refresh()
+ self.left_win.no_ut_refresh(abs_y, abs_x)
+ self.right_win.no_ut_refresh(abs_y, abs_x)
+
+ def change_type(self, dummy):
+ '''Cycle the type for the currently active object, and
+ update its field
+
+ '''
+ LOGGER.debug("changing type")
+
+ part_field = self.get_active_object().get_active_object()
+ part_info = part_field.data_obj
+
+ part_order = self.ui_obj.get_parts_in_use().index(part_info)
+
+ old_obj = part_info.discovered_doc_obj
+ old_type = list()
+ if old_obj is not None:
+ if self.has_partition_data:
+ old_type.append(old_obj.part_type)
+ else:
+ if old_obj.in_zpool is not None:
+ old_type.append(old_obj.in_zpool)
+ else:
+ in_use = part_info.doc_obj.in_use
+ if in_use is not None:
+ if in_use['used_name']:
+ old_type.append((in_use['used_name'])[0])
+
+ LOGGER.debug("extra type to cycle: %s", old_type)
+ part_info.cycle_type(extra_type=old_type)
+ self.set_disk_info(ui_obj=self.ui_obj, no_part_ok=True)
+ self.activate_index(part_order)
+
+ return None
+
+ def create_extended(self, ext_part_field):
+ '''If this is the original extended partition, restore the original
+ logical partitions. Otherwise, create a single unused logical
+ partition.
+
+ '''
+ if not ext_part_field.data_obj.modified():
+ self.right_win.clear()
+ self.orig_logicals_active = True
+ logicals = deepcopy(self._orig_data.get_logicals())
+ self.disk_info.partitions.extend(logicals)
+ for idx, logical in enumerate(logicals):
+ self.list_area.y_loc = idx
+ self.create_list_item(logical, self.right_win, self.list_area)
+ if self.right_win not in self.objects:
+ self.objects.append(self.right_win)
+ self.right_win.activate_object_force(0, force_to_top=True)
+ self.right_win.make_inactive()
+ self.right_win.no_ut_refresh()
+ else:
+ # Leave old data be, create new Unused logical partition
+ if self.right_win not in self.objects:
+ self.objects.append(self.right_win)
+ self.append_unused_logical()
+
+ def activate_index(self, obj_index):
+ '''Activate the object at the specified index '''
+
+ if obj_index < len(self.left_win.objects):
+ LOGGER.debug("activating in left_win")
+ self.left_win.activate_object(obj_index)
+ self.activate_object(self.left_win)
+ self.right_win.scroll(scroll_to_line=0)
+ else:
+ activate = obj_index - len(self.left_win.objects)
+ LOGGER.debug('activating in right win')
+ self.right_win.activate_object_force(activate, force_to_top=True)
+ self.activate_object(self.right_win)
+ left_active = self.left_win.get_active_object()
+ if left_active is not None:
+ left_active.make_inactive()
+
+ def append_unused_logical(self):
+ '''Adds a single Unused logical partition to the right window'''
+ new_part = self.disk_info.append_unused_logical()
+ self.list_area.y_loc = len(self.right_win.all_objects)
+ bottom = self.list_area.y_loc - self.right_win.area.lines + 1
+ self.right_win.bottom = max(0, bottom)
+ self.create_list_item(new_part, self.right_win, self.list_area)
+ scroll = len(self.right_win.all_objects) > self.right_win.area.lines
+ self.right_win.use_vert_scroll_bar = scroll
+ self.right_win.no_ut_refresh()
+
+
+def decimal_valid(edit_field, disk_win=None):
+ '''Check text to see if it is a decimal number of precision no
+ greater than the tenths place.
+
+ '''
+ text = edit_field.get_text().lstrip()
+ if text.endswith(" "):
+ raise UIMessage(_('Only the digits 0-9 and "." are valid.'))
+ vals = text.split(".")
+ if len(vals) > 2:
+ raise UIMessage(_('A number can only have one "."'))
+ try:
+ if len(vals[0]) > 0:
+ int(vals[0])
+ if len(vals) > 1 and len(vals[1]) > 0:
+ int(vals[1])
+ except ValueError:
+ raise UIMessage(_('Only the digits 0-9 and "." are valid.'))
+ if len(vals) > 1 and len(vals[1]) > 1:
+ raise UIMessage(_("Size can be specified to only one decimal place."))
+ if disk_win is not None:
+ text = text.rstrip(".")
+ if not text:
+ text = "0"
+
+ new_size = Size(text + Size.gb_units)
+ max_size = edit_field.data_obj.get_max_size()
+
+ # When comparing sizes, check only to the first decimal place,
+ # as that is all the user sees. (Rounding errors that could
+ # cause the partition/slice layout to be invalid get cleaned up
+ # prior to target instantiation)
+ new_size_rounded = round(new_size.get(Size.gb_units), 1)
+ max_size_rounded = round(max_size.get(Size.gb_units), 1)
+ if new_size_rounded > max_size_rounded:
+ raise UIMessage(_("The new size (%(size).1f) is greater than "
+ "the available space (%(avail).1f)") %
+ {"size": new_size_rounded,
+ "avail": max_size_rounded})
+ return True
+
+
+def on_exit_edit(edit_field, disk_win=None):
+ '''On exit, if the user has left the field blank, set the size to 0'''
+
+ text = edit_field.get_text()
+ if not text.strip():
+ text = "0"
+ edit_field.set_text("%.1f" % float(text))
+
+ part_order = disk_win.ui_obj.get_parts_in_use().index(edit_field.data_obj)
+ LOGGER.debug("Part being resized is at index: %s", part_order)
+
+ new_size_text = text.strip()
+
+ LOGGER.debug("Resizing text=%s", new_size_text)
+ new_size = Size(new_size_text + Size.gb_units)
+ old_size = edit_field.data_obj.size
+
+ new_size_byte = new_size.get(Size.byte_units)
+ old_size_byte = old_size.get(Size.byte_units)
+
+ precision = Size(UI_PRECISION).get(Size.byte_units)
+
+ if abs(new_size_byte - old_size_byte) > precision:
+ resized_obj = edit_field.data_obj.doc_obj.resize(float(new_size_text),
+ size_units=Size.gb_units)
+
+ if isinstance(resized_obj, Partition):
+ resized_obj.in_zpool = ROOT_POOL
+ else:
+ if resized_obj.in_zpool == ROOT_POOL:
+ resized_obj.tag = V_ROOT
+
+ if disk_win is not None:
+ disk_win.set_disk_info(ui_obj=disk_win.ui_obj)
+ disk_win.activate_index(part_order)
+
+ dump_doc("After resize")
+
+
+def get_recommended_size(target_controller):
+ '''Returns the recommended size for the installation, as a Size object '''
+ if DiskWindow.REC_SIZE is None:
+ DiskWindow.REC_SIZE = target_controller.recommended_target_size
+ return DiskWindow.REC_SIZE
+
+
+def get_minimum_size(target_controller):
+ '''Returns the minimum disk space needed for installation,
+ as a Size object
+
+ '''
+
+ if DiskWindow.MIN_SIZE is None:
+ DiskWindow.MIN_SIZE = target_controller.minimum_target_size
+ return DiskWindow.MIN_SIZE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/fdisk_partitions.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,273 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Screen for selecting to use whole disk, or a partition/slice on the disk
+'''
+
+import logging
+import platform
+
+from solaris_install.engine import InstallEngine
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.target.physical import Disk, Partition, Slice
+from solaris_install.target.size import Size
+from solaris_install.text_install import _, RELEASE, TUI_HELP
+from solaris_install.text_install.disk_window import DiskWindow
+from solaris_install.text_install.ti_target_utils import \
+ get_desired_target_disk, get_solaris_partition, dump_doc, ROOT_POOL
+from terminalui.base_screen import BaseScreen, SkipException
+from terminalui.i18n import textwidth
+from terminalui.list_item import ListItem
+from terminalui.window_area import WindowArea
+
+
+LOGGER = None
+
+
+class FDiskPart(BaseScreen):
+ '''Allow user to choose to use the whole disk, or move to the
+ partition/slice edit screen.
+
+ '''
+
+ BOOT_TEXT = _("Boot")
+ HEADER_FDISK = _("Fdisk Partitions: %(size).1fGB %(type)s %(bootable)s")
+ HEADER_PART_SLICE = _("Solaris Partition Slices")
+ HEADER_SLICE = _("Solaris Slices: %(size).1fGB %(type)s %(bootable)s")
+ PARAGRAPH_FDISK = _("%(release)s can be installed on the whole "
+ "disk or a partition on the disk.") % RELEASE
+ PARAGRAPH_PART_SLICE = _("%(release)s can be installed in the "
+ "whole fdisk partition or within a "
+ "slice in the partition") % RELEASE
+ PARAGRAPH_SLICE = _("%(release)s can be installed on the whole"
+ " disk or a slice on the disk.") % RELEASE
+ FOUND_PART = _("The following partitions were found on the disk.")
+ PROPOSED_PART = _("A partition table was not found. The following is"
+ " proposed.")
+ FOUND_SLICE = _("The following slices were found on the disk.")
+ PROPOSED_SLICE = _("A VTOC label was not found. The following is "
+ "proposed.")
+ USE_WHOLE_DISK = _("Use the whole disk")
+ USE_WHOLE_PARTITION = _("Use the whole partition")
+ USE_SLICE_IN_PART = _("Use a slice in the partition")
+ USE_PART_IN_DISK = _("Use a partition of the disk")
+ USE_SLICE_IN_DISK = _("Use a slice on the disk")
+
+ SPARC_HELP = (TUI_HELP + "/%s/sparc_solaris_slices.txt",
+ _("Solaris Slices"))
+ X86_PART_HELP = (TUI_HELP + "/%s/"
+ "x86_fdisk_partitions.txt",
+ _("Fdisk Partitions"))
+ X86_SLICE_HELP = (TUI_HELP + "/%s/x86_fdisk_slices.txt",
+ _("Solaris Partition Slices"))
+
+ def __init__(self, main_win, target_controller, x86_slice_mode=False):
+ '''If x86_slice_mode == True, this screen presents options for using a
+ whole partition, or a slice within the partition.
+ Otherwise, it presents options for using the whole disk, or using a
+ partition (x86) or slice (SPARC) within the disk
+
+ '''
+ super(FDiskPart, self).__init__(main_win)
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+ self.x86_slice_mode = x86_slice_mode
+ self.is_x86 = True
+ self.help_format = " %s"
+ if platform.processor() == "sparc": # SPARC, slices on a disk
+ self.is_x86 = False
+ self.header_text = FDiskPart.HEADER_SLICE
+ self.paragraph = FDiskPart.PARAGRAPH_SLICE
+ self.found = FDiskPart.FOUND_SLICE
+ self.proposed = FDiskPart.PROPOSED_SLICE
+ self.use_whole = FDiskPart.USE_WHOLE_DISK
+ self.use_part = FDiskPart.USE_SLICE_IN_DISK
+ self.help_data = FDiskPart.SPARC_HELP
+ elif self.x86_slice_mode: # x86, slices within a partition
+ self.instance = ".slice"
+ self.header_text = FDiskPart.HEADER_PART_SLICE
+ self.paragraph = FDiskPart.PARAGRAPH_PART_SLICE
+ self.found = FDiskPart.FOUND_SLICE
+ self.proposed = FDiskPart.PROPOSED_SLICE
+ self.use_whole = FDiskPart.USE_WHOLE_PARTITION
+ self.use_part = FDiskPart.USE_SLICE_IN_PART
+ self.help_data = FDiskPart.X86_SLICE_HELP
+ self.help_format = " %s"
+ else: # x86, partitions on a disk
+ self.header_text = FDiskPart.HEADER_FDISK
+ self.paragraph = FDiskPart.PARAGRAPH_FDISK
+ self.found = FDiskPart.FOUND_PART
+ self.proposed = FDiskPart.PROPOSED_PART
+ self.use_whole = FDiskPart.USE_WHOLE_DISK
+ self.use_part = FDiskPart.USE_PART_IN_DISK
+ self.help_data = FDiskPart.X86_PART_HELP
+ self.disk_win = None
+ self.partial_disk_item = None
+ self.whole_disk_item = None
+ self.disk = None
+ self.tc = target_controller
+ self.use_whole_segment = True
+
+ def _show(self):
+ '''Display partition data for selected disk, and present the two
+ choices
+
+ '''
+ doc = InstallEngine.get_instance().doc
+
+ if self.x86_slice_mode:
+
+ disk = get_desired_target_disk(doc)
+ if disk.whole_disk:
+ raise SkipException
+
+ sol_partition = get_solaris_partition(doc)
+
+ LOGGER.debug("Working with the following partition:")
+ LOGGER.debug(str(sol_partition))
+
+ if sol_partition is None:
+ # Must have a Solaris partition
+ err_msg = "Critical error - no Solaris partition found"
+ LOGGER.error(err_msg)
+ raise ValueError(err_msg)
+
+ # See if there are any slices in the partition
+ all_slices = sol_partition.get_children(class_type=Slice)
+
+ if not all_slices:
+ LOGGER.info("No previous slices found")
+
+ # Setting the in_zpool flag to indicate the whole
+ # partition should be used. The needed underlying
+ # slices will be created in the next step when
+ # the in_zpool flag is detected.
+ sol_partition.in_zpool = ROOT_POOL
+
+ raise SkipException
+
+ LOGGER.debug("Preserved partition with existing slices, "
+ "presenting option to install into a slice")
+
+ self.disk = sol_partition
+
+ else:
+
+ self.disk = get_desired_target_disk(doc)
+
+ LOGGER.debug("Working with the following disk:")
+ LOGGER.debug(str(self.disk))
+
+ if self.disk.whole_disk:
+ LOGGER.debug("disk.whole_disk=True, skip editting")
+ raise SkipException
+
+ if self.disk.is_boot_disk():
+ bootable = FDiskPart.BOOT_TEXT
+ else:
+ bootable = u""
+ disk_size_gb = self.disk.disk_prop.dev_size.get(Size.gb_units)
+ header_text = self.header_text % \
+ {"size": disk_size_gb,
+ "type": self.disk.disk_prop.dev_type,
+ "bootable": bootable}
+ self.main_win.set_header_text(header_text)
+
+ y_loc = 1
+ y_loc += self.center_win.add_paragraph(self.paragraph, start_y=y_loc)
+
+ y_loc += 1
+ if self.is_x86 and not self.x86_slice_mode:
+ all_parts = self.disk.get_children(class_type=Partition)
+ else:
+ all_parts = self.disk.get_children(class_type=Slice)
+
+ found_parts = bool(all_parts)
+
+ if found_parts:
+ next_line = self.found
+ else:
+ next_line = self.proposed
+ y_loc += self.center_win.add_paragraph(next_line, start_y=y_loc)
+
+ y_loc += 1
+ disk_win_area = WindowArea(6, 70, y_loc, 0)
+ self.disk_win = DiskWindow(disk_win_area, self.disk,
+ target_controller=self.tc,
+ window=self.center_win)
+ y_loc += disk_win_area.lines
+
+ y_loc += 3
+ whole_disk_width = textwidth(self.use_whole) + 3
+ cols = (self.win_size_x - whole_disk_width) / 2
+ whole_disk_item_area = WindowArea(1, whole_disk_width, y_loc, cols)
+ self.whole_disk_item = ListItem(whole_disk_item_area,
+ window=self.center_win,
+ text=self.use_whole,
+ centered=True)
+
+ y_loc += 1
+ partial_width = textwidth(self.use_part) + 3
+ cols = (self.win_size_x - partial_width) / 2
+ partial_item_area = WindowArea(1, partial_width, y_loc, cols)
+ self.partial_disk_item = ListItem(partial_item_area,
+ window=self.center_win,
+ text=self.use_part,
+ centered=True)
+
+ self.main_win.do_update()
+ if self.use_whole_segment:
+ self.center_win.activate_object(self.whole_disk_item)
+ else:
+ self.center_win.activate_object(self.partial_disk_item)
+
+ def on_continue(self):
+ '''Set the user's selection in the install target. If they chose
+ to use the entire disk (or entire partition), define a single
+ partition (or slice) to consume the whole disk (or partition)
+
+ '''
+
+ if self.center_win.get_active_object() is self.whole_disk_item:
+ self.use_whole_segment = True
+ if isinstance(self.disk, Disk):
+ LOGGER.debug("Setting whole_disk and creating default"
+ " layout for %s", self.disk)
+ disk = self.tc.select_disk(self.disk, use_whole_disk=True)[0]
+ disk.whole_disk = True
+ else:
+ # it's a partition, set the in_zpool attribute in
+ # the object for now. The next screen will
+ # fill in needed slices
+ self.disk.in_zpool = ROOT_POOL
+ else:
+ self.use_whole_segment = False
+ if isinstance(self.disk, Disk):
+ LOGGER.debug("Setting whole_disk to false")
+ self.disk.whole_disk = False
+ else:
+ self.disk.in_zpool = None
+
+ dump_doc("At the end of fdisk_partitions.continue")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/install_progress.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Start the installation, provide functions for updating the install progress
+'''
+
+import curses
+import math
+
+from solaris_install.text_install import _, RELEASE
+from solaris_install.text_install.ti_install import perform_ti_install
+from terminalui.base_screen import BaseScreen
+from terminalui.i18n import ljust_columns
+from terminalui.inner_window import InnerWindow
+from terminalui.window_area import WindowArea
+
+
+class InstallProgress(BaseScreen):
+ '''Present a progress bar, and callback hooks for an installation
+ Thread to update this screen with percent complete and status.
+
+ '''
+
+ HEADER_TEXT = _("Installing %(release)s") % RELEASE
+ PROG_BAR_ENDS = (ord('['), ord(']'))
+
+ QUIT_DISK_MODIFIED = _("Do you want to quit the Installer?\n\n"
+ "Any changes made to the disk by the "
+ "Installer will be left \"as is.\"")
+
+ def __init__(self, main_win, install_data, target_controller):
+ super(InstallProgress, self).__init__(main_win)
+
+ # Location on screen where the status messages should get printed
+ # Format: (y, x, max-width)
+ self.status_msg_loc = (4, 12, 50)
+
+ # Location on screen where the progress bar should be displayed
+ # Format: (y, x, width)
+ self.status_bar_loc = (6, 10, 50)
+
+ self.status_bar = None
+ self.status_bar_width = None
+ self.install_data = install_data
+ self.progress_color = None
+ self.update_to = None
+ self.tc = target_controller
+
+ def set_actions(self):
+ '''Remove all actions except F9_Quit.'''
+ self.main_win.actions.pop(curses.KEY_F2) # Remove F2_Continue
+ self.main_win.actions.pop(curses.KEY_F3) # Remove F3_Back
+ self.main_win.actions.pop(curses.KEY_F6) # Remove F6_Help
+
+ def _show(self):
+ '''Set an initial status message, and initialize the progress bar'''
+ self.set_status_message(InstallProgress.HEADER_TEXT)
+ self.init_status_bar(*self.status_bar_loc)
+ self.main_win.redrawwin()
+ self.main_win.do_update()
+
+ def validate_loop(self):
+ ''' Do the actual installation '''
+ self.center_win.window.timeout(100) # Make getch() non-blocking
+ perform_ti_install(self.install_data, self, self.update_status)
+ self.center_win.window.timeout(-1) # Restore getch() to be blocking
+ return self.main_win.screen_list.get_next(self)
+
+ def update_status(self, percent, message):
+ '''Update this screen to display message and set the status
+ bar to 'percent'.
+
+ '''
+
+ self.set_status_message(message)
+ self.set_status_percent(percent)
+ self.main_win.redrawwin()
+ self.main_win.do_update()
+
+ def set_status_message(self, message):
+ '''Set the status message on the screen, completely overwriting
+ the previous message'''
+ self.center_win.add_text(ljust_columns(\
+ message, self.status_msg_loc[2]), self.status_msg_loc[0],\
+ self.status_msg_loc[1], max_chars=self.status_msg_loc[2])
+
+ def set_status_percent(self, percent):
+ '''Set the completion percentage by updating the progress bar.
+ Note that this is implemented as a 'one-way' change (updating to
+ a percent that is lower than previously set will not work)
+
+ '''
+ width = self.status_bar_width
+ complete = int(math.ceil(float(percent) / 100.0 * width))
+ self.status_bar.add_text(" " * complete, start_y=0, start_x=1,
+ max_chars=width)
+ percent_text = "(%i%%)" % percent
+ percent_bar = percent_text.center(width)
+ left_half = percent_bar[:complete]
+ right_half = percent_bar[complete:]
+ self.status_bar.window.addstr(0, 1, left_half, self.progress_color)
+ self.status_bar.window.addstr(0, complete + 1, right_half)
+ self.status_bar.no_ut_refresh()
+
+ def init_status_bar(self, y_loc, x_loc, width):
+ '''Initialize the progress bar window and set to 0%'''
+ self.status_bar_width = width
+ status_bar_area = WindowArea(1, width + 3, y_loc, x_loc + 1)
+ self.status_bar = InnerWindow(status_bar_area,
+ window=self.center_win)
+ self.status_bar.window.addch(0, 0, InstallProgress.PROG_BAR_ENDS[0])
+ self.status_bar.window.addch(0, width + 1,
+ InstallProgress.PROG_BAR_ENDS[1])
+ self.progress_color = self.center_win.color_theme.progress_bar
+ self.set_status_percent(0)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/install_status.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,121 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Report the status of an installation to the user
+'''
+
+import curses
+
+from solaris_install.text_install import _, RELEASE
+from terminalui.action import Action
+from terminalui.base_screen import BaseScreen
+
+
+class RebootException(SystemExit):
+ '''Raised when user requests reboot'''
+ pass
+
+
+class InstallStatus(BaseScreen):
+ '''
+ Display text to the user indicating success or failure of the installation.
+ Also provide option for viewing the install log
+
+ '''
+
+ SUCCESS_HEADER = _("Installation Complete")
+ FAILED_HEADER = _("Installation Failed")
+
+ SUCCESS_TEXT = _("The installation of %(release)s has completed "
+ "successfully.\n\n"
+ "Reboot to start the newly installed software "
+ "or Quit if you wish to perform additional "
+ "tasks before rebooting.\n\n"
+ "The installation log is available at "
+ "%(log_tmp)s. After reboot it can be found"
+ " at %(log_final)s.")
+
+ FAILED_TEXT = _("The installation did not complete normally.\n\n"
+ "For more information you can review the"
+ " installation log.\n"
+ "The installation log is available at %(log_tmp)s")
+
+ def __init__(self, main_win, install_data):
+ super(InstallStatus, self).__init__(main_win)
+ self.log_locations = {}
+ self.install_data = install_data
+
+ def set_actions(self):
+ '''Remove all actions except Quit, and add actions for rebooting
+ and viewing the log.
+
+ '''
+ self.main_win.actions.pop(curses.KEY_F2) # Remove F2_Continue
+ self.main_win.actions.pop(curses.KEY_F3) # Remove F3_Back
+ self.main_win.actions.pop(curses.KEY_F6) # Remove F6_Help
+
+ if self.install_data.install_succeeded:
+ reboot_action = Action(curses.KEY_F8, _("Reboot"), reboot_system)
+ self.main_win.actions[reboot_action.key] = reboot_action
+
+ log_action = Action(curses.KEY_F4, _("View Log"),
+ self.main_win.screen_list.get_next)
+ self.main_win.actions[log_action.key] = log_action
+
+ def _show(self):
+ '''Display the correct text based on whether the installation
+ succeeded or failed.
+
+ '''
+
+ self.log_locations["log_tmp"] = self.install_data.log_location
+ self.log_locations["log_final"] = self.install_data.log_final
+ if self.install_data.install_succeeded:
+ self.header_text = InstallStatus.SUCCESS_HEADER
+ paragraph_text = InstallStatus.SUCCESS_TEXT
+ else:
+ self.header_text = InstallStatus.FAILED_HEADER
+ paragraph_text = InstallStatus.FAILED_TEXT
+ self.main_win.set_header_text(self.header_text)
+
+ fmt = {}
+ fmt.update(self.log_locations)
+ fmt.update(RELEASE)
+ self.center_win.add_paragraph(paragraph_text % fmt, 2)
+
+ def confirm_quit(self):
+ '''No need to confirm after installation is complete'''
+ return True
+
+
+def reboot_system(screen=None):
+ '''Attempts to reboot the system (unless running with the '-n' command
+ line flag)
+
+ '''
+ if screen and screen.install_data.no_install_mode:
+ raise SystemExit("REBOOT")
+ else:
+ raise RebootException
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/log_viewer.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Read in and display the install log to the user
+'''
+
+import curses
+
+from solaris_install.text_install import _
+from terminalui.base_screen import BaseScreen
+from terminalui.i18n import convert_paragraph
+from terminalui.scroll_window import ScrollWindow
+from terminalui.window_area import WindowArea
+
+
+class LogViewer(BaseScreen):
+ '''Screen for reading and displaying the install log'''
+
+ HEADER_TEXT = _("Installation Log")
+
+ def __init__(self, main_win, install_data):
+ super(LogViewer, self).__init__(main_win)
+ self.log_data = None
+ self.scroll_area = None
+ self.install_data = install_data
+
+ def set_actions(self):
+ '''Remove all actions except F3_Back'''
+ self.main_win.actions.pop(curses.KEY_F2)
+ self.main_win.actions.pop(curses.KEY_F6)
+ self.main_win.actions.pop(curses.KEY_F9)
+
+ def _show(self):
+ '''Create a scrollable region and fill it with the install log'''
+
+ self.center_win.border_size = (0, 0)
+ self.scroll_area = WindowArea(self.win_size_y,
+ self.win_size_x,
+ 0, 0, len(self.get_log_data()))
+ log = ScrollWindow(self.scroll_area, window=self.center_win)
+ log.add_paragraph(self.get_log_data(), 0, 2)
+ self.center_win.activate_object(log)
+
+ def get_log_data(self):
+ '''Attempt to read in the install log file. If an error occurs,
+ the log_data is set to a string explaining the cause, if possible.
+
+ '''
+ if self.log_data is None:
+ try:
+ with open(self.install_data.log_location) as log_file:
+ log_data = log_file.read()
+ except (OSError, IOError) as error:
+ self.log_data = _("Could not read log file:\n\t%s") % \
+ error.strerror
+ max_chars = self.win_size_x - 4
+ self.log_data = convert_paragraph(log_data, max_chars)
+ return self.log_data
--- a/usr/src/cmd/text-install/osol_install/__init__.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-#!/usr/bin/python
-'''This file SHOULD NOT be built into the package
-It is included solely to provide the ability to run/debug
-osol_install.text_install.* directly from the workspace
-(so that Python recognizes the osol_install folder as a
-module). osol_install/__init__.py is normally delivered by
-pkg:/system/install
-'''
--- a/usr/src/cmd/text-install/osol_install/profile/Makefile Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-include ../../../Makefile.cmd
-
-all:= TARGET= all
-clean:= TARGET= clean
-clobber:= TARGET= clobber
-install:= TARGET= install
-
-PYMODULES= __init__.py \
- disk_info.py \
- disk_space.py \
- install_profile.py \
- partition_info.py \
- slice_info.py
-
-PYCMODULES= $(PYMODULES:%.py=%.pyc)
-
-ROOTPROGS= $(PROGS:%=$(ROOTUSRBIN)/%)
-
-ROOTPYMODULES= $(PYMODULES:%=$(ROOTPYTHONVENDORINSTALLPROF)/%)
-
-ROOTPYCMODULES= $(PYCMODULES:%=$(ROOTPYTHONVENDORINSTALLPROF)/%)
-
-MSGFILES = $(PYMODULES)
-
-.KEEP_STATE:
-
-all: python
-
-clean:
- rm -f $(PROGS) *.pyc
-
-clobber: clean
-
-
-install: all .WAIT $(ROOTPROGS) \
- $(ROOTPYTHONVENDOR) \
- $(ROOTPYTHONVENDORINSTALL) \
- $(ROOTPYTHONVENDORINSTALLPROF) \
- $(ROOTPYMODULES) \
- $(ROOTPYCMODULES)
-
-python:
- $(PYTHON) -m compileall -l $(@D)
-
-
-FRC:
-
-include ../../../Makefile.targ
-
--- a/usr/src/cmd/text-install/osol_install/profile/disk_info.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,629 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Object to represent Disks
-'''
-
-from copy import copy, deepcopy
-import logging
-import platform
-
-import osol_install.tgt as tgt
-from osol_install.profile.disk_space import DiskSpace, round_to_multiple, \
- round_down
-from osol_install.profile.partition_info import PartitionInfo, MIN_GAP_SIZE, \
- MIN_LOGICAL_GAP_SIZE
-from osol_install.profile.slice_info import SliceInfo
-from osol_install.text_install.ti_install_utils import InstallationError
-
-
-def adjust_tgt_parts(tgt_objs, tgt_parent=None, ext_part=None):
- '''Modify a list of tgt.Partitions or tgt.Slices in place. For each item
- in the list, ensure that its offset is *past* the previous tgt object, and
- ensure that its size + offset doesn't cause it to overlap with the
- subsequent partition/slice.
-
- tgt.Partitions/Slices whose 'modified' flag is false are ignored. These
- are assumed to already exist on the disk (and assumed to not overlap);
- new Partitions/Slices are adjusted around the existing ones.
-
- tgt_objs: A list of tgt.Partitions or tgt.Slices. Logical partitions
- should NOT be mixed with standard partitions.
- tgt_parent: The tgt.Disk or tgt.Partition on which the tgt_objs are
- supposed to fit
- ext_part: The extended partition on which the tgt_objs are supposed to fit
-
- It is invalid to specify both tgt_parent and ext_part (ext_part will be
- ignored and the resulting disk layout could be indeterminate)
-
- '''
-
- if not tgt_objs:
- return
-
- if tgt_parent is not None:
- cylsz = tgt_parent.geometry.cylsz
- if isinstance(tgt_objs[0], tgt.Slice):
- # Checking slices on a partition/disk
- # Can start at 0
- min_offset = 0
-
- if isinstance(tgt_parent, tgt.Slice):
- # Using S2 to determine max. All blocks usable
- abs_max_end = tgt_parent.blocks
- else:
- # Using a disk or partition to determine max
- # Must leave 2 cylinders worth of space available
- abs_max_end = tgt_parent.blocks - 2 * cylsz
-
- else:
- # Checking partitions on a disk
- # Use the minimum partition offset as starting point
- # Don't exceed the end of the disk
- min_offset = PartitionInfo.MIN_OFFSET
- abs_max_end = tgt_parent.blocks
- elif ext_part is not None:
- # Logicals on an extended partition
- # Should have an offset past the start of the extended partition
- # Should not go past the end of the extended partition
- min_offset = ext_part.offset
- abs_max_end = ext_part.offset + ext_part.blocks
- cylsz = ext_part.geometry.cylsz
- else:
- raise TypeError("Must specify ext_part or tgt_parent keyword arg")
-
- for idx, tgt_obj in enumerate(tgt_objs):
-
- if tgt_obj.modified and tgt_obj.offset < min_offset:
- tgt_obj.offset = round_to_multiple(min_offset, cylsz)
-
- if tgt_obj is tgt_objs[-1]:
- # Last item in the list - don't let this obj slide past the end
- # of the disk/partition
- max_end = abs_max_end
- else:
- # More items in the list - don't let this obj overlap with the
- # next item
- max_end = tgt_objs[idx+1].offset - 1
-
- if tgt_obj.modified and tgt_obj.offset + tgt_obj.blocks > max_end:
- shift = (tgt_obj.offset + tgt_obj.blocks) - max_end
- new_blocks = tgt_obj.blocks - shift
- tgt_obj.blocks = round_down(new_blocks, cylsz)
-
- # Minimum offset for next obj should be past the end of this obj.
- # Preserve the current value of min_offset if it happens to be greater
- # than the end of this obj (for example, if this slice overlaps with,
- # and lies completely within, a prior slice)
- min_offset = max(min_offset, tgt_obj.offset + tgt_obj.blocks + 1)
-
-
-# Pylint gets confused and thinks DiskInfo.size is a string but it's
-# actually a DiskSpace object
-# pylint: disable-msg=E1103
-class DiskInfo(object):
- '''Represents a single disk on the system.'''
-
- FDISK = "fdisk"
- GPT = "gpt"
- VTOC = "vtoc"
-
- def __init__(self, blocksz=512, cylsz=None, boot=False, partitions=None,
- slices=None, controller=None, label=None, name=None,
- removable=False, serialno=None, size=None, vendor=None,
- tgt_disk=None):
- '''Constructor takes either a tgt_disk, which should be a tgt.Disk
- object, or a set of parameters. If tgt_disk is supplied, all other
- parameters are ignored.
-
- '''
- self._size = None
- self._tgt_disk = tgt_disk
- self._ext_part_idx = 0
- self._sol_part_idx = 0
- self._unused_parts_added = False
- if tgt_disk:
- self.blocksz = tgt_disk.geometry.blocksz
- self.cylsz = tgt_disk.geometry.cylsz
- self.boot = tgt_disk.boot
- self.partitions = []
- self.slices = []
- if tgt_disk.children:
- if isinstance(tgt_disk.children[0], tgt.Slice):
- for child in tgt_disk.children:
- self.slices.append(SliceInfo(tgt_slice=child))
- else:
- for child in tgt_disk.children:
- self.partitions.append(PartitionInfo(tgt_part=child))
- self.controller = tgt_disk.controller
- self.label = set()
- if tgt_disk.gpt:
- self.label.add(DiskInfo.GPT)
- if tgt_disk.vtoc:
- self.label.add(DiskInfo.VTOC)
- if tgt_disk.fdisk:
- self.label.add(DiskInfo.FDISK)
- self.name = tgt_disk.name
- self.removable = tgt_disk.removable
- self.serialno = tgt_disk.serialno
- size = str(tgt_disk.blocks * self.blocksz) + "b"
- self.size = size
- self.vendor = tgt_disk.vendor
- else:
- self.blocksz = blocksz
- self.cylsz = cylsz
- self.boot = boot
- if partitions and slices:
- raise ValueError("A disk cannot have both partitions and"
- " slices")
- if partitions is None:
- partitions = []
- self.partitions = partitions
- if slices is None:
- slices = []
- self.slices = slices
- self.controller = controller
- self.label = label
- self.name = name
- self.removable = removable
- self.serialno = serialno
- self.size = size
- self.vendor = vendor
- self.use_whole_segment = False
-
- if platform.processor() == "i386":
- self.was_blank = not bool(self.partitions)
- else:
- self.was_blank = not bool(self.slices)
-
- def __str__(self):
- result = ["Disk Info (%s):" % self.name]
- result.append("Size: %s" % self.size)
- for part in self.partitions:
- result.append(str(part))
- for slice_info in self.slices:
- result.append(str(slice_info))
- return "\n".join(result)
-
- def get_type(self):
- '''Return this disk's 'type' (controller) as a string'''
- if self.controller is None:
- return ""
- else:
- return self.controller.upper()
-
- type = property(get_type)
-
- def get_size(self):
- '''Returns this disk's size as a DiskSpace object'''
- return self._size
-
- def set_size(self, size):
- '''Set this disk's size. size must be either a DiskSpace or a string
- that will be accepted by DiskSpace.__init__
-
- '''
- if isinstance(size, DiskSpace):
- self._size = deepcopy(size)
- else:
- self._size = DiskSpace(size)
-
- size = property(get_size, set_size)
-
- def get_blocks(self):
- '''Return the number of blocks on this disk'''
- return int(self.size.size_as("b") / self.blocksz)
-
- blocks = property(get_blocks)
-
- def get_solaris_data(self, check_multiples=False):
- '''Find and return the solaris partition (x86) or install target
- slice (sparc) on this disk.
-
- Returns None if one does not exist.
-
- '''
- if platform.processor() == "i386":
- parts = self.partitions
- else:
- parts = self.slices
-
- if (not check_multiples and self._sol_part_idx < len(parts) and
- parts[self._sol_part_idx].is_solaris_data()):
- return parts[self._sol_part_idx]
-
- solaris_data = None
- for part in parts:
- if part.is_solaris_data():
- if solaris_data is None:
- self._sol_part_idx = parts.index(part)
- solaris_data = part
- if not check_multiples:
- break
- elif check_multiples:
- raise ValueError("Found multiple children with "
- "solaris data")
-
- return solaris_data
-
- def get_extended_partition(self):
- '''Find and return the Extended partition on this disk.
- Returns None if this disk has no extended partition.
-
- '''
- if (self._ext_part_idx < len(self.partitions) and
- self.partitions[self._ext_part_idx].is_extended()):
- return self.partitions[self._ext_part_idx]
-
- for part in self.partitions:
- if part.is_extended():
- self._ext_part_idx = part
- return part
-
- return None
-
- def get_logicals(self):
- '''Retrieve all the logicals on this disk'''
- logicals = []
- for part in self.partitions:
- if part.is_logical():
- logicals.append(part)
- return logicals
-
- def get_standards(self):
- '''Return all non-logical partitions'''
- standards = []
- for part in self.partitions:
- if not part.is_logical():
- standards.append(part)
- return standards
-
- def remove_logicals(self):
- '''Delete the logicals from this disk'''
- remove_all = []
- for part in self.partitions:
- if part.is_logical():
- remove_all.append(part)
- for part in remove_all:
- self.partitions.remove(part)
-
- def collapse_unused_logicals(self):
- '''Collapse adjacent unused logical partitions'''
- logicals = self.get_logicals()
- removal_count = 0
- for idx, logical in enumerate(logicals[:-1]):
- next_log = logicals[idx+1]
- if (logical.id == PartitionInfo.UNUSED and
- next_log.id == PartitionInfo.UNUSED):
- self.partitions.remove(next_log)
- removal_count += 1
- return removal_count
-
- def add_unused_parts(self):
- '''On x86: Sort through the logical and non-logical partitions and
- find the largest gaps. For non-logical partitions, create additional
- Unused partitions such that the number of
- non-logical partitions == MAX_STANDARD_PARTITIONS. For logical
- partitions, create additional Unused partitions such that the number of
- logical partitions == MAX_LOGICAL_PARTITIONS
-
- On SPARC: Create Unused slices in the largest gaps of empty space,
- so that there are exactly 8 slices
-
- For non-logical partitions, gaps smaller than 1 GB are ignored.
- For logical partitions, gaps smaller than 0.1 GB are ignored.
-
- This method adds unused parts exactly once.
-
- '''
- if self._unused_parts_added:
- return
-
- if self.partitions:
- use_partitions = True
- parts = self.get_standards()
- parts.sort(cmp=PartitionInfo.compare)
- numbers = range(1, PartitionInfo.MAX_STANDARD_PARTITIONS + 1)
- start_pt = 0
- elif self.slices:
- use_partitions = False
- parts = copy(self.slices)
- parts.sort(cmp=SliceInfo.compare)
- numbers = range(SliceInfo.MAX_SLICES)
- numbers.remove(SliceInfo.BACKUP_SLICE)
- start_pt = 0
- else:
- raise ValueError("Cannot determine if this disk has partitions"
- " or slices")
- backup_part = None
- if not use_partitions:
- for part in parts:
- if part.number == SliceInfo.BACKUP_SLICE:
- backup_part = part
- if backup_part is not None:
- parts.remove(backup_part)
-
- min_gap_size = MIN_GAP_SIZE.size_as("b")
- gaps = []
- end_pt = 0
- for part in parts:
- if part.number in numbers:
- numbers.remove(part.number)
- start_pt = part.offset.size_as("b")
- gap_size = start_pt - end_pt
- if gap_size > min_gap_size:
- gaps.append((gap_size, end_pt))
- end_pt = part.get_endblock().size_as("b")
- end_disk = self.size.size_as("b")
- gap_size = end_disk - end_pt
- if gap_size > min_gap_size:
- gaps.append((gap_size, end_pt))
- # Sorting a list of tuples will sort by the first item in the tuple,
- # In this case, gap_size, such that the smallest gap is first.
- # Then, the largest gaps can be popped off the end of the list
- gaps.sort()
- for part_num in numbers:
- if gaps:
- gap = gaps.pop()
- offset = str(gap[1]) + "b"
- else:
- offset = str(end_pt) + "b"
- if use_partitions:
- new_part = PartitionInfo(part_num=part_num, offset=offset)
- self.partitions.append(new_part)
- else:
- new_part = SliceInfo(slice_num=part_num, offset=offset)
- self.slices.append(new_part)
- if not use_partitions and backup_part is None:
- new_part = SliceInfo(slice_num=SliceInfo.BACKUP_SLICE,
- size=self.size)
- self.slices.append(new_part)
- self.sort_disk_order()
-
- # now process the logical partitions
- if use_partitions:
- logicals = self.get_logicals()
- ext_part = self.get_extended_partition()
- if ext_part is not None:
- min_logical_gap_size = MIN_LOGICAL_GAP_SIZE.size_as("b")
- logical_gaps = []
- end_pt = ext_part.offset.size_as("b")
-
- numbers = range(PartitionInfo.FIRST_LOGICAL,
- PartitionInfo.FIRST_LOGICAL +
- PartitionInfo.MAX_LOGICAL_PARTITIONS)
- for logical in logicals:
- numbers.remove(logical.number)
- start_pt = logical.offset.size_as("b")
- logical_gap_size = start_pt - end_pt
- if logical_gap_size > min_logical_gap_size:
- logical_gaps.append((logical_gap_size, end_pt))
- end_pt = logical.get_endblock().size_as("b")
-
- end_disk = ext_part.get_endblock().size_as("b")
- logical_gap_size = end_disk - end_pt
- if logical_gap_size > min_logical_gap_size:
- logical_gaps.append((logical_gap_size, end_pt))
-
- # Sorting a list of tuples will sort by the first item
- # in the tuple, in this case, logical_gap_size, such that
- # the smallest gap is first.
- logical_gaps.sort()
- for number in numbers:
- if logical_gaps:
- logical_gap = logical_gaps.pop()
- offset = str(logical_gap[1]) + "b"
- else:
- break
- new_part = PartitionInfo(part_num=number, offset=offset)
- self.partitions.append(new_part)
-
- self.sort_disk_order()
-
- self._unused_parts_added = True
-
- def append_unused_logical(self):
- '''Append a single unused logical partition to the disk.
- The last logical partition is assumed to be something *other*
- than an 'unused' partition
-
- '''
- ext_part = self.get_extended_partition()
- if ext_part is None:
- return None
- logicals = self.get_logicals()
-
- if len(logicals) > 0:
- last_log = logicals[-1]
- offset = last_log.get_endblock()
- part_num = last_log.number + 1
- else: # First logical on this disk
- offset = ext_part.offset
- part_num = PartitionInfo.FIRST_LOGICAL
- new_part = PartitionInfo(part_num=part_num, offset=offset)
- self.partitions.append(new_part)
- return new_part
-
- def sort_disk_order(self):
- '''Sort partitions/slices in disk order'''
- self.partitions.sort(cmp=PartitionInfo.compare)
- self.slices.sort(cmp=SliceInfo.compare)
-
- def get_parts(self):
- '''Return the list of partitions or slices, depending on what
- this disk has on it
-
- '''
- if self.partitions:
- return self.partitions
- else:
- return self.slices
-
- def to_tgt(self):
- '''Transfer the install profile information to tgt format
-
- '''
- if self._tgt_disk is not None:
- tgt_disk = self._tgt_disk
- else:
- name = self.name
- blocks = round_to_multiple(self.get_blocks(), self.cylsz)
- controller = self.controller
- boot = self.boot
- removable = self.removable
- vendor = self.vendor
- serialno = self.serialno
- geo = tgt.Geometry(cylsz=self.cylsz, blocksz=self.blocksz)
- tgt_disk = tgt.Disk(geo, name, blocks, controller=controller,
- boot=boot, removable=removable,
- vendor=vendor, serialno=serialno)
-
- backup_slice = None
- if self.partitions:
- sl_iter = iter(self.get_solaris_data().slices)
- else:
- sl_iter = iter(self.slices)
- for slice_ in sl_iter:
- if slice_.number == SliceInfo.BACKUP_SLICE:
- backup_slice = slice_._tgt_slice
- break
-
- tgt_disk.use_whole = self.use_whole_segment
-
- child_list = ()
- if not tgt_disk.use_whole:
- for partition in self.partitions:
- part = partition.to_tgt(self)
- if part is not None:
- child_list += (part,)
- tgt_disk.fdisk = True
- if not child_list:
- for slice_info in self.slices:
- sl = slice_info.to_tgt(self)
- if sl is not None:
- child_list += (sl,)
- tgt_disk.vtoc = True
-
- tgt_disk.children = child_list
- slice_parent = tgt_disk
- if child_list and isinstance(child_list[0], tgt.Partition):
- standards = []
- logicals = []
- ext_part = None
- for child in child_list:
- if child.id == PartitionInfo.DELETED:
- continue
- if child.number > PartitionInfo.MAX_STANDARD_PARTITIONS:
- logicals.append(child)
- else:
- standards.append(child)
- if child.id in PartitionInfo.EXTENDED:
- ext_part = child
- if child.id == PartitionInfo.SOLARIS:
- slice_parent = child
- adjust_tgt_parts(standards, tgt_parent=tgt_disk)
- if logicals:
- adjust_tgt_parts(logicals, ext_part=ext_part)
-
- slices = []
- for child in slice_parent.children:
- if child.number == SliceInfo.BACKUP_SLICE:
- continue
- slices.append(child)
- if backup_slice is not None:
- slice_parent = backup_slice
- adjust_tgt_parts(slices, tgt_parent=slice_parent)
-
- # print out the tgt_disk object for debugging
- logging.debug("%s", tgt_disk)
-
- return tgt_disk
-
- def create_default_layout(self):
- '''Create a reasonable default layout consisting of a single slice
- or partition that consumes the whole disk. In the slice case, also
- add the traditional backup slice.
-
- '''
- # do not allow size to exceed MAX_VTOC
- maxsz = min(self.get_size(), SliceInfo.MAX_VTOC)
-
- if platform.processor() == "sparc":
- whole_part = SliceInfo(slice_num=0, size=self.size,
- slice_type=SliceInfo.ROOT_POOL)
- backup_part = SliceInfo(slice_num=SliceInfo.BACKUP_SLICE,
- size=self.size)
- self.slices = [whole_part, backup_part]
- self.label.add(DiskInfo.VTOC)
- else:
- whole_part = PartitionInfo(part_num=1, size=maxsz,
- partition_id=PartitionInfo.SOLARIS)
- whole_part.create_default_layout()
- self.partitions = [whole_part]
- self.label.add(DiskInfo.FDISK)
-
- def get_install_dev_name_and_size(self):
- '''Returns the installation device name string and the size of the
- install device in MB.
-
- '''
- install_target = self.get_install_target()
- if install_target is None:
- logging.error("Failed to find device to install onto")
- raise InstallationError
- name = self.name + "s" + str(install_target.number)
- size = (int)(install_target.size.size_as("mb"))
- return (name, size)
-
- def get_install_device_size(self):
- '''Returns the size of the install device in MB. '''
- # Size is the second item in the tuple
- return self.get_install_dev_name_and_size()[1]
-
- def get_install_device(self):
- '''Returns the install device name string. '''
- # Install device is the first item in the tuple
- return self.get_install_dev_name_and_size()[0]
-
- def get_install_target(self):
- '''Returns the slice target of this installation'''
- try:
- install_target = self
- if install_target.partitions:
- install_target = install_target.get_solaris_data()
- install_target = install_target.get_solaris_data()
- return install_target
- except AttributeError:
- logging.debug("Install target not yet defined")
- return None
-
- def get_install_root_pool(self):
- ''' Returns name of the pool to be used for installation '''
- install_slice = self.get_install_target()
- if install_slice is None:
- logging.error("Failed to find device to install onto")
- raise InstallationError
- slice_type = install_slice.get_type()
- return (str(slice_type[1]))
--- a/usr/src/cmd/text-install/osol_install/profile/disk_space.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Object representing space on a disk (for example, a file, partition or slice)
-'''
-
-
-def round_to_multiple(num, mult):
- ''' Round the given number (num) up to a given multiple (mult) '''
- remainder = num % mult
- if (remainder == 0):
- return (num) # num is already of the given multiple
- return (num + (mult - remainder))
-
-
-def round_down(num, mult):
- ''' Round the given number (num) down to a given multiple (mult) '''
- return (num - (num % mult))
-
-
-class DiskSpace(object):
- '''Represent a disk, partition or file's size in
- bytes, kb, mb, gb or tb
-
- '''
- SIZES = {"b" : 1,
- "kb" : 1024,
- "mb" : 1024**2,
- "gb" : 1024**3,
- "tb" : 1024**4}
-
- def __init__(self, size=None):
- '''size must be a string with a suffix of b, kb, mb, gb or tb'''
- self._size = 0
- self.size = size
-
- def __str__(self):
- return self.size_as_string()
-
- def set_size(self, size):
- '''Set the size to the string specified'''
- if size is None:
- self._size = 0
- else:
- self._size = DiskSpace.check_format(size)
-
- def size_as_string(self, unit_str="gb"):
- '''Return a string representing this object's size in the indicated
- units
-
- '''
- return str(self.size_as(unit_str)) + unit_str
-
- def size_as(self, unit_str="gb"):
- '''Return the size of this DiskSpace converted in scale to unit_str.
- unit_str defaults to gigabytes
-
- unit_str must be in DiskSpace.SIZES
- '''
- unit_str = unit_str.lower()
- if unit_str in DiskSpace.SIZES:
- return (self._size / DiskSpace.SIZES[unit_str])
- else:
- raise ValueError("%s not a recognized suffix" % unit_str)
-
- size = property(size_as, set_size)
-
- @staticmethod
- def check_format(input_str):
- '''Analyze a string to determine if it could represent a DiskSpace
-
- input_str must be a string object, or a TypeError will be raised
-
- Returns the object's size in bytes if input_str could represent
- a DiskSpace, and raises ValueError otherwise
-
- '''
- if not isinstance(input_str, basestring):
- raise TypeError("input_str must be a string")
- input_str = input_str.strip()
- # Determine whether the units are represented by a single character
- # (bytes, b), or two characters (kb, mb, gb, tb)
- if input_str[-2:-1].isdigit():
- units = input_str[-1:].lower()
- value = input_str[:-1]
- else:
- units = input_str[-2:].lower()
- value = input_str[:-2]
- if units in DiskSpace.SIZES:
- if value:
- # Raises a ValueError if value isn't a parseable float
- return float(value) * DiskSpace.SIZES[units]
- else:
- raise TypeError("Invalid value (%s)" % input_str)
- else:
- raise ValueError("Invalid units (%s)" % units)
-
- def __cmp__(self, other):
- if self._size > other._size:
- return 1
- if self._size < other._size:
- return -1
- return 0
--- a/usr/src/cmd/text-install/osol_install/profile/install_profile.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Represent all details of an installation in a single Python object,
-including the disk target, netwrok configuration, system details (such as
-timezone and locale), users, and zpool / zfs datasets.
-'''
-
-
-from solaris_install.data_object import DataObject
-
-
-INSTALL_PROF_LABEL = "install_profile"
-
-
-class InstallProfile(DataObject):
- '''
- Represents an entire installation profile
- '''
-
- LABEL = INSTALL_PROF_LABEL
-
- # pylint: disable-msg=E1003
- def __init__(self, disk=None, zpool=None):
- super(DataObject, self).__init__(InstallProfile.LABEL)
- self.disk = disk
- self.zpool = zpool
- self.install_succeeded = False
-
- def __str__(self):
- result = ["Install Profile:"]
- result.append("Install Completed - %s" % self.install_succeeded)
- result.append(str(self.disk))
- result.append(str(self.zpool))
- return "\n".join(result)
-
- ## InstallProfile not intended for long term use as a DataObject
- ## It's expected that this will be replaced with formal DataObject
- ## structures as the Text Installer transitions to use the InstallEngine
- ## and DOC more completely.
- def to_xml(self):
- return None
-
- @classmethod
- def from_xml(cls, xml_node):
- return None
-
- @classmethod
- def can_handle(cls, xml_node):
- return False
-
- def __getstate__(self):
- '''Do not 'pickle' InstallProfiles'''
- return None
--- a/usr/src/cmd/text-install/osol_install/profile/partition_info.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,648 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Object to represent Partitions
-'''
-
-from copy import copy, deepcopy
-import logging
-
-import osol_install.tgt as tgt
-from osol_install.profile.disk_space import DiskSpace, round_to_multiple, \
- round_down
-from osol_install.profile.slice_info import SliceInfo, UI_PRECISION
-
-# Minimum space between partitions before presenting it as 'unused' space
-# Used by PartitionInfo.add_unused_parts()
-MIN_GAP_SIZE = DiskSpace("1gb")
-
-# Minimum space between logical partitions before presenting it as
-# 'unused' space. Used by DiskInfo.add_unused_parts()
-MIN_LOGICAL_GAP_SIZE = DiskSpace("100mb")
-
-# Pylint gets confused and thinks PartitionInfo.size and PartitionInfo.offset
-# are strings, but they are actually DiskSpace objects
-# pylint: disable-msg=E1103
-class PartitionInfo(object):
- '''Represents a single partition on a disk on the system.
-
- EDITABLE_PART_TYPES is a list of primary partition types for which
- modifying the size is supported.
-
- KNOWN_PART_TYPES is a list of primary partition types which can be
- created out of a previously unused block of space
-
- *_LOGIC_TYPES represent the same concepts applied to logical partitions'''
-
- UNUSED = None
- UNUSED_TEXT = "Unused"
- SOLARIS = 0xBF
- SOLARIS_ONE = 0x82
- EXT_DOS = 0x05
- FDISK_EXTLBA = 0x0f
- DELETED = 0
-
- LOGICAL_BLOCK_PAD = 63
-
- EXTENDED = [EXT_DOS, FDISK_EXTLBA]
- EXTENDED_TEXT = "Extended"
-
- EDITABLE_PART_TYPES = [SOLARIS,
- EXT_DOS,
- FDISK_EXTLBA]
-
- EDITABLE_LOGIC_TYPES = [SOLARIS]
-
- KNOWN_PART_TYPES = [UNUSED,
- SOLARIS,
- EXT_DOS]
-
- KNOWN_LOGIC_TYPES = [UNUSED]
- KNOWN_LOGIC_TYPES.extend(EDITABLE_LOGIC_TYPES)
-
- MIN_OFFSET = 1
-
- MAX_STANDARD_PARTITIONS = 4
- MAX_LOGICAL_PARTITIONS = 32
- FIRST_LOGICAL = 5
-
- def __init__(self, part_num=None, offset="0gb", size="0gb", slices=None,
- blocksz=512, partition_id=UNUSED, tgt_part=None):
- '''Constructor takes either a tgt_part, which should be a tgt.Partition
- object, or a set of parameters. If tgt_part is supplied, all other
- parameters are ignored.
-
- '''
- self._sol_slice_idx = 0
- self._offset = None
- self._size = None
- self._tgt_part = tgt_part
- self.original_type = None
- self.previous_size = None
- if tgt_part:
- self.slices = []
- for child in tgt_part.children:
- self.slices.append(SliceInfo(tgt_slice=child))
- self.number = tgt_part.number
- offset = str(tgt_part.offset * tgt_part.geometry.blocksz) + "b"
- self.offset = offset
- size = str(tgt_part.blocks * tgt_part.geometry.blocksz) + "b"
- self.blocksz = tgt_part.geometry.blocksz
- self.size = size
- self.id = tgt_part.id
- else:
- if slices is None:
- slices = []
- self.slices = slices
- self.number = part_num
- self.offset = offset
- self.blocksz = blocksz
- self.size = size
- self.id = partition_id
- self.use_whole_segment = False
- self.boot_slice = None
- self.alt_slice = None
- self.orig_slices = copy(self.slices)
-
- def __str__(self):
- result = ["Partition Info (%s):" % self.number]
- result.append("Type: %s" % self.type)
- result.append("Offset: %s" % self.offset)
- result.append("Size: %s" % self.size)
- for slice_info in self.slices:
- result.append(str(slice_info))
- return "\n".join(result)
-
- @staticmethod
- def compare(left, right):
- '''Compare 2 tgt.Partition or PartitionInfo's in such a way that
- passing this method to list.sort() results in a list sorted by disk
- order.
-
- '''
- if isinstance(left, tgt.Partition):
- left = PartitionInfo(left)
- if not isinstance(left, PartitionInfo):
- return NotImplemented
-
- if isinstance(right, tgt.Partition):
- right = PartitionInfo(right)
- if not isinstance(right, PartitionInfo):
- return NotImplemented
-
- if left.is_logical() == right.is_logical():
- left_off = left.offset.size_as("b")
- right_off = right.offset.size_as("b")
- if left_off < right_off:
- return -1
- elif left_off > right_off:
- return 1
- else:
- return 0
- elif left.is_logical():
- return 1
- else:
- return -1
-
- def get_offset(self):
- '''Return this partition's offset as a DiskSpace object'''
- return self._offset
-
- def set_offset(self, offset):
- '''Set this partition's offset. Must be either a DiskSpace object
- or a string that will be accepted by DiskSpace.__init__
-
- '''
- if isinstance(offset, DiskSpace):
- self._offset = deepcopy(offset)
- else:
- self._offset = DiskSpace(offset)
-
- def get_size(self):
- '''Returns this partition's size as a DiskSpace object'''
- return self._size
-
- def set_size(self, size):
- '''Set this partition's size. size must be either a DiskSpace or a
- string that will be accepted by DiskSpace.__init__
-
- '''
- if isinstance(size, DiskSpace):
- self._size = deepcopy(size)
- else:
- self._size = DiskSpace(size)
-
- def get_type(self):
- '''Return this object's partition type'''
- return self.id
-
- def get_blocks(self):
- '''Return the number of blocks on this partition'''
- return int(self.size.size_as("b") / self.blocksz)
-
- blocks = property(get_blocks)
- offset = property(get_offset, set_offset)
- size = property(get_size, set_size)
- type = property(get_type)
-
- def is_logical(self):
- '''Returns true if this is a logical partition'''
- if self.number is not None:
- return (self.number > PartitionInfo.MAX_STANDARD_PARTITIONS)
- else:
- return False
-
- def is_extended(self):
- '''Returns True if this is an Extended Partition, False otherwise'''
- return (self.id in PartitionInfo.EXTENDED)
-
- def cycle_type(self, disk, extra_types=None):
- '''Cycle this partition's type. Potential types are based on
- whether or not another Solaris partition exists, whether an extended
- partition exists, and whether or not this is a logical partition.
-
- If extra_types is passed in, it should be a list of other potential
- types. These types will also be considered when cycling.
-
- '''
- if extra_types is None:
- extra_types = []
- types = set()
- sol2_part = disk.get_solaris_data()
- has_solaris_part = (sol2_part is not None)
-
- ext_part = disk.get_extended_partition()
- has_extended = (ext_part is not None)
-
- # If this is the extended partition, and the Solaris2 partition
- # is a logical partition, allow for cycling from Extended to Solaris2
- if (has_extended and ext_part is self and
- has_solaris_part and sol2_part.is_logical()):
- has_solaris_part = False
-
- if self.is_logical():
- types.update(PartitionInfo.KNOWN_LOGIC_TYPES)
- else:
- types.update(PartitionInfo.KNOWN_PART_TYPES)
- types.update(extra_types)
- types = list(types)
- types.sort()
- if self.id in types:
- logging.debug("type in types, cycling next")
- type_index = types.index(self.id)
- type_index = (type_index + 1) % len(types)
- self.id = types[type_index]
- logging.debug("now %s", self.id)
- else:
- logging.debug("type NOT in types, setting to types[0]")
- self.original_type = self.id
- self.id = types[0]
- if self.id == PartitionInfo.UNUSED:
- self.previous_size = self.size
- self.size = "0GB"
- elif has_solaris_part and self.id == PartitionInfo.SOLARIS:
- self.cycle_type(disk, extra_types)
- elif has_extended and self.is_extended():
- self.cycle_type(disk, extra_types)
-
- def get_description(self):
- '''
- Return a string suitable for representing this partition in a UI
- '''
- description = None
- if self.id == PartitionInfo.UNUSED:
- description = PartitionInfo.UNUSED_TEXT
- elif self.is_extended():
- description = PartitionInfo.EXTENDED_TEXT
- else:
- description = tgt.Partition.ID[self.id]
- return str(description)
-
- def get_max_size(self, disk):
- '''Analyze nearby partitions and determine the total unused, available
- space that this partition could consume without affecting other
- partitions.
-
- Result is in gigabytes
-
- '''
- if self.is_logical():
- parts = disk.get_logicals()
- ext_part = disk.get_extended_partition()
- else:
- parts = disk.get_standards()
- if self not in parts:
- raise ValueError("This partition was not found on the "
- "supplied disk")
- self_idx = parts.index(self)
- prev_part = None
- next_part = None
- for part in reversed(parts[:self_idx]):
- if part.id != PartitionInfo.UNUSED:
- prev_part = part
- break
- for part in parts[self_idx+1:]:
- if part.id != PartitionInfo.UNUSED:
- next_part = part
- break
- msg_str = self.get_description() + ":"
- if prev_part is None:
- msg_str += "No prev part:"
- if self.is_logical():
- begin_avail_space = ext_part.offset.size_as("gb")
- else:
- begin_avail_space = 0
- else:
- try:
- begin_avail_space = prev_part.get_endblock().size_as("gb")
- msg_str += ("prev_part(%s).endblock=%s:" %
- (prev_part.type, begin_avail_space))
- except Exception:
- logging.error("%s", prev_part)
- raise
- if next_part is None:
- if self.is_logical():
- msg_str += ("no next_part (ext_part size=%s):" %
- ext_part.size.size_as("gb"))
- end_avail_space = ext_part.get_endblock().size_as("gb")
- else:
- msg_str += ("no next_part (disk_size=%s):" %
- disk.size.size_as("gb"))
- end_avail_space = disk.size.size_as("gb")
- else:
- end_avail_space = next_part.offset.size_as("gb")
- msg_str += ("next_part(%s).offset=%s:" %
- (next_part.type, end_avail_space))
- logging.debug(msg_str)
- avail = min(end_avail_space - begin_avail_space,
- SliceInfo.MAX_VTOC.size_as("gb"))
- if avail < 0:
- avail = 0
- return avail
-
- def get_endblock(self):
- '''Returns the ending 'offset' of this partition, as a DiskSpace'''
- try:
- start_pt = self.offset.size_as("b")
- end_pt = self.size.size_as("b")
- return DiskSpace(str(start_pt + end_pt) + "b")
- except AttributeError:
- raise AttributeError("%s does not have valid size data" %
- self.__class__.__name__)
-
- def get_solaris_data(self, check_multiples=False):
- '''Returns the slice within this partition that has the Solaris root
- pool.
-
- Raises AttributeError if there is no such slice
-
- '''
- if (not check_multiples and self._sol_slice_idx < len(self.slices) and
- self.slices[self._sol_slice_idx].is_solaris_data()):
- return self.slices[self._sol_slice_idx]
-
- solaris_data = None
- for slice_info in self.slices:
- if slice_info.is_solaris_data():
- if solaris_data is None:
- self._sol_slice_idx = self.slices.index(slice_info)
- solaris_data = slice_info
- if not check_multiples:
- break
- elif check_multiples:
- raise ValueError("Found multiple slices with 'solaris'"
- "data on them")
-
- return solaris_data
-
- def editable(self, disk):
- '''Returns True if it is possible to edit this partition's size'''
- if self.is_extended():
- for logical in disk.get_logicals():
- if logical.id != PartitionInfo.UNUSED:
- return False
- if self.id in PartitionInfo.EDITABLE_PART_TYPES:
- return True
- else:
- return False
-
- def sort_disk_order(self):
- '''Sort slices by disk order'''
- self.slices.sort(cmp=SliceInfo.compare)
-
- def get_parts(self):
- '''Return the slices on this partition. Provided for interface
- compatibility with DiskInfo.get_parts()
-
- '''
- return self.slices
-
- def add_unused_parts(self):
- '''Sort through the non-logical partitions, find the largest gaps,
- and create additional Unused partitions such that the number of
- non-logical partitions == MAX_STANDARD_PARTITIONS.
-
- Gaps smaller than 1 GB are ignored.
-
- Also note that the x86 boot slice (8) and x86 alt slice (9) are
- hidden from view after calling this method. They're stored for
- later retrieval in self.boot_slice and self.alt_slice respectively
-
- '''
- boot_slice = None
- for part in self.slices:
- if part.number == SliceInfo.x86_BOOT_SLICE:
- boot_slice = part
- if boot_slice is not None:
- self.boot_slice = boot_slice
- self.slices.remove(boot_slice)
-
- alt_slice = None
- for part in self.slices:
- if part.number == SliceInfo.x86_ALT_SLICE:
- alt_slice = part
- if alt_slice is not None:
- self.alt_slice = alt_slice
- self.slices.remove(alt_slice)
-
- parts = copy(self.slices)
- parts.sort(cmp=SliceInfo.compare)
- numbers = range(SliceInfo.MAX_SLICES)
- numbers.remove(SliceInfo.BACKUP_SLICE)
- backup_part = None
- for part in parts:
- if part.number == SliceInfo.BACKUP_SLICE:
- backup_part = part
- break
- if backup_part is not None:
- parts.remove(backup_part)
- start_pt = 0
-
- min_gap_size = MIN_GAP_SIZE.size_as("b")
- gaps = []
- end_pt = 0
- for part in parts:
- if part.number == SliceInfo.BACKUP_SLICE:
- continue
- if part.number in numbers:
- numbers.remove(part.number)
- start_pt = part.offset.size_as("b")
- gap_size = start_pt - end_pt
- if gap_size > min_gap_size:
- gaps.append((gap_size, end_pt))
- end_pt = part.get_endblock().size_as("b")
- end_disk = self.size.size_as("b")
- gap_size = end_disk - end_pt
- if gap_size > min_gap_size:
- gaps.append((gap_size, end_pt))
- # Sorting a list of tuples will sort by the first item in the tuple,
- # In this case, gap_size, such that the smallest gap is first.
- # Then, the largest gaps can be popped off the end of the list
- gaps.sort()
- for part_num in numbers:
- if gaps:
- gap = gaps.pop()
- offset = str(gap[1]) + "b"
- else:
- offset = str(end_pt) + "b"
- new_part = SliceInfo(slice_num=part_num, offset=offset)
- self.slices.append(new_part)
- if len(self.slices) >= SliceInfo.MAX_SLICES:
- break
- if backup_part is None:
- new_part = SliceInfo(slice_num=SliceInfo.BACKUP_SLICE,
- size=self.size)
- self.slices.append(new_part)
- self.sort_disk_order()
-
- def adjust_offset(self, parent):
- '''Adjust this partition's offset such that it no longer overlaps
- with subsequent partitions, by comparing this partition's trailing
- edge with the next used partition's offset.
-
- Additionally, any unused partitions found are shifted to align
- with this partition's trailing edge, if needed
-
- '''
- if self.is_logical():
- parts = parent.get_logicals()
- ext_part = parent.get_extended_partition()
- else:
- parts = parent.get_standards()
-
- self_idx = parts.index(self)
- endblock = self.get_endblock()
- endblock_bytes = endblock.size_as("gb")
- unused_parts = []
- shift = None
- for part in parts[self_idx+1:]:
- if part.offset.size_as("gb") < endblock_bytes:
- if part.id == PartitionInfo.UNUSED:
- unused_parts.append(part)
- else:
- shift = endblock_bytes - part.offset.size_as("gb")
- break
- else:
- break
- else:
- # Check to ensure we don't slip past the end of the disk
- # (or extended partition, if this is a logical partition)
- if self.is_logical():
- max_endblock = ext_part.get_endblock().size_as("gb")
- else:
- max_endblock = parent.size.size_as("gb")
- if endblock_bytes > max_endblock:
- shift = endblock_bytes - max_endblock
-
- if shift is not None:
- new_offset = max(0, self.offset.size_as("gb") - shift)
- self.offset = str(new_offset) + "gb"
-
- new_endblock = self.get_endblock()
- for part in unused_parts:
- part.offset = new_endblock
-
- def to_tgt(self, parent):
- '''Transfer the install profile information to tgt format'''
-
- if not self.modified():
- part = deepcopy(self._tgt_part)
- else:
- # Something changed, need to create a new one
- geo = tgt.Geometry(parent.cylsz, self.blocksz)
-
- if self.type == PartitionInfo.UNUSED:
- # Partition was deleted. Return an empty partition,
- # which will indicate to target instantiation to
- # delete this partition
- return tgt.Partition(geo, self.number, PartitionInfo.DELETED,
- 0, 0, modified=True)
-
- offset = int(self.offset.size_as("b") / self.blocksz)
- offset = max(PartitionInfo.MIN_OFFSET, offset)
- blocks = self.get_blocks()
-
- if self.is_logical() or self.is_extended():
- # Ensure that the required minimum amount of empty space
- # precedes and follows this logical partition
- offset += PartitionInfo.LOGICAL_BLOCK_PAD
- blocks -= 2 * PartitionInfo.LOGICAL_BLOCK_PAD
-
- # offset must be a multiple of tgt.Geometry.cylsz
- offset = round_to_multiple(offset, geo.cylsz)
- blocks = round_down(blocks, geo.cylsz)
-
- part = tgt.Partition(geo, self.number, self.id, offset, blocks,
- modified=True)
-
- part.use_whole = self.use_whole_segment
-
- child_list = ()
- if not part.use_whole:
- slices = []
- if self.boot_slice is not None:
- slices.append(self.boot_slice)
- if self.alt_slice is not None:
- slices.append(self.alt_slice)
- slices.extend(self.slices)
- for slice_info in slices:
- sl = slice_info.to_tgt(parent)
- if sl is not None:
- child_list += (sl,)
-
- part.children = child_list
- return (part)
-
- def modified(self, off_by=UI_PRECISION):
- '''Returns False if and only if this PartitionInfo was instantiated
- from a tgt.Partition, and this PartitionInfo does not differ in
- substance from the tgt.Partition from which it was instantiated.
-
- Size, offset, id and number are compared to determine whether this
- partition has been modified. Slices within this partition are not
- considered.
-
- off_by - A string or DiskSpace indicating a rounding factor. Any size
- data (offset, size) that differs by less than the given amount is
- assumed to be unchanged. e.g., if the tgt.Partition indicates a size
- of 10.05GB and this PartitionInfo has a size of 10.1GB, and off_by
- is the default of 0.1GB, then it is assumed that the represented
- partition has not changed. (The original tgt.Partition size should be
- used, for accuracy)
-
- '''
- if self._tgt_part is None:
- return True
-
- if not isinstance(off_by, DiskSpace):
- off_by = DiskSpace(off_by)
- off_by_bytes = off_by.size_as("b")
-
- if self.number != self._tgt_part.number:
- return True
-
- if self.id != self._tgt_part.id:
- return True
-
- tgt_size = self._tgt_part.blocks * self._tgt_part.geometry.blocksz
- if abs(tgt_size - self.size.size_as("b")) > off_by_bytes:
- return True
-
- tgt_offset = self._tgt_part.offset * self._tgt_part.geometry.blocksz
- if abs(tgt_offset - self.offset.size_as("b")) > off_by_bytes:
- return True
-
- return False
-
- def destroyed(self, off_by=UI_PRECISION):
- '''Returns True if this partition previously had data, and has also
- been modified. Also returns True if this is the Solaris2 partition,
- but the partition originally had no slices - the slice editing screen
- is skipped in such a case, so the user needs to be informed that the
- entire contents of the Solaris2 partition will be destroyed (as the
- entire partition will be used as the install target)
-
- '''
- if self._tgt_part is None:
- return False
- modified = self.modified(off_by)
- return modified or (self.is_solaris_data() and not self.orig_slices)
-
- def create_default_layout(self):
- '''Create a reasonable default layout, consisting of a single
- slice that consumes the entire partition (as well as defining the
- traditional backup slice)
-
- '''
- whole_part = SliceInfo(slice_num=0, size=self.size,
- slice_type=SliceInfo.ROOT_POOL)
- backup_part = SliceInfo(slice_num=SliceInfo.BACKUP_SLICE,
- size=self.size)
- self.slices = [whole_part, backup_part]
-
- def is_solaris_data(self):
- '''Returns True if this PartitionInfo would be the install target'''
- return self.id == PartitionInfo.SOLARIS
-
--- a/usr/src/cmd/text-install/osol_install/profile/slice_info.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,467 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Object to represent Slices
-'''
-
-from copy import deepcopy
-from UserString import UserString
-import logging
-
-import osol_install.tgt as tgt
-from osol_install.profile.disk_space import DiskSpace, round_to_multiple
-
-UI_PRECISION = DiskSpace("0.05gb")
-
-
-# Pylint gets confused and thinks SliceInfo.size and SliceInfo.offset
-# are strings, but they are actually DiskSpace objects
-# pylint: disable-msg=E1103
-class SliceInfo(object):
- '''Represents a single slice on a partition or slice.'''
- MAX_SLICES = 8
- MAX_VTOC = DiskSpace("2tb")
-
- BACKUP_SLICE = 2
- x86_BOOT_SLICE = 8
- x86_ALT_SLICE = 9
- UNUSED = (None, None)
- UNUSED_TEXT = "Unused"
- DEFAULT_POOL = UserString("")
- ZPOOL = tgt.Slice.AZPOOL
- ZPOOL_TYPES = [tgt.Slice.AZPOOL, tgt.Slice.EZPOOL, tgt.Slice.SZPOOL,
- tgt.Slice.CZPOOL]
- ROOT_POOL = (ZPOOL, DEFAULT_POOL)
- LEGACY = "legacy"
- UFS = tgt.Slice.FS
- UFS_TEXT = "UFS"
- UNKNOWN = "???"
-
- TYPES = [UNUSED,
- ROOT_POOL]
-
- def __init__(self, slice_num=0, size=None, offset=None, blocksz=512,
- slice_type=None, readonly=False, unmountable=True,
- tag=None, tgt_slice=None):
- '''Constructor takes either a tgt_slice, which should be a tgt.Slice
- object, or a set of parameters. If tgt_slice is supplied, all other
- parameters are ignored.
-
- '''
- self._tgt_slice = tgt_slice
- self._size = None
- self._offset = None
- self.previous_size = None
- if tgt_slice:
- size = str(tgt_slice.blocks * tgt_slice.geometry.blocksz) + "b"
- self.size = size
- offset = str(tgt_slice.offset * tgt_slice.geometry.blocksz) + "b"
- self.blocksz = tgt_slice.geometry.blocksz
- self.offset = offset
- self.number = tgt_slice.number
- self.readonly = tgt_slice.readonly
- self.type = (tgt_slice.type, tgt_slice.user)
- self.last_mount = tgt_slice.last_mount
- self.unmountable = tgt_slice.unmountable
- self.tag = tgt_slice.tag
- else:
- self.readonly = readonly
- if slice_type is None:
- slice_type = SliceInfo.UNUSED
- if len(slice_type) != 2:
- raise TypeError("slice_type must be tuple of length 2")
- self.type = slice_type
- self.unmountable = unmountable
- self.size = size
- self.blocksz = blocksz
- self.offset = offset
- self.number = slice_num
- self.last_mount = None
- self.tag = tag
- self.original_type = self.type
-
- def __str__(self):
- result = ["Slice Info (%s):" % self.number]
- result.append("Type: %s:%s" % self.type)
- result.append("Offset: %s" % self.offset)
- result.append("Size: %s" % self.size)
- return "\n".join(result)
-
- @staticmethod
- def compare(left, right):
- '''Returns an integer such that this method can be passed to
- list.sort() and the list will be sorted in disk layout order.
-
- The backup slice is always listed last
-
- '''
- if isinstance(left, tgt.Slice):
- left = SliceInfo(left)
- if not isinstance(left, SliceInfo):
- return NotImplemented
-
- if isinstance(right, tgt.Slice):
- right = SliceInfo(right)
- if not isinstance(right, SliceInfo):
- return NotImplemented
-
- if left.number == SliceInfo.BACKUP_SLICE:
- return 1
- elif right.number == SliceInfo.BACKUP_SLICE:
- return -1
-
- left_off = left.offset.size_as("b")
- right_off = right.offset.size_as("b")
- if left_off < right_off:
- return -1
- elif left_off > right_off:
- return 1
- else:
- return 0
-
- def get_offset(self):
- '''Return this slice's offset as a DiskSpace object'''
- return self._offset
-
- def set_offset(self, offset):
- '''Set this slice's offset. Must be either a DiskSpace object
- or a string that will be accepted by DiskSpace.__init__
-
- '''
- if isinstance(offset, DiskSpace):
- self._offset = deepcopy(offset)
- else:
- self._offset = DiskSpace(offset)
-
- def get_size(self):
- '''Returns this slice's size as a DiskSpace object'''
- return self._size
-
- def set_size(self, size):
- '''Set this slice's size. size must be either a DiskSpace or a
- string that will be accepted by DiskSpace.__init__
-
- '''
- if isinstance(size, DiskSpace):
- self._size = deepcopy(size)
- else:
- self._size = DiskSpace(size)
-
- def get_type(self):
- '''Returns this SliceInfo's 'type'
- Here for interface compatibility with PartitionInfo.get_type()'''
- return self.type
-
- def get_blocks(self):
- '''Return the number of blocks on this slice'''
- return int(self.size.size_as("b") / self.blocksz)
-
- size = property(get_size, set_size)
- offset = property(get_offset, set_offset)
-
- def get_description(self):
- '''Return a string suitable for representing this slice in a UI'''
- description = None
- if self.number == SliceInfo.BACKUP_SLICE:
- description = tgt.Slice.BACKUP
- elif self.type == SliceInfo.UNUSED:
- description = SliceInfo.UNUSED_TEXT
- elif self.type[0] == SliceInfo.UFS:
- if self.last_mount:
- description = self.last_mount
- else:
- description = SliceInfo.UFS_TEXT
- elif self.type[0] == tgt.Slice.UNKNOWN:
- if self.tag == tgt.Slice.UNKNOWN:
- description = SliceInfo.UNKNOWN
- else:
- description = self.tag
- elif self.type[1] and self.type[1] != tgt.Slice.UNKNOWN:
- description = self.type[1]
- else:
- description = self.type[0]
- return str(description)
-
- def cycle_type(self, parent, extra_types=None):
- '''Cycle this partition's type. If extra_types is given, it should
- be a list of additional types - these will be considered when cycling
- to the next type
-
- '''
- if extra_types is None:
- extra_types = []
- if self.number == SliceInfo.BACKUP_SLICE:
- return
-
- has_solaris_data = (parent.get_solaris_data() is not None)
- types = set()
- types.update(SliceInfo.TYPES)
- types.update(extra_types)
- types = list(types)
- types.sort()
- if self.type in types:
- logging.debug("type in types, cycling next")
- type_index = types.index(self.type)
- type_index = (type_index + 1) % len(types)
- self.type = types[type_index]
- logging.debug("now %s-%s", *self.type)
- else:
- logging.debug("type NOT in types, setting to types[0]")
- self.original_type = self.type
- self.type = types[0]
- if self.type == SliceInfo.UNUSED:
- self.previous_size = self.size
- self.size = "0GB"
- elif self.is_rpool():
- if has_solaris_data:
- self.cycle_type(parent, extra_types)
-
- def get_endblock(self):
- '''Returns the ending 'offset' of this slice, as a DiskSpace'''
- try:
- start_pt = self.offset.size_as("b")
- end_pt = self.size.size_as("b")
- return DiskSpace(str(start_pt + end_pt) + "b")
- except AttributeError:
- raise AttributeError("%s does not have valid size data" %
- self.__class__.__name__)
-
- def get_max_size(self, parent):
- '''Return the maximum possible size this slice could consume,
- in gigabytes, based on adjacent unused space
-
- '''
- if self.number == SliceInfo.BACKUP_SLICE:
- return self.size.size_as("gb")
- msg_str = "get_max_size:%s:" % self.number
- slices = parent.slices
- if self not in slices:
- raise ValueError("This slice not in the parent!")
- self_idx = slices.index(self)
- prev_slice = None
- next_slice = None
-
- # Search for the slice prior to this one with the largest "endblock"
- # Since existing slices may overlap, this could be any slice prior
- # to the current one.
- for slice_info in reversed(slices[:self_idx]):
- if (slice_info.type != SliceInfo.UNUSED and
- slice_info.number != SliceInfo.BACKUP_SLICE):
- if (prev_slice is None or
- slice_info.get_endblock() > prev_slice.get_endblock()):
- prev_slice = slice_info
- for slice_info in slices[self_idx+1:]:
- if (slice_info.type != SliceInfo.UNUSED and
- slice_info.number != SliceInfo.BACKUP_SLICE):
- next_slice = slice_info
- break
- if prev_slice is None:
- msg_str += "prev_part=None:start_pt=0:"
- start_pt = 0
- else:
- msg_str += "prev_part=%s:" % prev_slice.number
- start_pt = prev_slice.get_endblock().size_as("gb")
- msg_str += "start_pt=" + str(start_pt) + ":"
-
- if next_slice is None:
- msg_str += "next_part=None:end_pt="
- for slice_info in reversed(slices):
- # Use the backup slice to define the absolute max size
- # any given slice can be. (This is usually the last slice,
- # hence the use of a reversed iterator)
- if slice_info.number == SliceInfo.BACKUP_SLICE:
- end_pt = slice_info.size.size_as("gb")
- break
- else:
- # Default to the parent's size if there happens to be no S2
- end_pt = parent.size.size_as("gb")
- msg_str += str(end_pt) + ":"
- else:
- msg_str += "next_part=%s:" % next_slice.number
- end_pt = next_slice.offset.size_as("gb")
- msg_str += "end_pt=%s:" % end_pt
- max_space = end_pt - start_pt
- if max_space < 0:
- max_space = 0
- msg_str += "max_size=%s" % max_space
- logging.debug(msg_str)
- return max_space
-
- def editable(self, dummy):
- '''Returns True if the installer is capable of resizing this Slice'''
- return self.is_rpool()
-
- def adjust_offset(self, parent):
- '''Adjust this slice's offset such that it no longer overlaps
- with prior or subsequent slices, by comparing this slice's
- offset with prior slices, and its endblock with subsequent ones.
-
- Additionally, any unused slices found are shifted to align
- with this slice's trailing edge, if needed.
-
- This function should only be called after ensuring that this slice's
- size is less than or equal its max_size (as given by get_max_size);
- the behavior of this function when attempting to adjust in both
- directions is undefined. Additionally, the slices on the parent
- should already be sorted in disk order.
-
- '''
- if self.number == SliceInfo.BACKUP_SLICE:
- return
- parts = parent.get_parts()
- self_idx = parts.index(self)
- endblock = self.get_endblock()
- endblock_bytes = endblock.size_as("gb")
- unused_parts = []
-
- pre_shift = 0
- for part in parts[:self_idx]:
- if (part.type == SliceInfo.UNUSED or
- part.number == SliceInfo.BACKUP_SLICE):
- continue
- overlap = (part.get_endblock().size_as("gb") -
- self.offset.size_as("gb"))
- pre_shift = max(pre_shift, overlap)
-
- if pre_shift > 0:
- new_offset = self.offset.size_as("gb") + pre_shift
- self.offset = str(new_offset) + "gb"
-
- post_shift = None
- for part in parts[self_idx+1:]:
- if part.offset.size_as("gb") < endblock_bytes:
- if part.type == SliceInfo.UNUSED:
- unused_parts.append(part)
- elif part.number != SliceInfo.BACKUP_SLICE:
- post_shift = endblock_bytes - part.offset.size_as("gb")
- break
- else:
- break
- else:
- # Check to ensure we don't slip past the end of the disk/partition
- max_endblock = parent.size.size_as("gb")
- if endblock_bytes > max_endblock:
- post_shift = endblock_bytes - max_endblock
-
- if post_shift is not None:
- new_offset = max(0, self.offset.size_as("gb") - post_shift)
- self.offset = str(new_offset) + "gb"
- new_endblock = self.get_endblock()
- for part in unused_parts:
- part.offset = new_endblock
-
- def to_tgt(self, parent):
- '''Transfer the install profile information to tgt format'''
- # Create tgt.Slice object
-
- if self.get_type() == SliceInfo.UNUSED:
- return None
-
- if not self.modified():
- return self._tgt_slice
-
- # Don't need to include the 'backup' slice, libti will
- # automatically create one appropriately
- if self.number == SliceInfo.BACKUP_SLICE:
- return None
-
- # Something changed, need to create a new one
- geo = tgt.Geometry(parent.cylsz, self.blocksz)
-
- # offset must be a multiple of tgt.Geometry.cylsz
- off = int(self.offset.size_as("b") / self.blocksz)
- offset = round_to_multiple(off, geo.cylsz)
-
- blocks = round_to_multiple(self.get_blocks(), geo.cylsz)
-
- tag = tgt.Slice.UNASSIGNED
- slice_type = self.type[0]
- user = self.type[1]
- sl = tgt.Slice(geo, self.number, tag, slice_type, offset, blocks,
- modified=True, user=str(user),
- unmountable=self.unmountable, readonly=self.readonly)
- return (sl)
-
- def modified(self, off_by=UI_PRECISION):
- '''Returns False if and only if this SliceInfo was instantiated from
- a tgt.Slice, and this SliceInfo does not differ in substance
- from the tgt.Slice from which it was instantiated.
-
- Size, offset, type and number are compared to determine
- whether this slice has been modified.
-
- off_by - A string or DiskSpace indicating a rounding factor. Any size
- data (offset, size) that differs by less than the given amount is
- assumed to be unchanged. e.g., if the tgt.Slice indicates a size
- of 10.05GB and this SliceInfo has a size of 10.1GB, and off_by
- is the default of 0.1GB, then it is assumed that the represented
- slice has not changed. (The original tgt.Slice size should be
- used, for accuracy)
-
- '''
- if self._tgt_slice is None:
- return True
-
- if not isinstance(off_by, DiskSpace):
- off_by = DiskSpace(off_by)
- off_by_bytes = off_by.size_as("b")
-
- if self.number != self._tgt_slice.number:
- return True
-
- if self.type[0] != self._tgt_slice.type:
- return True
-
- if self.type[1] != self._tgt_slice.user:
- return True
-
- tgt_size = self._tgt_slice.blocks * self._tgt_slice.geometry.blocksz
- if abs(tgt_size - self.size.size_as("b")) > off_by_bytes:
- return True
-
- tgt_offset = self._tgt_slice.offset * self._tgt_slice.geometry.blocksz
- if abs(tgt_offset - self.offset.size_as("b")) > off_by_bytes:
- return True
-
- return False
-
- def destroyed(self, off_by=UI_PRECISION):
- '''Returns True if this slice previously had data, and has also
- been modified.
-
- '''
- if self.is_rpool():
- return True
- return (self._tgt_slice is not None and self.modified(off_by))
-
- def is_rpool(self):
- '''Returns True this slice is the default pool
-
- '''
- return (self.type[0] in SliceInfo.ZPOOL_TYPES and
- self.type[1] == SliceInfo.DEFAULT_POOL)
-
- def is_solaris_data(self):
- return self.is_rpool()
--- a/usr/src/cmd/text-install/osol_install/text_install/Makefile Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-include ../../../Makefile.cmd
-
-all:= TARGET= all
-clean:= TARGET= clean
-clobber:= TARGET= clobber
-install:= TARGET= install
-
-MSG_DOMAIN = textinstall
-
-PROGS= text-install
-
-PYMODULES= __init__.py \
- disk_selection.py \
- disk_window.py \
- fdisk_partitions.py \
- install_progress.py \
- install_status.py \
- log_viewer.py \
- partition_edit_screen.py \
- summary.py \
- ti_install.py \
- ti_install_utils.py \
- welcome.py
-
-PYCMODULES= $(PYMODULES:%.py=%.pyc)
-
-ROOTPROGS= $(PROGS:%=$(ROOTUSRBIN)/%)
-
-ROOTPYMODULES= $(PYMODULES:%=$(ROOTPYTHONVENDORINSTALLTI)/%)
-
-ROOTPYCMODULES= $(PYCMODULES:%=$(ROOTPYTHONVENDORINSTALLTI)/%)
-
-MSGFILES = $(PYMODULES)
-
-.KEEP_STATE:
-
-all: python $(PROGS)
-
-clean:
- rm -f *.pyc $(MSG_DOMAIN).po*
-
-clobber: clean
-
-
-install: all .WAIT $(ROOTPROGS) \
- $(ROOTPYTHONVENDOR) \
- $(ROOTPYTHONVENDORINSTALL) \
- $(ROOTPYTHONVENDORINSTALLTI) \
- $(ROOTPYMODULES) \
- $(ROOTPYCMODULES) \
- .WAIT msgs
-
-python:
- $(PYTHON) -m compileall -l $(@D)
-
-msgs: $(MSG_DOMAIN).po
-
-$(MSG_DOMAIN).po: $(PYMODULES)
- @echo "Making messages file $(MSG_DOMAIN).po"
- $(GNUXGETTEXT) $(GNUXGETFLAGS) -d $(MSG_DOMAIN) \
- $(MSGFILES)
-
-FRC:
-
-include ../../../Makefile.targ
--- a/usr/src/cmd/text-install/osol_install/text_install/__init__.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,309 +0,0 @@
-#!/usr/bin/python2.6
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Text / (n)Curses based UI for installing Oracle Solaris
-'''
-
-import gettext
-
-# Variables used by modules this module imports
-# Defined here to avoid circular import errors
-_ = gettext.translation("textinstall", "/usr/share/locale",
- fallback=True).ugettext
-RELEASE = {"release" : _("Oracle Solaris")}
-TUI_HELP = "/usr/share/text-install/help"
-
-
-import curses
-import locale
-import logging
-from optparse import OptionParser
-import os
-import platform
-import signal
-import subprocess
-import sys
-import traceback
-
-import libbe_py
-from osol_install.liblogsvc import init_log
-from osol_install.profile.install_profile import InstallProfile, \
- INSTALL_PROF_LABEL
-from osol_install.text_install.disk_selection import DiskScreen
-from osol_install.text_install.fdisk_partitions import FDiskPart
-from osol_install.text_install.install_progress import InstallProgress
-from osol_install.text_install.install_status import InstallStatus, \
- RebootException
-from osol_install.text_install.log_viewer import LogViewer
-from osol_install.text_install.partition_edit_screen import PartEditScreen
-from osol_install.text_install.summary import SummaryScreen
-from osol_install.text_install.welcome import WelcomeScreen
-from solaris_install.engine import InstallEngine
-from solaris_install.logger import INSTALL_LOGGER_NAME
-import solaris_install.sysconfig as sysconfig
-import terminalui
-from terminalui import LOG_LEVEL_INPUT, LOG_NAME_INPUT
-from terminalui.action import Action
-from terminalui.base_screen import BaseScreen
-from terminalui.help_screen import HelpScreen
-from terminalui.i18n import get_encoding, set_wrap_on_whitespace
-from terminalui.main_window import MainWindow
-from terminalui.screen_list import ScreenList
-
-
-LOG_LOCATION_FINAL = "/var/sadm/system/logs/install_log"
-DEFAULT_LOG_LOCATION = "/tmp/install_log"
-DEFAULT_LOG_LEVEL = "info"
-DEBUG_LOG_LEVEL = "debug"
-LOG_FORMAT = ("%(asctime)-25s %(name)-10s "
- "%(levelname)-10s %(message)-50s")
-REBOOT = "/usr/sbin/reboot"
-
-
-def exit_text_installer(logname=None, errcode=0):
- '''Close out the logger and exit with errcode'''
- logging.info("**** END ****")
- logging.shutdown()
- if logname is not None:
- print _("Exiting Text Installer. Log is available at:\n%s") % logname
- if isinstance(errcode, unicode):
- errcode = errcode.encode(get_encoding())
- sys.exit(errcode)
-
-
-def setup_logging(logname, log_level):
- '''Initialize the logger, logging to logname at log_level'''
- log_level = log_level.upper()
- if hasattr(logging, log_level):
- log_level = getattr(logging, log_level.upper())
- elif log_level == LOG_NAME_INPUT:
- log_level = LOG_LEVEL_INPUT
- else:
- raise IOError(2, "Invalid --log-level parameter", log_level.lower())
- logging.basicConfig(filename=logname, level=log_level,
- filemode='w', format=LOG_FORMAT)
- logging.info("**** START ****")
- return log_level
-
-
-def make_screen_list(main_win):
- '''Initialize the screen list. On x86, add screens for editing slices
- within a partition. Also, trigger the target discovery thread.
-
- '''
-
- result = []
- result.append(WelcomeScreen(main_win))
- disk_screen = DiskScreen(main_win)
- disk_screen.start_discovery()
- result.append(disk_screen)
- result.append(FDiskPart(main_win))
- result.append(PartEditScreen(main_win))
- if platform.processor() == "i386":
- result.append(FDiskPart(main_win, x86_slice_mode=True))
- result.append(PartEditScreen(main_win, x86_slice_mode=True))
-
- result.extend(sysconfig.get_all_screens(main_win))
-
- result.append(SummaryScreen(main_win))
- result.append(InstallProgress(main_win))
- result.append(InstallStatus(main_win))
- result.append(LogViewer(main_win))
- return result
-
-
-def _reboot_cmds(is_x86):
- '''Generate list of cmds to try fast rebooting'''
- cmds = []
-
- ret_val, be_list = libbe_py.beList()
- if ret_val == 0:
- for be in be_list:
- if be.get("active_boot", False):
- root_ds = "%s" % be['root_ds']
- if is_x86:
- cmds.append([REBOOT, "-f", "--", root_ds])
- else:
- # SPARC requires "-Z" before the root dataset
- cmds.append([REBOOT, "-f", "--", "-Z", root_ds])
- break
-
- # Fallback reboot. If the subprocess.call(..) command above fails,
- # simply do a standard reboot.
- cmds.append([REBOOT])
- return cmds
-
-
-def reboot(is_x86):
- '''Reboot the machine, attempting fast reboot first if available'''
- cmds = _reboot_cmds(is_x86)
- for cmd in cmds:
- try:
- subprocess.call(cmd)
- except OSError, err:
- logging.warn("Reboot failed:\n\t'%s'\n%s",
- " ".join(cmd), err)
- else:
- logging.warn("Reboot failed:\n\t'%s'.\nWill attempt"
- " standard reboot", " ".join(cmd))
-
-
-def prepare_engine(options):
- eng = InstallEngine(loglevel=options.log_level, debug=options.debug)
- terminalui.init_logging(INSTALL_LOGGER_NAME)
-
- install_profile = InstallProfile()
- install_profile.log_location = options.logname
- install_profile.log_final = LOG_LOCATION_FINAL
- install_profile.no_install_mode = options.no_install
-
- eng.doc.persistent.insert_children([install_profile])
-
- sysconfig.register_checkpoint()
-
-
-def init_locale():
- locale.setlocale(locale.LC_ALL, "")
- gettext.install("textinstall", "/usr/share/locale", unicode=True)
- set_wrap_on_whitespace(_("DONT_TRANSLATE_BUT_REPLACE_msgstr_WITH_True_"
- "OR_False: Should wrap text on whitespace in"
- " this language"))
- BaseScreen.set_default_quit_text(_("Confirm: Quit the Installer?"),
- _("Do you want to quit the Installer?"),
- _("Cancel"),
- _("Quit"))
-
-
-def main():
- init_locale()
-
- if os.getuid() != 0:
- sys.exit(_("The %(release)s Text Installer must be run with "
- "root privileges") % RELEASE)
- usage = "usage: %prog [-l FILE] [-v LEVEL] [-d] [-n]"
- parser = OptionParser(usage=usage, version="%prog 1.1")
- parser.add_option("-l", "--log-location", dest="logname",
- help=_("Set log location to FILE (default: %default)"),
- metavar="FILE", default=DEFAULT_LOG_LOCATION)
- parser.add_option("-v", "--log-level", dest="log_level",
- default=None,
- help=_("Set log verbosity to LEVEL. In order of "
- "increasing verbosity, valid values are 'error' "
- "'warn' 'info' 'debug' or 'input'\n[default:"
- " %default]"),
- choices=["error", "warn", "info", "debug", "input"],
- metavar="LEVEL")
- parser.add_option("-d", "--debug", action="store_true", dest="debug",
- default=False, help=_("Enable debug mode. Sets "
- "logging level to 'input' and enables CTRL-C for "
- "killing the program\n"))
- parser.add_option("-b", "--no-color", action="store_true", dest="force_bw",
- default=False, help=_("Force the installer to run in "
- "black and white. This may be useful on some SPARC "
- "machines with unsupported frame buffers\n"))
- parser.add_option("-n", "--no-install", action="store_true",
- dest="no_install", default=False,
- help=_("Runs in 'no installation' mode. When run"
- " in 'no installation' mode, no persistent changes are"
- " made to the disks and booted environment\n"))
- options, args = parser.parse_args()
- if options.log_level is None:
- if options.debug:
- options.log_level = DEBUG_LOG_LEVEL
- else:
- options.log_level = DEFAULT_LOG_LEVEL
- try:
- options.log_level = setup_logging(options.logname, options.log_level)
- except IOError, err:
- parser.error("%s '%s'" % (err.strerror, err.filename))
- logging.debug("CLI options: log location = %s, verbosity = %s, debug "
- "mode = %s, no install = %s, force_bw = %s",
- options.logname, options.log_level, options.debug,
- options.no_install, options.force_bw)
- init_log(0) # initialize old logging service
- profile = None
- try:
- with terminalui as initscr:
- win_size_y, win_size_x = initscr.getmaxyx()
- if win_size_y < 24 or win_size_x < 80:
- msg = _(" Terminal too small. Min size is 80x24."
- " Current size is %(x)ix%(y)i.") % \
- {'x': win_size_x, 'y': win_size_y}
- exit_text_installer(errcode=msg)
- prepare_engine(options)
-
- screen_list = ScreenList()
- actions = [Action(curses.KEY_F2, _("Continue"),
- screen_list.get_next),
- Action(curses.KEY_F3, _("Back"),
- screen_list.previous_screen),
- Action(curses.KEY_F6, _("Help"), screen_list.show_help),
- Action(curses.KEY_F9, _("Quit"), screen_list.quit)]
-
- main_win = MainWindow(initscr, screen_list, actions,
- force_bw=options.force_bw)
- screen_list.help = HelpScreen(main_win, _("Help Topics"),
- _("Help Index"),
- _("Select a topic and press "
- "Continue."))
- win_list = make_screen_list(main_win)
- screen_list.help.setup_help_data(win_list)
- screen_list.screen_list = win_list
- screen = screen_list.get_next()
- ctrl_c = None
- doc = InstallEngine.get_instance().doc
- while screen is not None:
- profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)
- logging.debug("Install profile:\n%s", profile)
- logging.debug("Displaying screen: %s", type(screen))
- screen = screen.show()
- if not options.debug and ctrl_c is None:
- # This prevents the user from accidentally hitting
- # ctrl-c halfway through the install. Ctrl-C is left
- # available through the first screen in case terminal
- # display issues make it impossible for the user to
- # quit gracefully
- ctrl_c = signal.signal(signal.SIGINT, signal.SIG_IGN)
- errcode = 0
- except RebootException:
- reboot(platform.processor() == "i386")
- except SystemExit:
- raise
- except:
- logging.exception(str(profile))
- exc_type, exc_value = sys.exc_info()[:2]
- print _("An unhandled exception occurred.")
- if str(exc_value):
- print '\t%s: "%s"' % (exc_type.__name__, str(exc_value))
- else:
- print "\t%s" % exc_type.__name__
- print _("Full traceback data is in the installation log")
- print _("Please file a bug at http://defect.opensolaris.org")
- errcode = 1
- exit_text_installer(options.logname, errcode)
-
-if __name__ == '__main__':
- main()
--- a/usr/src/cmd/text-install/osol_install/text_install/disk_selection.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,433 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Screens and functions to display a list of disks to the user.
-'''
-
-from copy import deepcopy
-import curses
-import logging
-import platform
-import threading
-import traceback
-
-from osol_install.profile.disk_info import DiskInfo, SliceInfo
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _, RELEASE, TUI_HELP
-from osol_install.text_install.disk_window import DiskWindow, \
- get_minimum_size, \
- get_recommended_size
-from osol_install.text_install.ti_install_utils import get_zpool_list
-import osol_install.tgt as tgt
-from solaris_install.engine import InstallEngine
-from terminalui.base_screen import BaseScreen, QuitException, UIMessage
-from terminalui.i18n import fit_text_truncate, textwidth, ljust_columns
-from terminalui.list_item import ListItem
-from terminalui.scroll_window import ScrollWindow
-from terminalui.window_area import WindowArea
-
-
-class DiskScreen(BaseScreen):
- '''
- Allow the user to select a (valid) disk target for installation
- Display the partition/slice table for the highlighted disk
-
- '''
-
- HEADER_TEXT = _("Disks")
- PARAGRAPH = _("Where should %(release)s be installed?") % RELEASE
- SIZE_TEXT = _("Recommended size: %(recommend).1fGB "
- "Minimum size: %(min).1fGB")
- DISK_SEEK_TEXT = _("Seeking disks on system")
- FOUND_x86 = _("The following partitions were found on the disk.")
- FOUND_SPARC = _("The following slices were found on the disk.")
- PROPOSED_x86 = _("A partition table was not found. The following is "
- "proposed.")
- PROPOSED_SPARC = _("A VTOC label was not found. The following "
- "is proposed.")
- PROPOSED_GPT = _("A GPT labeled disk was found. The following is "
- "proposed.")
- TOO_SMALL = _("Too small")
- TOO_BIG_WARN = _("Limited to %.1f TB")
- GPT_LABELED = _("GPT labeled disk")
- NO_DISKS = _("No disks found. Additional device drivers may "
- "be needed.")
- NO_TARGETS = _("%(release)s cannot be installed on any disk") % RELEASE
- TGT_ERROR = _("An error occurred while searching for installation"
- " targets. Please check the install log and file a bug"
- " at defect.opensolaris.org.")
-
- DISK_HEADERS = [(8, _("Type")),
- (10, _("Size(GB)")),
- (6, _("Boot")),
- (9, _("Device")),
- (15, _("Manufacturer")),
- (22, _("Notes"))]
- SPINNER = ["\\", "|", "/", "-"]
-
- DISK_WARNING_HEADER = _("Warning")
- DISK_WARNING_TOOBIG = _("Only the first %.1fTB can be used.")
- DISK_WARNING_GPT = _("You have chosen a GPT labeled disk. Installing "
- "onto a GPT labeled disk will cause the loss "
- "of all existing data and the disk will be "
- "relabeled as SMI.")
-
- CANCEL_BUTTON = _("Cancel")
- CONTINUE_BUTTON = _("Continue")
-
- HELP_DATA = (TUI_HELP + "/%s/disks.txt", _("Disks"))
-
- def __init__(self, main_win):
- super(DiskScreen, self).__init__(main_win)
- if platform.processor() == "i386":
- self.found_text = DiskScreen.FOUND_x86
- self.proposed_text = DiskScreen.PROPOSED_x86
- else:
- self.found_text = DiskScreen.FOUND_SPARC
- self.proposed_text = DiskScreen.PROPOSED_SPARC
-
- disk_header_text = []
- for header in DiskScreen.DISK_HEADERS:
- header_str = fit_text_truncate(header[1], header[0]-1, just="left")
- disk_header_text.append(header_str)
- self.disk_header_text = " ".join(disk_header_text)
- max_note_size = DiskScreen.DISK_HEADERS[5][0]
- self.too_small_text = DiskScreen.TOO_SMALL[:max_note_size]
- max_disk_size = SliceInfo.MAX_VTOC.size_as("tb")
- too_big_warn = DiskScreen.TOO_BIG_WARN % max_disk_size
- self.too_big_warn = too_big_warn[:max_note_size]
- self.disk_warning_too_big = \
- DiskScreen.DISK_WARNING_TOOBIG % max_disk_size
-
- self.disks = []
- self.existing_pools = []
- self.disk_win = None
- self.disk_detail = None
- self.num_targets = 0
- self.td_handle = None
- self._size_line = None
- self.selected_disk = 0
- self._minimum_size = None
- self._recommended_size = None
- self.do_copy = False # Flag indicating if install_profile.disk
- # should be copied
-
- def determine_minimum(self):
- '''Returns minimum install size, fetching first if needed'''
- self.determine_size_data()
- return self._minimum_size
-
- minimum_size = property(determine_minimum)
-
- def determine_recommended(self):
- '''Returns recommended install size, fetching first if needed'''
- self.determine_size_data()
- return self._recommended_size
-
- recommended_size = property(determine_recommended)
-
- def determine_size_data(self):
- '''Retrieve the minimum and recommended sizes and generate the string
- to present that information.
-
- '''
- if self._minimum_size is None or self._recommended_size is None:
- self._recommended_size = get_recommended_size().size_as("gb")
- self._minimum_size = get_minimum_size().size_as("gb")
-
- def get_size_line(self):
- '''Returns the line of text displaying the min/recommended sizes'''
- if self._size_line is None:
- size_dict = {"recommend" : self.recommended_size,
- "min" : self.minimum_size}
- self._size_line = DiskScreen.SIZE_TEXT % size_dict
- return self._size_line
-
- size_line = property(get_size_line)
-
- def wait_for_disks(self):
- '''Block while waiting for libtd to finish. Catch F9 and quit
- if needed
-
- '''
- if self.td_handle is None:
- self.start_discovery()
- self.main_win.actions.pop(curses.KEY_F2, None)
- self.main_win.actions.pop(curses.KEY_F6, None)
- self.main_win.actions.pop(curses.KEY_F3, None)
- self.main_win.show_actions()
- if self.td_handle.is_alive():
- self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1,
- self.win_size_x - 3)
- self.main_win.do_update()
- offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2
- spin_index = 0
- self.center_win.window.timeout(250)
- while self.td_handle.is_alive():
- input_key = self.main_win.getch()
- if input_key == curses.KEY_F9:
- if self.confirm_quit():
- raise QuitException
- self.center_win.add_text(DiskScreen.SPINNER[spin_index], 5,
- offset)
- self.center_win.no_ut_refresh()
- self.main_win.do_update()
- spin_index = (spin_index + 1) % len(DiskScreen.SPINNER)
-
- self.center_win.window.timeout(-1)
- self.center_win.clear()
-
- # Get the list of existing zpools on the
- # system and based on that come up with
- # a unique name for the root pool
- index = 1
- pool_name = "rpool"
- while pool_name in self.existing_pools:
- pool_name = "rpool%d" % index
- index += 1
-
- # Set the SliceInfo.DEFAULT_POOL to the unique
- # pool name
- SliceInfo.DEFAULT_POOL.data = pool_name
-
- def _show(self):
- '''Create a list of disks to choose from and create the window
- for displaying the partition/slice information from the selected
- disk
-
- '''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- self.wait_for_disks()
- self.num_targets = 0
-
- if not self.disks:
- self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1,
- max_x=(self.win_size_x - 1))
- return
-
- if isinstance(self.disks[0], BaseException):
- if len(self.disks) == 1:
- raise tgt.TgtError(("Unexpected error (%s) during target "
- "discovery. See log for details.") %
- self.disks[0])
- else:
- self.disks = self.disks[1:]
- logging.warn("Failure in target discovery, but one or more"
- " disks found. Continuing.")
-
- boot_disk = self.disks[0]
- for disk in self.disks:
- if (disk.size.size_as("gb") > self.minimum_size):
- self.num_targets += 1
- if disk.boot:
- boot_disk = disk
- self.disks.remove(boot_disk)
- self.disks.insert(0, boot_disk)
-
- if self.num_targets == 0:
- self.center_win.add_paragraph(DiskScreen.NO_TARGETS, 1, 1,
- max_x=(self.win_size_x - 1))
- return
-
- self.main_win.reset_actions()
- self.main_win.show_actions()
-
- y_loc = 1
- self.center_win.add_text(DiskScreen.PARAGRAPH, y_loc, 1)
-
- y_loc += 1
- self.center_win.add_text(self.size_line, y_loc, 1)
-
- y_loc += 2
- self.center_win.add_text(self.disk_header_text, y_loc, 1)
-
- y_loc += 1
- self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1,
- curses.ACS_HLINE,
- textwidth(self.disk_header_text))
-
- y_loc += 1
- disk_win_area = WindowArea(4, textwidth(self.disk_header_text) + 2,
- y_loc, 0)
- disk_win_area.scrollable_lines = len(self.disks) + 1
- self.disk_win = ScrollWindow(disk_win_area,
- window=self.center_win)
-
- disk_item_area = WindowArea(1, disk_win_area.columns - 2, 0, 1)
- disk_index = 0
- len_type = DiskScreen.DISK_HEADERS[0][0] - 1
- len_size = DiskScreen.DISK_HEADERS[1][0] - 1
- len_boot = DiskScreen.DISK_HEADERS[2][0] - 1
- len_dev = DiskScreen.DISK_HEADERS[3][0] - 1
- len_mftr = DiskScreen.DISK_HEADERS[4][0] - 1
- for disk in self.disks:
- disk_text_fields = []
- type_field = disk.type[:len_type]
- type_field = ljust_columns(type_field, len_type)
- disk_text_fields.append(type_field)
- disk_size = disk.size.size_as("gb")
- size_field = "%*.1f" % (len_size, disk_size)
- disk_text_fields.append(size_field)
- if disk.boot:
- bootable_field = "+".center(len_boot)
- else:
- bootable_field = " " * (len_boot)
- disk_text_fields.append(bootable_field)
- device_field = disk.name[:len_dev]
- device_field = ljust_columns(device_field, len_dev)
- disk_text_fields.append(device_field)
- if disk.vendor is not None:
- mftr_field = disk.vendor[:len_mftr]
- mftr_field = ljust_columns(mftr_field, len_mftr)
- else:
- mftr_field = " " * len_mftr
- disk_text_fields.append(mftr_field)
- selectable = True
- if disk_size < self.minimum_size:
- note_field = self.too_small_text
- selectable = False
- elif DiskInfo.GPT in disk.label:
- note_field = DiskScreen.GPT_LABELED
- elif disk_size > SliceInfo.MAX_VTOC.size_as("gb"):
- note_field = self.too_big_warn
- else:
- note_field = ""
- disk_text_fields.append(note_field)
- disk_text = " ".join(disk_text_fields)
- disk_item_area.y_loc = disk_index
- disk_list_item = ListItem(disk_item_area, window=self.disk_win,
- text=disk_text, add_obj=selectable)
- disk_list_item.on_make_active = on_activate
- disk_list_item.on_make_active_kwargs["disk_info"] = disk
- disk_list_item.on_make_active_kwargs["disk_select"] = self
- disk_index += 1
- self.disk_win.no_ut_refresh()
-
- y_loc += 7
- disk_detail_area = WindowArea(6, 70, y_loc, 1)
- self.disk_detail = DiskWindow(disk_detail_area, self.disks[0],
- window=self.center_win)
-
- self.main_win.do_update()
- self.center_win.activate_object(self.disk_win)
- self.disk_win.activate_object(self.selected_disk)
- # Set the flag so that the disk is not copied by on_change_screen,
- # unless on_activate gets called as a result of the user changing
- # the selected disk.
- self.do_copy = False
-
- def on_change_screen(self):
- ''' Assign the selected disk to the InstallProfile, and make note of
- its index (in case the user returns to this screen later)
-
- '''
- if self.disk_detail is not None:
- if self.do_copy or self.install_profile.disk is None:
- disk = self.disk_detail.disk_info
- self.install_profile.disk = deepcopy(disk)
- self.install_profile.original_disk = disk
- self.selected_disk = self.disk_win.active_object
-
- def start_discovery(self):
- '''Spawn a thread to begin target discovery'''
- logging.debug("spawning target discovery thread")
- self.td_handle = threading.Thread(target=DiskScreen.get_disks,
- args=(self.disks,
- self.existing_pools))
- logging.debug("starting target discovery thread")
- self.td_handle.start()
-
- @staticmethod
- def get_disks(disks, pools):
- '''
- Call into target discovery and get disk data. The disks found are
- added to the list passed in by the 'disks' argument
-
- '''
- try:
- td_disks = tgt.discover_target_data()
- for disk in td_disks:
- disks.append(DiskInfo(tgt_disk=disk))
- pools.extend(get_zpool_list())
- # If an exception occurs, regardless of type, log it, add it as the
- # first item in the disk list, and consume it (an uncaught Exception
- # in this threaded code would distort the display).
- # During the call to _show, if an exception occurred, the program
- # aborts gracefully
- # pylint: disable-msg=W0703
- except BaseException, err:
- logging.exception(traceback.format_exc())
- disks.insert(0, err)
-
-
- def validate(self):
- '''Validate the size of the disk.'''
- disk = self.disk_detail.disk_info
-
- warning_txt = []
- if DiskInfo.GPT in disk.label:
- warning_txt.append(DiskScreen.DISK_WARNING_GPT)
- if disk.size > SliceInfo.MAX_VTOC:
- warning_txt.append(self.disk_warning_too_big)
- warning_txt = " ".join(warning_txt)
-
- if warning_txt:
- # warn the user and give user a chance to change
- result = self.main_win.pop_up(DiskScreen.DISK_WARNING_HEADER,
- warning_txt,
- DiskScreen.CANCEL_BUTTON,
- DiskScreen.CONTINUE_BUTTON)
-
- if not result:
- raise UIMessage() # let user select different disk
-
- # if user didn't quit it is always OK to ignore disk size,
- # that will be forced less than the maximum in partitioning.
-
-
-def on_activate(disk_info=None, disk_select=None):
- '''When a disk is selected, pass its data to the disk_select_screen'''
- max_x = disk_select.win_size_x - 1
-
- if DiskInfo.GPT in disk_info.label:
- # default to use whole disk for GPT labeled disk
- disk_select.center_win.add_paragraph(DiskScreen.PROPOSED_GPT, 11, 1,
- max_x=max_x)
- disk_info.create_default_layout()
- disk_info.use_whole_segment = True
- elif disk_info.was_blank:
- disk_select.center_win.add_paragraph(disk_select.proposed_text, 11, 1,
- max_x=max_x)
- disk_info.create_default_layout()
- disk_info.use_whole_segment = True
- else:
- disk_select.center_win.add_paragraph(disk_select.found_text, 11, 1,
- max_x=max_x)
- disk_select.disk_detail.set_disk_info(disk_info)
- # User selected a different disk; set the flag so that it gets copied later
- disk_select.do_copy = True
--- a/usr/src/cmd/text-install/osol_install/text_install/disk_window.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,845 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-
-'''
-UI component for displaying (and editing) partition & slice data
-'''
-
-
-from copy import deepcopy
-import curses
-import logging
-
-from osol_install.profile.disk_space import DiskSpace, round_to_multiple
-from osol_install.profile.partition_info import PartitionInfo, UI_PRECISION
-from osol_install.text_install import _
-from osol_install.text_install.ti_install_utils import \
- get_minimum_size as get_min_install_size
-from osol_install.text_install.ti_install_utils import \
- get_recommended_size as get_rec_install_size
-from osol_install.text_install.ti_install_utils import SwapDump, \
- InstallationError
-from terminalui import LOG_LEVEL_INPUT
-from terminalui.base_screen import UIMessage
-from terminalui.edit_field import EditField
-from terminalui.i18n import fit_text_truncate, textwidth
-from terminalui.inner_window import InnerWindow, no_action
-from terminalui.list_item import ListItem
-from terminalui.scroll_window import ScrollWindow
-from terminalui.window_area import WindowArea
-
-
-class DiskWindow(InnerWindow):
- '''Display and edit disk information, including partitions and slices'''
-
- STATIC_PARTITION_HEADERS = [(12, _("Primary"), _("Logical")),
- (9, _("Size(GB)"), _("Size(GB)"))]
-
- EDIT_PARTITION_HEADERS = [(13, _("Primary"), _("Logical")),
- (9, _("Size(GB)"), _("Size(GB)")),
- (7, _(" Avail"), _(" Avail"))]
-
- STATIC_SLICE_HEADERS = [(13, _("Slice"), _("Slice")),
- (2, "#", "#"),
- (9, _("Size(GB)"), _("Size(GB)"))]
-
- EDIT_SLICE_HEADERS = [(13, _("Slice"), _("Slice")),
- (2, "#", "#"),
- (9, _("Size(GB)"), _("Size(GB)")),
- (7, _(" Avail"), _(" Avail"))]
-
- ADD_KEYS = {curses.KEY_LEFT : no_action,
- curses.KEY_RIGHT : no_action}
-
- DEAD_ZONE = 3
- SCROLL_PAD = 2
-
- MIN_SIZE = None
- REC_SIZE = None
-
- SIZE_PRECISION = UI_PRECISION.size_as("gb")
- DESTROYED_MARK = EditField.ASTERISK_CHAR
-
- def __init__(self, area, disk_info, editable=False,
- error_win=None, reset=None, **kwargs):
- '''See also InnerWindow.__init__
-
- disk_info (required) - A DiskInfo object containing the data to be
- represented. Also accepts PartitionInfo objects (for displaying slice
- data within that partition). If disk_info has partition(s), those are
- displayed. If not, but it has slices, then those are displayed. If
- neither partition data nor slice data are available, a ValueError is
- raised. This window makes a copy of the disk_info, as well as keeping
- a reference to the original for the purposes of resetting at a later
- time.
-
- headers (required) - List of tuples to populate the header of this
- window with. The first item in each tuple should be the width of the
- header, the second item should be the left side header.
-
- editable (optional) - If True, the window will be created such that
- data is editable.
-
- '''
- self.headers = None
- self.orig_ext_part_field = None
- self.orig_logicals_active = False
- self.ext_part_field = None
- self.error_win = error_win
- self.editable = editable
- self.win_width = None
- self.left_win = None
- self.right_win = None
- self.list_area = None
- self.edit_area = None
- super(DiskWindow, self).__init__(area, add_obj=editable, **kwargs)
- self.left_header_string = None
- self.right_header_string = None
- self._orig_data = None
- self._reset = reset
- self.disk_info = None
- self.has_partition_data = True
- self.key_dict[curses.KEY_LEFT] = self.on_arrow_key
- self.key_dict[curses.KEY_RIGHT] = self.on_arrow_key
- if self.editable:
- self.key_dict[curses.KEY_F5] = self.change_type
-
- if getattr(disk_info, "do_revert", False):
- self.reset()
- else:
- self.set_disk_info(disk_info)
-
- def _init_win(self, window):
- '''Require at least 70 columns and 6 lines to fit current needs for
- display of partitions and slices. Builds two inner ScrollWindows for
- displaying/editing the data.
-
- '''
- if self.area.columns < 70:
- raise ValueError, "Insufficient space - area.columns < 70"
- if self.area.lines < 6:
- raise ValueError, "Insufficient space - area.lines < 6"
- self.win_width = (self.area.columns - DiskWindow.DEAD_ZONE
- + DiskWindow.SCROLL_PAD) / 2
-
- super(DiskWindow, self)._init_win(window)
-
- win_area = WindowArea(self.area.lines - 1, self.win_width, 2, 0)
- win_area.scrollable_lines = self.area.lines - 2
- self.left_win = ScrollWindow(win_area, window=self, add_obj=False)
- self.left_win.color = None
- self.left_win.highlight_color = None
- win_area.x_loc = self.win_width + DiskWindow.DEAD_ZONE
- win_area.scrollable_lines = 2 * PartitionInfo.MAX_LOGICAL_PARTITIONS
- self.right_win = ScrollWindow(win_area, window=self, add_obj=False)
- self.right_win.color = None
- self.right_win.highlight_color = None
-
- def set_disk_info(self, disk_info):
- '''Set up this DiskWindow to represent disk_info'''
- if getattr(disk_info, "partitions", False):
- self.has_partition_data = True
- elif disk_info.slices:
- self.has_partition_data = False
- else:
- return
-
- if self.has_partition_data:
- if self.editable:
- self.headers = DiskWindow.EDIT_PARTITION_HEADERS
- self.list_area = WindowArea(1, self.headers[0][0] +
- self.headers[1][0],
- 0, DiskWindow.SCROLL_PAD)
- self.edit_area = WindowArea(1, self.headers[1][0], 0,
- self.headers[0][0])
- else:
- self.headers = DiskWindow.STATIC_PARTITION_HEADERS
- else:
- if self.editable:
- self.headers = DiskWindow.EDIT_SLICE_HEADERS
- self.list_area = WindowArea(1, self.headers[0][0] +
- self.headers[1][0] +
- self.headers[2][0],
- 0, DiskWindow.SCROLL_PAD)
- self.edit_area = WindowArea(1, self.headers[2][0], 0,
- self.headers[0][0] +
- self.headers[1][0])
- else:
- self.headers = DiskWindow.STATIC_SLICE_HEADERS
-
- self._orig_data = disk_info
- self.disk_info = deepcopy(disk_info)
- self.disk_info.add_unused_parts()
-
- self.left_win.clear()
- self.right_win.clear()
- self.window.erase()
- self.print_headers()
-
- if self.editable:
- self.active_object = None
- self.build_edit_fields()
- self.right_win.bottom = max(0, len(self.right_win.all_objects) -
- self.right_win.area.lines)
- if self.has_partition_data:
- self.orig_ext_part_field = None
- for obj in self.left_win.objects:
- if (obj.data_obj.is_extended()):
- self.orig_ext_part_field = obj
- self.orig_logicals_active = True
- break
- else:
- self.print_data()
-
- def print_headers(self):
- '''Print the headers for the displayed data.
-
- header[0] - The width of this column. header[1] and header[2] are
- trimmed to this size
- header[1] - The internationalized text for the left window
- header[2] - The internationalized text for the right window
-
- '''
- self.left_header_string = []
- self.right_header_string = []
- for header in self.headers:
- left_header_str = header[1]
- right_header_str = header[2]
- # Trim the header to fit in the column width,
- # splitting columns with at least 1 space
- # Pad with extra space(s) to align the columns
- left_header_str = fit_text_truncate(left_header_str,
- header[0]-1, just="left")
- self.left_header_string.append(left_header_str)
- right_header_str = fit_text_truncate(right_header_str,
- header[0]-1, just="left")
- self.right_header_string.append(right_header_str)
- self.left_header_string = " ".join(self.left_header_string)
- self.right_header_string = " ".join(self.right_header_string)
- logging.debug(self.left_header_string)
- self.add_text(self.left_header_string, 0, DiskWindow.SCROLL_PAD)
- right_win_offset = (self.win_width + DiskWindow.DEAD_ZONE +
- DiskWindow.SCROLL_PAD)
- self.add_text(self.right_header_string, 0, right_win_offset)
- self.window.hline(1, DiskWindow.SCROLL_PAD, curses.ACS_HLINE,
- textwidth(self.left_header_string))
- self.window.hline(1, right_win_offset, curses.ACS_HLINE,
- textwidth(self.right_header_string))
- self.no_ut_refresh()
-
- def print_data(self):
- '''Print static (non-editable) data.
-
- Slices - fill the left side, then remaining slices on the right side.
- If for some reason not all slices fit, indicate how many more slices
- there area
-
- Partitions - Put standard partitions on the left, logical partitions
- on the right
-
- '''
- part_index = 0
- if self.has_partition_data:
- max_parts = PartitionInfo.MAX_STANDARD_PARTITIONS
- else:
- max_parts = min(len(self.disk_info.slices),
- self.left_win.area.lines)
- win = self.left_win
- y_loc = 0
- for next_part in self.disk_info.get_parts():
- if y_loc >= max_parts:
- if win is self.left_win:
- win = self.right_win
- y_loc = 0
- max_parts = win.area.lines
- else:
- if self.has_partition_data:
- num_extra = len(self.disk_info.partitions) - part_index
- more_parts_txt = _("%d more partitions") % num_extra
- else:
- num_extra = len(self.disk_info.slices) - part_index
- more_parts_txt = _("%d more slices") % num_extra
- win.add_text(more_parts_txt, win.area.lines, 3)
- break
- x_loc = DiskWindow.SCROLL_PAD
- field = 0
- win.add_text(next_part.get_description(), y_loc, x_loc,
- self.headers[field][0] - 1)
- x_loc += self.headers[field][0]
- field += 1
- if not self.has_partition_data:
- win.add_text(str(next_part.number), y_loc, x_loc,
- self.headers[field][0] - 1)
- x_loc += self.headers[field][0]
- field += 1
- win.add_text("%*.1f" % (self.headers[field][0]-1,
- next_part.size.size_as("gb")),
- y_loc, x_loc,
- self.headers[field][0]-1)
- x_loc += self.headers[field][0]
- y_loc += 1
- field += 1
- part_index += 1
- self.right_win.use_vert_scroll_bar = False
- self.no_ut_refresh()
-
- def build_edit_fields(self):
- '''Build subwindows for editing partition sizes
-
- For slices, fill the left side, then the right (right side scrolling as
- needed, though this shouldn't happen unless the number of slices on
- disk exceeds 8 for some reason)
-
- For partitions, fill the left side up to MAX_STANDARD_PARTITIONS,
- and place all logical partitions on the right.
-
- '''
- if self.has_partition_data:
- max_left_parts = PartitionInfo.MAX_STANDARD_PARTITIONS
- else:
- max_left_parts = min(len(self.disk_info.slices),
- self.left_win.area.lines)
- part_iter = iter(self.disk_info.get_parts())
- try:
- next_part = part_iter.next()
- self.objects.append(self.left_win)
- for y_loc in range(max_left_parts):
- self.list_area.y_loc = y_loc
- self.create_list_item(next_part, self.left_win, self.list_area)
- next_part.orig_type = next_part.type
- next_part = part_iter.next()
- self.objects.append(self.right_win)
- for y_loc in range(self.right_win.area.scrollable_lines):
- self.list_area.y_loc = y_loc
- self.create_list_item(next_part, self.right_win,
- self.list_area)
- next_part.orig_offset = next_part.offset.size_as("gb")
- next_part.orig_size = next_part.size.size_as("gb")
- next_part = part_iter.next()
- except StopIteration:
- if len(self.right_win.all_objects) <= self.right_win.area.lines:
- self.right_win.use_vert_scroll_bar = False
- self.right_win.no_ut_refresh()
- else:
- raise ValueError("Could not fit all partitions in DiskWindow")
- self.no_ut_refresh()
-
- def create_list_item(self, next_part, win, list_area):
- '''Add an entry for next_part (a PartitionInfo or SliceInfo) to
- the DiskWindow
-
- '''
- next_part.is_dirty = False
- next_part.restorable = True
- next_part.orig_offset = next_part.offset.size_as("gb")
- next_part.orig_size = next_part.size.size_as("gb")
- next_part.orig_type = next_part.type
- list_item = ListItem(list_area, window=win, data_obj=next_part)
- list_item.key_dict.update(DiskWindow.ADD_KEYS)
- list_item.on_make_inactive = on_leave_part_field
- list_item.on_make_inactive_kwargs = {"field" : list_item}
- edit_field = EditField(self.edit_area, window=list_item,
- numeric_pad=" ",
- validate=decimal_valid,
- on_exit=on_exit_edit,
- error_win=self.error_win,
- add_obj=False,
- data_obj=next_part)
- edit_field.right_justify = True
- edit_field.validate_kwargs["disk_win"] = self
- edit_field.key_dict.update(DiskWindow.ADD_KEYS)
- self.update_part(part_field=list_item)
- return list_item
-
- def update_part(self, part_info=None, part_field=None):
- '''Sync changed partition data to the screen.'''
- if part_field is None:
- if part_info is None:
- raise ValueError("Must supply either part_info or part_field")
- part_field = self.find_part_field(part_info)[1]
- elif part_info is None:
- part_info = part_field.data_obj
- elif part_field.data_obj is not part_info:
- raise ValueError("part_field must be a ListItem associated with "
- "part_info")
- if not isinstance(part_field, ListItem):
- raise TypeError("part_field must be a ListItem associated with "
- "part_info")
- if self.has_partition_data:
- desc_text = part_info.get_description()
- else:
- desc_length = self.headers[0][0] - 1
- desc_text = "%-*.*s %i" % (desc_length, desc_length,
- part_info.get_description(),
- part_info.number)
- part_field.set_text(desc_text)
- edit_field = part_field.all_objects[0]
- edit_field.set_text("%.1f" % part_info.size.size_as("gb"))
- self.mark_if_destroyed(part_field)
- self._update_edit_field(part_info, part_field, edit_field)
-
- self.update_avail_space(part_info=part_info)
- if self.has_partition_data:
- if part_info.is_extended():
- self.ext_part_field = part_field
-
- def _update_edit_field(self, part_info, part_field, edit_field):
- '''If the partition/slice is editable, add it to the .objects list.
- If it's also the part_field that's currently selected, then activate
- the edit field.
-
- '''
- if part_info.editable(self.disk_info):
- part_field.objects = [edit_field]
- active_win = self.get_active_object()
- if active_win is not None:
- if active_win.get_active_object() is part_field:
- part_field.activate_object(edit_field)
- else:
- edit_field.make_inactive()
- part_field.objects = []
- part_field.active_object = None
-
- def mark_if_destroyed(self, part_field):
- '''Determine if the partition/slice represented by part_field has
- changed such that its contents will be destroyed.
-
- '''
- part_info = part_field.data_obj
- destroyed = part_info.destroyed()
- self.mark_destroyed(part_field, destroyed)
-
- def mark_destroyed(self, part_field, destroyed):
- '''If destroyed is True, add an asterisk indicating that the
- partition or slice's content will be destroyed during installation.
- Otherwise, clear the asterisk
-
- '''
- y_loc = part_field.area.y_loc
- x_loc = part_field.area.x_loc - 1
- if part_field in self.right_win.objects:
- win = self.right_win
- else:
- win = self.left_win
- if destroyed:
- win.window.addch(y_loc, x_loc, DiskWindow.DESTROYED_MARK,
- win.color_theme.inactive)
- else:
- win.window.addch(y_loc, x_loc, InnerWindow.BKGD_CHAR)
-
- def update_avail_space(self, part_number=None, part_info=None):
- '''Update the 'Avail' column for the specified slice or partition.
- If no number is given, all avail columns are updated
-
- '''
- if part_number is None and part_info is None:
- self._update_all_avail_space()
- else:
- self._update_avail_space(part_number, part_info)
-
- def _update_all_avail_space(self):
- '''Update the 'Avail' column for all slices or partitions.'''
- idx = 0
- for item in self.left_win.objects:
- self.update_avail_space(idx)
- idx += 1
- for item in self.right_win.objects:
- self.update_avail_space(idx)
- idx += 1
- y_loc = idx - len(self.left_win.objects)
- if self.has_partition_data:
- x_loc = self.headers[0][0] + self.headers[1][0] + 1
- field = 2
- else:
- x_loc = (self.headers[0][0] + self.headers[1][0] +
- self.headers[2][0] + 1)
- field = 3
- if y_loc > 0:
- self.right_win.add_text(" " * self.headers[field][0],
- y_loc, x_loc)
- elif y_loc == 0 and self.has_partition_data:
- # Blank out the size fields of removed (non-original)
- # logical partitions
- orig_logicals = len(self._orig_data.get_logicals())
- for right_y_loc in range(orig_logicals,
- self.right_win.area.scrollable_lines):
- self.right_win.add_text(" " * self.headers[field][0],
- right_y_loc, x_loc)
-
- def _update_avail_space(self, part_number=None, part_info=None):
- '''Update the 'Avail' column for the specified slice or partition.'''
- if part_number is None:
- win, item = self.find_part_field(part_info)
- elif part_number < len(self.left_win.objects):
- win = self.left_win
- item = win.objects[part_number]
- else:
- win = self.right_win
- item = win.objects[part_number - len(self.left_win.objects)]
- if self.has_partition_data:
- x_loc = self.headers[0][0] + self.headers[1][0] + 1
- field = 2
- else:
- x_loc = (self.headers[0][0] + self.headers[1][0] +
- self.headers[2][0] + 1)
- field = 3
- y_loc = item.area.y_loc
- part = item.data_obj
- max_space = part.get_max_size(self.disk_info)
- max_space = "%*.1f" % (self.headers[field][0], max_space)
- win.add_text(max_space, y_loc, x_loc)
-
- def find_part_field(self, part_info):
- '''Given a PartitionInfo or SliceInfo object, find the associated
- ListItem. This search compares by reference, and will only succeed
- if you have a handle to the exact object referenced by the ListItem
-
- '''
- for win in [self.left_win, self.right_win]:
- for item in win.objects:
- if item.data_obj is part_info:
- return win, item
- raise ValueError("Part field not found")
-
- def reset(self, dummy=None):
- '''Reset disk_info to _orig_data.
- Meaningful only for editable DiskWindows
-
- '''
- if self.editable:
- if self._reset is not None:
- self.set_disk_info(self._reset)
- else:
- self.set_disk_info(self._orig_data)
- self.activate_solaris_data()
-
- def activate_solaris_data(self):
- '''Find the Solaris Partition / ZFS Root Pool Slice and activate it.
- See also DiskInfo.get_solaris_data()
-
- '''
- if self.editable:
- solaris_part = self.disk_info.get_solaris_data()
- if solaris_part is None:
- logging.debug("No Solaris data, activating default")
- self.activate_object()
- self.right_win.scroll(scroll_to_line=0)
- return
- disk_order = self.disk_info.get_parts().index(solaris_part)
- logging.debug("solaris disk at disk_order = %s", disk_order)
- if disk_order < len(self.left_win.objects):
- logging.debug("activating in left_win")
- self.left_win.activate_object(disk_order)
- self.activate_object(self.left_win)
- self.right_win.scroll(scroll_to_line=0)
- else:
- activate = disk_order - len(self.left_win.objects)
- logging.debug('activating in right win')
- self.right_win.activate_object_force(activate,
- force_to_top=True)
- self.activate_object(self.right_win)
- left_active = self.left_win.get_active_object()
- if left_active is not None:
- left_active.make_inactive()
-
- def make_active(self):
- '''On activate, select the solaris partition or ZFS root pool,
- instead of defaulting to 0
-
- '''
- self.set_color(self.highlight_color)
- self.activate_solaris_data()
-
- def on_arrow_key(self, input_key):
- '''
- On curses.KEY_LEFT: Move from the right win to the left win
- On curses.KEY_RIGHT: Move from the left to the right
-
- '''
- if (input_key == curses.KEY_LEFT and
- self.get_active_object() is self.right_win and
- len(self.left_win.objects) > 0):
-
- active_object = self.right_win.get_active_object().area.y_loc
- if (active_object >= len(self.left_win.objects)):
- active_object = len(self.left_win.objects) - 1
- self.activate_object(self.left_win)
- self.left_win.activate_object(active_object)
- return None
- elif (input_key == curses.KEY_RIGHT and
- self.get_active_object() is self.left_win and
- len(self.right_win.objects) > 0):
- active_line = (self.left_win.active_object +
- self.right_win.current_line[0])
- active_object = None
- force_to_top = False
- for obj in self.right_win.objects:
- if obj.area.y_loc >= active_line:
- active_object = obj
- off_screen = (self.right_win.current_line[0] +
- self.right_win.area.lines)
- if active_object.area.y_loc > off_screen:
- force_to_top = True
- break
- if active_object is None:
- active_object = 0
- self.left_win.activate_object(-1, loop=True)
- self.activate_object(self.right_win)
- self.right_win.activate_object_force(active_object,
- force_to_top=force_to_top)
- return None
- return input_key
-
- def no_ut_refresh(self, abs_y=None, abs_x=None):
- '''Refresh self, left win and right win explicitly'''
- super(DiskWindow, self).no_ut_refresh()
- self.left_win.no_ut_refresh(abs_y, abs_x)
- self.right_win.no_ut_refresh(abs_y, abs_x)
-
- def change_type(self, dummy):
- '''Cycle the type for the currently active object, and
- update its field
-
- '''
- logging.debug("changing type")
- part_field = self.get_active_object().get_active_object()
- part_info = part_field.data_obj
- old_type = part_info.type
- if part_info.restorable:
- part_info.cycle_type(self.disk_info, [part_info.orig_type])
- else:
- part_info.cycle_type(self.disk_info)
- new_type = part_info.type
- if old_type != new_type:
- max_size = part_info.get_max_size(self.disk_info)
- if part_info.restorable:
- part_info.is_dirty = (new_type != part_info.orig_type)
- if old_type == part_info.UNUSED:
- if part_info.orig_type == part_info.UNUSED:
- part_info.size = "%.1fgb" % max_size
- part_info.adjust_offset(self.disk_info)
- else:
- part_info.size = part_info.previous_size
- if part_info.is_dirty and part_info.size.size_as("gb") > max_size:
- part_info.size = "%.1fgb" % max_size
- part_info.adjust_offset(self.disk_info)
- if self.has_partition_data:
- if old_type in PartitionInfo.EXTENDED:
- self.deactivate_logicals()
- elif part_info.is_extended():
- self.create_extended(part_field)
- elif part_info.is_logical():
- last_logical = self.right_win.objects[-1].data_obj
-
- if (old_type == PartitionInfo.UNUSED and
- self.right_win.objects[-1] is part_field):
- logging.debug("part is logical, old type unused, "
- "last field")
- self.append_unused_logical()
- elif (len(self.right_win.objects) > 1 and
- new_type == PartitionInfo.UNUSED and
- self.right_win.objects[-2] is part_field and
- last_logical.type == PartitionInfo.UNUSED):
- # If we cycle the second to last partition to Unused,
- # combine it with the last unused partition
- remove = self.right_win.objects[-1]
- self.right_win.remove_object(remove)
- self.update_part(part_field=self.ext_part_field)
- self.update_part(part_field=part_field)
- logging.log(LOG_LEVEL_INPUT, "part updated to:\n%s", part_info)
- self.update_avail_space()
- part_field.no_ut_refresh()
- return None
-
- def deactivate_logicals(self):
- '''Marks as destroyed all logicals in the original extended partition,
- and sets them as unselectable. Additionally, completely removes
- any logical partitions added by the user.
-
- '''
- if self.orig_logicals_active:
- original_logicals = len(self._orig_data.get_logicals())
- self.orig_logicals_active = False
- else:
- original_logicals = 0
- logging.log(LOG_LEVEL_INPUT, "orig logicals = %s", original_logicals)
- self.disk_info.remove_logicals()
- for obj in self.right_win.objects[:original_logicals]:
- self.mark_destroyed(obj, True)
- for obj in self.right_win.objects[original_logicals:]:
- obj.clear()
- self.right_win.remove_object(obj)
-
- if self.right_win in self.objects:
- self.objects.remove(self.right_win)
- self.right_win.objects = []
- self.right_win.active_object = None
- scroll = len(self.right_win.all_objects) > self.right_win.area.lines
- self.right_win.use_vert_scroll_bar = scroll
- self.right_win.no_ut_refresh()
-
- def create_extended(self, ext_part_field):
- '''If this is the original extended partition, restore the original
- logical partitions. Otherwise, create a single unused logical
- partition.
-
- '''
- if not ext_part_field.data_obj.modified():
- self.right_win.clear()
- self.orig_logicals_active = True
- logicals = deepcopy(self._orig_data.get_logicals())
- self.disk_info.partitions.extend(logicals)
- for idx, logical in enumerate(logicals):
- self.list_area.y_loc = idx
- self.create_list_item(logical, self.right_win, self.list_area)
- if self.right_win not in self.objects:
- self.objects.append(self.right_win)
- self.right_win.activate_object_force(0, force_to_top=True)
- self.right_win.make_inactive()
- self.right_win.no_ut_refresh()
- else:
- # Leave old data be, create new Unused logical partition
- if self.right_win not in self.objects:
- self.objects.append(self.right_win)
- self.append_unused_logical()
-
- def append_unused_logical(self):
- '''Adds a single Unused logical partition to the right window'''
- new_part = self.disk_info.append_unused_logical()
- self.list_area.y_loc = len(self.right_win.all_objects)
- bottom = self.list_area.y_loc - self.right_win.area.lines + 1
- self.right_win.bottom = max(0, bottom)
- self.create_list_item(new_part, self.right_win, self.list_area)
- scroll = len(self.right_win.all_objects) > self.right_win.area.lines
- self.right_win.use_vert_scroll_bar = scroll
- self.right_win.no_ut_refresh()
-
-
-def on_leave_part_field(field=None):
- '''When leaving a field, if the field has been modified, mark it as
- non-restorable, meaning the original partition data has been
- modified to the extent that we can no longer safely ensure the preservation
- of any data on it. (DiskWindow.reset will still function)
-
- '''
- part_info = field.data_obj
- if part_info.is_dirty:
- part_info.restorable = False
-
-
-def decimal_valid(edit_field, disk_win=None):
- '''Check text to see if it is a decimal number of precision no
- greater than the tenths place.
-
- '''
- text = edit_field.get_text().lstrip()
- if text.endswith(" "):
- raise UIMessage(_('Only the digits 0-9 and "." are valid.'))
- vals = text.split(".")
- if len(vals) > 2:
- raise UIMessage(_('A number can only have one "."'))
- try:
- if len(vals[0]) > 0:
- int(vals[0])
- if len(vals) > 1 and len(vals[1]) > 0:
- int(vals[1])
- except ValueError:
- raise UIMessage(_('Only the digits 0-9 and "." are valid.'))
- if len(vals) > 1 and len(vals[1]) > 1:
- raise UIMessage(_("Size can be specified to only one decimal place."))
- if disk_win is not None:
- text = text.rstrip(".")
- if not text:
- text = "0"
- new_size = DiskSpace(text + "gb")
- max_size = edit_field.data_obj.get_max_size(disk_win.disk_info)
-
- # When comparing sizes, check only to the first decimal place,
- # as that is all the user sees. (Rounding errors that could
- # cause the partition/slice layout to be invalid get cleaned up
- # prior to target instantiation)
- new_size_rounded = round(new_size.size_as("gb"), 1)
- max_size_rounded = round(max_size, 1)
- if new_size_rounded > max_size_rounded:
- raise UIMessage(_("The new size (%(size).1f) is greater than "
- "the available space (%(avail).1f)") %
- {"size" : new_size_rounded,
- "avail" : max_size_rounded})
- size_diff = abs(new_size.size_as("gb") - edit_field.data_obj.orig_size)
- if size_diff > DiskWindow.SIZE_PRECISION:
- edit_field.data_obj.size = new_size
- edit_field.data_obj.adjust_offset(disk_win.disk_info)
- else:
- edit_field.data_obj.size = "%fgb" % edit_field.data_obj.orig_size
- disk_win.update_avail_space()
- disk_win.no_ut_refresh()
- part_field = disk_win.find_part_field(edit_field.data_obj)[1]
- disk_win.mark_if_destroyed(part_field)
- return True
-
-
-def on_exit_edit(edit_field):
- '''On exit, if the user has left the field blank, set the size to 0'''
- text = edit_field.get_text()
- if not text.strip():
- text = "0"
- edit_field.set_text("%.1f" % float(text))
-
-
-def get_recommended_size():
- '''Returns the recommended size for the installation, in GB'''
- if DiskWindow.REC_SIZE is None:
- try:
- swap_dump = SwapDump()
- rec_size = str(get_rec_install_size(swap_dump)) + "mb"
- DiskWindow.REC_SIZE = DiskSpace(rec_size)
- rec_size = DiskWindow.REC_SIZE.size_as("gb")
- rec_size = round_to_multiple(rec_size, 0.1)
- DiskWindow.REC_SIZE.size = "%sgb" % rec_size
- except (InstallationError):
- logging.warn("Unable to determine recommended install size")
- DiskWindow.REC_SIZE = DiskSpace("10gb")
- return DiskWindow.REC_SIZE
-
-
-def get_minimum_size():
- '''Returns the minimum disk space needed for installation, in GB. The
- value returned from get_min_install_size is rounded up to the nearest
- tenth of a gigabyte, so that the UI ensures enough space is allocated,
- given that the UI only allows for precision to tenths of a gigabyte.
-
- '''
- if DiskWindow.MIN_SIZE is None:
- try:
- swap_dump = SwapDump()
- min_size = str(get_min_install_size(swap_dump)) + "mb"
- DiskWindow.MIN_SIZE = DiskSpace(min_size)
- min_size = DiskWindow.MIN_SIZE.size_as("gb")
- min_size = round_to_multiple(min_size, 0.1)
- DiskWindow.MIN_SIZE.size = "%sgb" % min_size
- except (InstallationError):
- logging.warn("Unable to determine minimum install size")
- DiskWindow.MIN_SIZE = DiskSpace("6gb")
- return DiskWindow.MIN_SIZE
--- a/usr/src/cmd/text-install/osol_install/text_install/fdisk_partitions.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,230 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Screen for selecting to use whole disk, or a partition/slice on the disk
-'''
-
-import logging
-import platform
-
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _, RELEASE, TUI_HELP
-from osol_install.text_install.disk_window import DiskWindow
-from solaris_install.engine import InstallEngine
-from terminalui.base_screen import BaseScreen, SkipException
-from terminalui.i18n import textwidth
-from terminalui.list_item import ListItem
-from terminalui.window_area import WindowArea
-
-
-class FDiskPart(BaseScreen):
- '''Allow user to choose to use the whole disk, or move to the
- partition/slice edit screen.
-
- '''
-
- BOOT_TEXT = _("Boot")
- HEADER_FDISK = _("Fdisk Partitions: %(size).1fGB %(type)s %(bootable)s")
- HEADER_PART_SLICE = _("Solaris Partition Slices")
- HEADER_SLICE = _("Solaris Slices: %(size).1fGB %(type)s %(bootable)s")
- PARAGRAPH_FDISK = _("%(release)s can be installed on the whole "
- "disk or a partition on the disk.") % RELEASE
- PARAGRAPH_PART_SLICE = _("%(release)s can be installed in the "
- "whole fdisk partition or within a "
- "slice in the partition") % RELEASE
- PARAGRAPH_SLICE = _("%(release)s can be installed on the whole"
- " disk or a slice on the disk.") % RELEASE
- FOUND_PART = _("The following partitions were found on the disk.")
- PROPOSED_PART = _("A partition table was not found. The following is"
- " proposed.")
- FOUND_SLICE = _("The following slices were found on the disk.")
- PROPOSED_SLICE = _("A VTOC label was not found. The following is "
- "proposed.")
- USE_WHOLE_DISK = _("Use the whole disk")
- USE_WHOLE_PARTITION = _("Use the whole partition")
- USE_SLICE_IN_PART = _("Use a slice in the partition")
- USE_PART_IN_DISK = _("Use a partition of the disk")
- USE_SLICE_IN_DISK = _("Use a slice on the disk")
-
- SPARC_HELP = (TUI_HELP + "/%s/sparc_solaris_slices.txt",
- _("Solaris Slices"))
- X86_PART_HELP = (TUI_HELP + "/%s/"
- "x86_fdisk_partitions.txt",
- _("Fdisk Partitions"))
- X86_SLICE_HELP = (TUI_HELP + "/%s/x86_fdisk_slices.txt",
- _("Solaris Partition Slices"))
-
- def __init__(self, main_win, x86_slice_mode=False):
- '''If x86_slice_mode == True, this screen presents options for using a
- whole partition, or a slice within the partition.
- Otherwise, it presents options for using the whole disk, or using a
- partition (x86) or slice (SPARC) within the disk
-
- '''
- super(FDiskPart, self).__init__(main_win)
- self.x86_slice_mode = x86_slice_mode
- self.is_x86 = True
- self.help_format = " %s"
- if platform.processor() == "sparc": # SPARC, slices on a disk
- self.is_x86 = False
- self.header_text = FDiskPart.HEADER_SLICE
- self.paragraph = FDiskPart.PARAGRAPH_SLICE
- self.found = FDiskPart.FOUND_SLICE
- self.proposed = FDiskPart.PROPOSED_SLICE
- self.use_whole = FDiskPart.USE_WHOLE_DISK
- self.use_part = FDiskPart.USE_SLICE_IN_DISK
- self.help_data = FDiskPart.SPARC_HELP
- elif self.x86_slice_mode: # x86, slices within a partition
- self.instance = ".slice"
- self.header_text = FDiskPart.HEADER_PART_SLICE
- self.paragraph = FDiskPart.PARAGRAPH_PART_SLICE
- self.found = FDiskPart.FOUND_SLICE
- self.proposed = FDiskPart.PROPOSED_SLICE
- self.use_whole = FDiskPart.USE_WHOLE_PARTITION
- self.use_part = FDiskPart.USE_SLICE_IN_PART
- self.help_data = FDiskPart.X86_SLICE_HELP
- self.help_format = " %s"
- else: # x86, partitions on a disk
- self.header_text = FDiskPart.HEADER_FDISK
- self.paragraph = FDiskPart.PARAGRAPH_FDISK
- self.found = FDiskPart.FOUND_PART
- self.proposed = FDiskPart.PROPOSED_PART
- self.use_whole = FDiskPart.USE_WHOLE_DISK
- self.use_part = FDiskPart.USE_PART_IN_DISK
- self.help_data = FDiskPart.X86_PART_HELP
- self.disk_info = None
- self.disk_win = None
- self.partial_disk_item = None
- self.whole_disk_item = None
-
- def _show(self):
- '''Display partition data for selected disk, and present the two
- choices
-
- '''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- if self.x86_slice_mode:
- disk = self.install_profile.disk
- self.disk_info = disk.get_solaris_data()
- if self.disk_info is None:
- err_msg = "Critical error - no Solaris partition found"
- logging.error(err_msg)
- raise ValueError(err_msg)
- logging.debug("bool(self.disk_info.slices)=%s",
- bool(self.disk_info.slices))
- logging.debug("self.disk_info.modified()=%s",
- self.disk_info.modified())
- if not self.disk_info.slices or self.disk_info.modified():
- logging.debug("Setting partition.use_whole_segment,"
- "creating default layout, and skipping")
- self.disk_info.use_whole_segment = True
- # We only do slice level editing on x86 if there are
- # existing slices on an existing (unmodified)Solaris
- # partition
- self.disk_info.create_default_layout()
- raise SkipException
- disp_disk = self.install_profile.original_disk.get_solaris_data()
- logging.debug("Preserved partition with existing slices:"
- " presenting option to install into a slice")
- else:
- self.disk_info = self.install_profile.disk
- disp_disk = self.install_profile.original_disk
- if self.disk_info.boot:
- bootable = FDiskPart.BOOT_TEXT
- else:
- bootable = u""
- header_text = self.header_text % \
- {"size" : self.disk_info.size.size_as("gb"),
- "type" : self.disk_info.type,
- "bootable" : bootable}
- self.main_win.set_header_text(header_text)
-
- y_loc = 1
- y_loc += self.center_win.add_paragraph(self.paragraph, start_y=y_loc)
-
- y_loc += 1
- if self.is_x86 and not self.x86_slice_mode:
- found_parts = bool(self.disk_info.partitions)
- else:
- found_parts = bool(self.disk_info.slices)
- if found_parts:
- next_line = self.found
- else:
- next_line = self.proposed
- y_loc += self.center_win.add_paragraph(next_line, start_y=y_loc)
-
- y_loc += 1
- disk_win_area = WindowArea(6, 70, y_loc, 0)
- self.disk_win = DiskWindow(disk_win_area, disp_disk,
- window=self.center_win)
- y_loc += disk_win_area.lines
-
- y_loc += 3
- whole_disk_width = textwidth(self.use_whole) + 3
- cols = (self.win_size_x - whole_disk_width) / 2
- whole_disk_item_area = WindowArea(1, whole_disk_width, y_loc, cols)
- self.whole_disk_item = ListItem(whole_disk_item_area,
- window=self.center_win,
- text=self.use_whole,
- centered=True)
-
- y_loc += 1
- partial_width = textwidth(self.use_part) + 3
- cols = (self.win_size_x - partial_width) / 2
- partial_item_area = WindowArea(1, partial_width, y_loc, cols)
- self.partial_disk_item = ListItem(partial_item_area,
- window=self.center_win,
- text=self.use_part,
- centered=True)
-
- self.main_win.do_update()
- if self.disk_info.use_whole_segment:
- self.center_win.activate_object(self.whole_disk_item)
- else:
- self.center_win.activate_object(self.partial_disk_item)
-
- def on_continue(self):
- '''Set the user's selection in the install_profile. If they chose
- to use the entire disk (or entire partition), define a single
- partition (or slice) to consume the whole disk (or partition)
-
- '''
- if self.center_win.get_active_object() is self.whole_disk_item:
- logging.debug("Setting use_whole_segment and creating default"
- " layout for %s", type(self.disk_info))
- self.disk_info.use_whole_segment = True
- self.disk_info.create_default_layout()
- else:
- logging.debug("Setting use_whole segment false for %s",
- type(self.disk_info))
- # If user had previously selected to use the whole disk
- # or partition, set the do_revert flag so that the following
- # screen will know to reset the disk (reverting the call
- # to create_default_layout, above)
- self.disk_info.do_revert = self.disk_info.use_whole_segment
- self.disk_info.use_whole_segment = False
--- a/usr/src/cmd/text-install/osol_install/text_install/install_progress.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,287 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Start a Thread for executing installation, and
-track the progress of the installation
-'''
-
-import curses
-import logging
-import math
-import threading
-import time
-
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _, RELEASE, TUI_HELP
-from osol_install.text_install.ti_install import perform_ti_install
-from solaris_install.engine import InstallEngine
-import solaris_install.sysconfig as sysconfig
-from terminalui.base_screen import BaseScreen
-from terminalui.i18n import ljust_columns
-from terminalui.inner_window import InnerWindow
-from terminalui.window_area import WindowArea
-from terminalui import LOG_LEVEL_INPUT
-
-
-class InstallProgress(BaseScreen):
- '''Present a progress bar, and callback hooks for an installation
- Thread to update this screen with percent complete and status.
-
- '''
-
- HEADER_TEXT = _("Installing %(release)s") % RELEASE
- PROG_BAR_ENDS = (ord('['), ord(']'))
-
- QUIT_DISK_MODIFIED = _("Do you want to quit the Installer?\n\n"
- "Any changes made to the disk by the "
- "Installer will be left \"as is.\"")
-
- def __init__(self, main_win):
- super(InstallProgress, self).__init__(main_win)
- self.really_quit = False
-
- # Location on screen where the status messages should get printed
- # Format: (y, x, max-width)
- self.status_msg_loc = (4, 12, 50)
-
- # Location on screen where the progress bar should be displayed
- # Format: (y, x, width)
- self.status_bar_loc = (6, 10, 50)
-
- self.last_update = 0
- # Minimum elapsed time in seconds between screen updates
- self.update_frequency = 2
- self.status_bar = None
- self.status_bar_width = None
- self.install_profile = None
- self.install_thread = None
- self.progress_color = None
- self.quit_event = threading.Event()
- self.update_event = threading.Event()
- self.time_change_event = threading.Event()
- self.update_to = None
-
- def set_actions(self):
- '''Remove all actions except F9_Quit.'''
- self.main_win.actions.pop(curses.KEY_F2) # Remove F2_Continue
- self.main_win.actions.pop(curses.KEY_F3) # Remove F3_Back
- self.main_win.actions.pop(curses.KEY_F6) # Remove F6_Help
-
- def _show(self):
- '''Set an initial status message, and initialize the progress bar'''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- self.set_status_message(InstallProgress.HEADER_TEXT)
- self.init_status_bar(*self.status_bar_loc)
-
- def validate_loop(self):
- '''Begin the installation. Honor the '-n' flag from the CLI by
- beginning a 'fake' installation thread instead of actually making
- changes to the disk.
-
- After starting the installation thread, wait for input from the
- user, in case they change their mind and try to quit.
-
- '''
- if self.install_profile.no_install_mode:
- self.install_thread = self.start_fake_install_thread()
- else:
- self.install_thread = self.start_install_thread()
- win = self.center_win.window
- win.timeout(100) # Set timeout for getch to 100 ms. The install_thread
- # will be checked at this frequency to see if it
- # has finished execution.
- # Keystroke input will still be processed immediately
- while self.install_thread.is_alive():
- # Wait for the install thread to signal that it has finished
- # updating the system time, so that we can safely use getch(),
- # which times out based on the system clock.
- self.time_change_event.wait()
- input_key = self.main_win.getch()
- if input_key == curses.KEY_F9:
- self.really_quit = self.confirm_quit()
- if self.really_quit:
- win.timeout(-1)
- self.install_thread.join()
- return None
- win.timeout(-1) # Restore getch() to be blocking
- return self.main_win.screen_list.get_next(self)
-
- def start_install_thread(self):
- '''Instantiate a Thread to do the installation, and start execution.'''
- handle = threading.Thread(target=InstallProgress.perform_install,
- args=(self.install_profile, self,
- InstallProgress.update_status,
- self.quit_event,
- self.time_change_event))
- handle.start()
- return handle
-
- def start_fake_install_thread(self):
- '''Instantiate a Thread to perform a 'fake' installation'''
- handle = threading.Thread(target=InstallProgress.fake_install,
- args=(self.install_profile, self,
- InstallProgress.update_status,
- self.quit_event))
- self.time_change_event.set()
- handle.start()
- return handle
-
- @staticmethod
- def perform_install(install_profile, screen, update_status, quit_event,
- time_change_event):
- '''Call function to perform the actual install.
-
- '''
- install_profile.install_succeeded = False
- try:
- perform_ti_install(install_profile, screen, update_status,
- quit_event, time_change_event)
- except BaseException, ex:
- logging.exception(ex)
-
- @staticmethod
- def fake_install(install_profile, screen, update_status, quit_event):
- '''For demonstration purposes only, this function is the target
- of the install thread when the '-n' flag is given at the command
- line. All this thread does is attempt to update the status/progress
- bar at set intervals.
-
- It also checks quit_event (a threading.Event), and, if set, immediately
- returns.
-
- '''
- # When the Text Installer is fully updated to use the new engine,
- # this function should be removed (in favor of simply using the
- # dry_run flag)
-
- install_profile.disk.to_tgt()
- eng = InstallEngine.get_instance()
- eng.execute_checkpoints(start_from =
- sysconfig.GENERATE_SC_PROFILE_CHKPOINT,
- dry_run=True)
- for i in range(101):
- update_status(screen, i, "at %d percent" % i)
- logging.log(LOG_LEVEL_INPUT, "at %s percent", i)
- quit_event.wait(0.2)
- if quit_event.is_set():
- logging.error("User forced quit! Aborting")
- install_profile.install_succeeded = False
- return
- install_profile.install_succeeded = True
-
- def update_status(self, percent, message):
- '''Update this screen to display message and set the status
- bar to 'percent'. This is the intended callback function
- for the installation thread.
-
- This function ensures the screen is not updated more often
- then once every 2 seconds (the update_frequency)
-
- The threading.Event, self.update_event, is used to ensure that this
- method does not clobber the confirm_quit pop-up
-
- '''
- if self.update_event.is_set():
- return
- update_time = time.time()
- if (update_time - self.last_update) > self.update_frequency:
- self._update_status(percent, message)
- self.last_update = update_time
-
- def _update_status(self, percent, message):
- '''Immediately update status, without checking the self.update_event
- or last update_time
-
- '''
- self.set_status_message(message)
- self.set_status_percent(percent)
- self.main_win.redrawwin()
- self.main_win.do_update()
-
- def set_status_message(self, message):
- '''Set the status message on the screen, completely overwriting
- the previous message'''
- self.center_win.add_text(ljust_columns(message, self.status_msg_loc[2]),
- self.status_msg_loc[0],
- self.status_msg_loc[1],
- max_chars=self.status_msg_loc[2])
-
- def set_status_percent(self, percent):
- '''Set the completion percentage by updating the progress bar.
- Note that this is implemented as a 'one-way' change (updating to
- a percent that is lower than previously set will not work)
-
- '''
- width = self.status_bar_width
- complete = int(math.ceil(float(percent) / 100.0 * width))
- self.status_bar.add_text(" " * complete, start_y=0, start_x=1,
- max_chars=width)
- percent_text = "(%i%%)" % percent
- percent_bar = percent_text.center(width)
- left_half = percent_bar[:complete]
- right_half = percent_bar[complete:]
- self.status_bar.window.addstr(0, 1, left_half, self.progress_color)
- self.status_bar.window.addstr(0, complete + 1, right_half)
- self.status_bar.no_ut_refresh()
-
- def init_status_bar(self, y_loc, x_loc, width):
- '''Initialize the progress bar window and set to 0%'''
- self.status_bar_width = width
- status_bar_area = WindowArea(1, width + 3, y_loc, x_loc + 1)
- self.status_bar = InnerWindow(status_bar_area,
- window=self.center_win)
- self.status_bar.window.addch(0, 0, InstallProgress.PROG_BAR_ENDS[0])
- self.status_bar.window.addch(0, width + 1,
- InstallProgress.PROG_BAR_ENDS[1])
- self.progress_color = self.center_win.color_theme.progress_bar
- self.last_update = 0
- self.set_status_percent(0)
-
- def confirm_quit(self):
- '''Confirm the user truly wants to quit, and if so, set quit_event
- so that the install thread can attempt to shut down as gracefully
- as possible.
-
- Clear update_event so that update_status does not attempt to write
- the progress bar while the user is contemplating the need/desire to
- quit.
-
- '''
- self.update_event.set()
- do_quit = self.main_win.pop_up(BaseScreen.CONFIRM_QUIT_HEADER,
- InstallProgress.QUIT_DISK_MODIFIED,
- BaseScreen.CANCEL_BUTTON,
- BaseScreen.CONFIRM_BUTTON)
- update_to = self.update_to
- if update_to is not None:
- self._update_status(update_to[0], update_to[1])
- self.update_event.clear()
-
- if do_quit:
- self.quit_event.set()
- return do_quit
--- a/usr/src/cmd/text-install/osol_install/text_install/install_status.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Report the status of an installation to the user
-'''
-
-import curses
-
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _, RELEASE, TUI_HELP
-from solaris_install.engine import InstallEngine
-from terminalui.action import Action
-from terminalui.base_screen import BaseScreen
-
-
-class RebootException(SystemExit):
- '''Raised when user requests reboot'''
- pass
-
-
-class InstallStatus(BaseScreen):
- '''
- Display text to the user indicating success or failure of the installation.
- Also provide option for viewing the install log
-
- '''
-
- SUCCESS_HEADER = _("Installation Complete")
- FAILED_HEADER = _("Installation Failed")
-
- SUCCESS_TEXT = _("The installation of %(release)s has completed "
- "successfully.\n\n"
- "Reboot to start the newly installed software "
- "or Quit if you wish to perform additional "
- "tasks before rebooting.\n\n"
- "The installation log is available at "
- "%(log_tmp)s. After reboot it can be found"
- " at %(log_final)s.")
-
- FAILED_TEXT = _("The installation did not complete normally.\n\n"
- "For more information you can review the"
- " installation log.\n"
- "The installation log is available at %(log_tmp)s")
-
- def __init__(self, main_win):
- super(InstallStatus, self).__init__(main_win)
- self.log_locations = {}
-
- def set_actions(self):
- '''Remove all actions except Quit, and add actions for rebooting
- and viewing the log.
-
- '''
- self.main_win.actions.pop(curses.KEY_F2) # Remove F2_Continue
- self.main_win.actions.pop(curses.KEY_F3) # Remove F3_Back
- self.main_win.actions.pop(curses.KEY_F6) # Remove F6_Help
-
- doc = InstallEngine.get_instance().doc
- install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
- if install_profile.install_succeeded:
- reboot_action = Action(curses.KEY_F8, _("Reboot"), reboot_system)
- self.main_win.actions[reboot_action.key] = reboot_action
-
- log_action = Action(curses.KEY_F4, _("View Log"),
- self.main_win.screen_list.get_next)
- self.main_win.actions[log_action.key] = log_action
-
-
- def _show(self):
- '''Display the correct text based on whether the installation
- succeeded or failed.
-
- '''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- self.log_locations["log_tmp"] = self.install_profile.log_location
- self.log_locations["log_final"] = self.install_profile.log_final
- if self.install_profile.install_succeeded:
- self.header_text = InstallStatus.SUCCESS_HEADER
- paragraph_text = InstallStatus.SUCCESS_TEXT
- else:
- self.header_text = InstallStatus.FAILED_HEADER
- paragraph_text = InstallStatus.FAILED_TEXT
- self.main_win.set_header_text(self.header_text)
-
- fmt = {}
- fmt.update(self.log_locations)
- fmt.update(RELEASE)
- self.center_win.add_paragraph(paragraph_text % fmt, 2)
-
- def confirm_quit(self):
- '''No need to confirm after installation is complete'''
- return True
-
-
-def reboot_system(screen=None):
- '''Attempts to reboot the system (unless running with the '-n' command
- line flag)
-
- '''
- if screen and screen.install_profile.no_install_mode:
- raise SystemExit("REBOOT")
- else:
- raise RebootException
--- a/usr/src/cmd/text-install/osol_install/text_install/log_viewer.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,89 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Read in and display the install log to the user
-'''
-
-import curses
-
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _
-from solaris_install.engine import InstallEngine
-from terminalui.base_screen import BaseScreen
-from terminalui.i18n import convert_paragraph
-from terminalui.scroll_window import ScrollWindow
-from terminalui.window_area import WindowArea
-
-class LogViewer(BaseScreen):
- '''Screen for reading and displaying the install log'''
-
- HEADER_TEXT = _("Installation Log")
-
- def __init__(self, main_win):
- super(LogViewer, self).__init__(main_win)
- self.log_data = None
- self.scroll_area = None
- self.install_profile = None
-
- def set_actions(self):
- '''Remove all actions except F3_Back'''
- self.main_win.actions.pop(curses.KEY_F2)
- self.main_win.actions.pop(curses.KEY_F6)
- self.main_win.actions.pop(curses.KEY_F9)
-
- def _show(self):
- '''Create a scrollable region and fill it with the install log'''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- self.center_win.border_size = (0, 0)
- self.scroll_area = WindowArea(self.win_size_y,
- self.win_size_x,
- 0, 0, len(self.get_log_data()))
- log = ScrollWindow(self.scroll_area, window=self.center_win)
- log.add_paragraph(self.get_log_data(), 0, 2)
- self.center_win.activate_object(log)
-
- def get_log_data(self):
- '''Attempt to read in the install log file. If an error occurs,
- the log_data is set to a string explaining the cause, if possible.
-
- '''
- if self.log_data is None:
- log_file = None
- try:
- try:
- log_file = open(self.install_profile.log_location)
- log_data = log_file.read()
- except (OSError, IOError), error:
- self.log_data = _("Could not read log file:\n\t%s") % \
- error.strerror
- finally:
- if log_file is not None:
- log_file.close()
- max_chars = self.win_size_x - 4
- self.log_data = convert_paragraph(log_data, max_chars)
- return self.log_data
--- a/usr/src/cmd/text-install/osol_install/text_install/partition_edit_screen.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,272 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-UI Components for displaying a screen allowing the user to edit
-partition and slice information
-'''
-
-import curses
-import logging
-import platform
-
-from osol_install.profile.disk_info import PartitionInfo, SliceInfo
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _, RELEASE, TUI_HELP
-from osol_install.text_install.disk_window import DiskWindow, get_minimum_size
-from solaris_install.engine import InstallEngine
-from terminalui import LOG_LEVEL_INPUT
-from terminalui.action import Action
-from terminalui.base_screen import BaseScreen, SkipException, UIMessage
-from terminalui.window_area import WindowArea
-
-
-class PartEditScreen(BaseScreen):
- '''Allows user editing of partitions on a disk, or slices on a
- disk/partition
-
- '''
-
- PARTITION_PARAGRAPH = _("Oracle Solaris will be installed into the Solaris"
- " partition. A partition's type can be changed"
- " using the F5 key.\n\n"
- "A partition's size can be increased "
- "up to its Avail space. Avail space can be "
- "increased by deleting an adjacent partition. "
- "Delete a partition by changing it to \"Unused\""
- " using the F5 key.\n\n"
- "The four primary partition slots are listed on "
- "the left. If one is an \"Extended\" partition "
- "its logical partitions are listed on the "
- "right.") % RELEASE
- SLICE_PARAGRAPH = _("%(release)s will be installed in the \"%(pool)s\" "
- "slice. Use the F5 key to change a slice to "
- "\"%(pool)s.\"\n\n"
- "A slice's size can be increased up to its Avail "
- "size. Avail can be increased by deleting an adjacent"
- " slice. Use the F5 key to delete a slice by changing"
- " it to \"Unused.\"\n\n"
- "Slices are listed in disk layout order.")
-
- HEADER_x86_PART = _("Select Partition: %(size).1fGB %(type)s "
- "%(bootable)s")
- HEADER_x86_SLICE = _("Select Slice in Fdisk Partition")
- HEADER_SPARC_SLICE = _("Select Slice: %(size).1fGB %(type)s"
- "%(bootable)s")
- SLICE_DESTROY_TEXT = _("indicates the slice's current content will be "
- "destroyed")
- PART_DESTROY_TEXT = _("indicates the partition's current content will "
- "be destroyed")
- BOOTABLE = _("Boot")
-
- SPARC_HELP = (TUI_HELP + "/%s/"
- "sparc_solaris_slices_select.txt",
- _("Select Slice"))
- X86_PART_HELP = (TUI_HELP + "/%s/"
- "x86_fdisk_partitions_select.txt",
- _("Select Partition"))
- X86_SLICE_HELP = (TUI_HELP + "/%s/"
- "x86_fdisk_slices_select.txt",
- _("Select Slice"))
-
- HELP_FORMAT = " %s"
-
- def __init__(self, main_win, x86_slice_mode=False):
- super(PartEditScreen, self).__init__(main_win)
- self.x86_slice_mode = x86_slice_mode
- self.is_x86 = (platform.processor() == "i386")
- self.header_text = platform.processor()
-
- if self.x86_slice_mode: # x86, Slice within a partition
- self.instance = ".slice"
- self.header_text = PartEditScreen.HEADER_x86_SLICE
- self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH
- self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT
- self.help_data = PartEditScreen.X86_SLICE_HELP
- elif self.is_x86: # x86, Partition on disk
- self.header_text = PartEditScreen.HEADER_x86_PART
- self.paragraph_text = PartEditScreen.PARTITION_PARAGRAPH
- self.destroy_text = PartEditScreen.PART_DESTROY_TEXT
- self.help_data = PartEditScreen.X86_PART_HELP
- else: # SPARC (Slice on disk)
- self.header_text = PartEditScreen.HEADER_SPARC_SLICE
- self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH
- self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT
- self.help_data = PartEditScreen.SPARC_HELP
- self.help_format = " %s"
-
- self.orig_data = None
- self.disk_win = None
-
- def set_actions(self):
- '''Edit Screens add 'Reset' and 'Change Type' actions. Since these
- do not manipulate screen direction, they are captured during
- processing by adding them to center_win's key_dict.
-
- '''
- super(PartEditScreen, self).set_actions()
- reset_action = Action(curses.KEY_F7, _("Reset"))
- change_action = Action(curses.KEY_F5, _("Change Type"))
- self.main_win.actions[reset_action.key] = reset_action
- self.main_win.actions[change_action.key] = change_action
- self.center_win.key_dict[curses.KEY_F7] = self.on_key_F7
-
- # pylint: disable-msg=C0103
- # F7 is the keyname and appropriate here
- def on_key_F7(self, dummy):
- '''F7 -> Reset the DiskWindow'''
- self.disk_win.reset()
- return None
-
- def _show(self):
- '''Display the explanatory paragraph and create the DiskWindow'''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- part = self.install_profile.disk
- if part.use_whole_segment:
- logging.debug("disk.use_whole_segment true, skipping editing")
- raise SkipException
- if self.x86_slice_mode:
- part = part.get_solaris_data()
- if part is None:
- err_msg = "Critical error - no Solaris partition found"
- logging.error(err_msg)
- raise ValueError(err_msg)
- if part.use_whole_segment:
- logging.debug("partition.use_whole_segment True:"
- " skipping slice editing")
- raise SkipException
- _orig_disk = self.install_profile.original_disk
- self.orig_data = _orig_disk.get_solaris_data()
- if self.orig_data is None:
- def_type = PartitionInfo.SOLARIS
- def_size = self.install_profile.disk.size
- self.orig_data = PartitionInfo(part_num=1,
- partition_id=def_type,
- size=def_size)
- else:
- self.orig_data = self.install_profile.original_disk
-
- if self.x86_slice_mode:
- header = self.header_text
- else:
- bootable = ""
- if self.is_x86 and part.boot:
- bootable = PartEditScreen.BOOTABLE
- header = self.header_text % {"size" : part.size.size_as("gb"),
- "type" : part.type,
- "bootable" : bootable}
- self.main_win.set_header_text(header)
-
- y_loc = 1
- fmt_dict = {'pool' : SliceInfo.DEFAULT_POOL}
- fmt_dict.update(RELEASE)
- y_loc += self.center_win.add_paragraph(self.paragraph_text % fmt_dict,
- y_loc)
-
- y_loc += 1
- disk_win_area = WindowArea(6, 70, y_loc, 0)
- self.disk_win = DiskWindow(disk_win_area, part,
- window=self.center_win,
- editable=True,
- error_win=self.main_win.error_line,
- reset=self.orig_data)
- y_loc += disk_win_area.lines
-
- y_loc += 1
- logging.log(LOG_LEVEL_INPUT, "calling addch with params start_y=%s,"
- "start_x=%s, ch=%c", y_loc, self.center_win.border_size[1],
- DiskWindow.DESTROYED_MARK)
- self.center_win.window.addch(y_loc, self.center_win.border_size[1],
- DiskWindow.DESTROYED_MARK,
- self.center_win.color_theme.inactive)
- self.center_win.add_text(self.destroy_text, y_loc, 2)
-
- self.main_win.do_update()
- self.center_win.activate_object(self.disk_win)
-
- def on_prev(self):
- '''Clear orig_data so re-visits reset correctly'''
- self.orig_data = None
-
- def on_continue(self):
- '''Get the modified partition/slice data from the DiskWindow, and
- update install_profile.disk with it
-
- '''
- disk_info = self.disk_win.disk_info
- if self.x86_slice_mode:
- solaris_part = self.install_profile.disk.get_solaris_data()
- solaris_part.slices = disk_info.slices
- elif self.is_x86:
- self.install_profile.disk.partitions = disk_info.partitions
- solaris_part = self.install_profile.disk.get_solaris_data()
- # If the Solaris partition has changed in any way, the entire
- # partition is used as the install target.
- if solaris_part.modified():
- logging.debug("Solaris partition modified, "
- "creating default layout")
- solaris_part.create_default_layout()
- else:
- logging.debug("Solaris partition unchanged, using original"
- " slice data")
- solaris_part.slices = solaris_part.orig_slices
- else:
- self.install_profile.disk.slices = disk_info.slices
-
- def validate(self):
- '''Ensure the Solaris partition or ZFS Root exists and is large
- enough
-
- '''
- disk_info = self.disk_win.disk_info
- if self.is_x86 and not self.x86_slice_mode:
- min_size_text = _("The Solaris2 partition must be at least"
- " %(size).1fGB")
- missing_part = _("There must be exactly one Solaris2 partition.")
- else:
- min_size_text = _("The size of %(pool)s must be at least"
- " %(size).1fGB")
- missing_part = _("There must be one ZFS root pool, '%(pool)s.'")
- min_size = round(get_minimum_size().size_as("gb"), 1)
- format_dict = {'pool' : SliceInfo.DEFAULT_POOL,
- 'size': min_size}
-
- try:
- part = disk_info.get_solaris_data(check_multiples=True)
- except ValueError:
- part = None
-
- if part is None:
- raise UIMessage(missing_part % format_dict)
-
- # When comparing sizes, check only to the first decimal place,
- # as that is all the user sees. (Rounding errors that could
- # cause the partition/slice layout to be invalid get cleaned up
- # prior to target instantiation)
- part_size = round(part.size.size_as("gb"), 1)
- if part_size < min_size:
- raise UIMessage(min_size_text % format_dict)
--- a/usr/src/cmd/text-install/osol_install/text_install/summary.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,224 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Display a summary of the user's selections
-'''
-
-import curses
-import logging
-
-from osol_install.profile.disk_info import SliceInfo
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _, RELEASE, TUI_HELP
-from solaris_install.engine import InstallEngine
-import solaris_install.sysconfig.profile
-from solaris_install.sysconfig.profile.network_info import NetworkInfo
-from solaris_install.sysconfig.profile.user_info import UserInfo
-from terminalui.action import Action
-from terminalui.base_screen import BaseScreen
-from terminalui.i18n import convert_paragraph
-from terminalui.window_area import WindowArea
-from terminalui.scroll_window import ScrollWindow
-
-
-class SummaryScreen(BaseScreen):
- '''Display a summary of the install profile to the user
- InnerWindow.__init__ is sufficient to initalize an instance
- of SummaryScreen
-
- '''
-
- HEADER_TEXT = _("Installation Summary")
- PARAGRAPH = _("Review the settings below before installing."
- " Go back (F3) to make changes.")
-
- HELP_DATA = (TUI_HELP + "/%s/summary.txt",
- _("Installation Summary"))
-
- INDENT = 2
-
- def set_actions(self):
- '''Replace the default F2_Continue with F2_Install'''
- install_action = Action(curses.KEY_F2, _("Install"),
- self.main_win.screen_list.get_next)
- self.main_win.actions[install_action.key] = install_action
-
- def _show(self):
- '''Prepare a text summary from the install_profile and display it
- to the user in a ScrollWindow
-
- '''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- self.sysconfig = solaris_install.sysconfig.profile.from_engine()
-
- y_loc = 1
- y_loc += self.center_win.add_paragraph(SummaryScreen.PARAGRAPH, y_loc)
-
- y_loc += 1
- summary_text = self.build_summary()
- # Wrap the summary text, accounting for the INDENT (used below in
- # the call to add_paragraph)
- max_chars = self.win_size_x - SummaryScreen.INDENT - 1
- summary_text = convert_paragraph(summary_text, max_chars)
- area = WindowArea(x_loc=0, y_loc=y_loc,
- scrollable_lines=(len(summary_text)+1))
- area.lines = self.win_size_y - y_loc
- area.columns = self.win_size_x
- scroll_region = ScrollWindow(area, window=self.center_win)
- scroll_region.add_paragraph(summary_text, start_x=SummaryScreen.INDENT)
-
- self.center_win.activate_object(scroll_region)
-
- def build_summary(self):
- '''Build a textual summary from the install_profile'''
- lines = []
-
- lines.append(_("Software: %s") % self.get_release())
- lines.append("")
- lines.append(self.get_disk_summary())
- lines.append("")
- lines.append(self.get_tz_summary())
- lines.append("")
- lines.append(_("Language: *The following can be changed when "
- "logging in."))
- if self.sysconfig.system.locale is None:
- self.sysconfig.system.determine_locale()
- lines.append(_(" Default language: %s") %
- self.sysconfig.system.actual_lang)
- lines.append("")
- lines.append(_("Keyboard layout: *The following can be "
- "changed when logging in."))
- lines.append(_(" Default keyboard layout: %s") %
- self.sysconfig.system.keyboard)
- lines.append("")
- lines.append(_("Terminal type: %s") %
- self.sysconfig.system.terminal_type)
- lines.append("")
- lines.append(_("Users:"))
- lines.extend(self.get_users())
- lines.append("")
- lines.append(_("Network:"))
- lines.extend(self.get_networks())
-
- return "\n".join(lines)
-
- def get_networks(self):
- '''Build a summary of the networks in the install_profile,
- returned as a list of strings
-
- '''
- network_summary = []
- network_summary.append(_(" Computer name: %s") %
- self.sysconfig.system.hostname)
- nic = self.sysconfig.nic
-
- if nic.type == NetworkInfo.AUTOMATIC:
- network_summary.append(_(" Network Configuration: Automatic"))
- elif nic.type == NetworkInfo.NONE:
- network_summary.append(_(" Network Configuration: None"))
- else:
- network_summary.append(_(" Manual Configuration: %s")
- % nic.nic_name)
- network_summary.append(_(" IP Address: %s") % nic.ip_address)
- network_summary.append(_(" Netmask: %s") % nic.netmask)
- if nic.gateway:
- network_summary.append(_(" Router: %s") % nic.gateway)
- if nic.dns_address:
- network_summary.append(_(" DNS: %s") % nic.dns_address)
- if nic.domain:
- network_summary.append(_(" Domain: %s") % nic.domain)
- return network_summary
-
- def get_users(self):
- '''Build a summary of the user information, and return it as a list
- of strings
-
- '''
- root = self.sysconfig.users[UserInfo.ROOT_IDX]
- primary = self.sysconfig.users[UserInfo.PRIMARY_IDX]
- user_summary = []
- if not root.password:
- user_summary.append(_(" Warning: No root password set"))
- if primary.login_name:
- user_summary.append(_(" Username: %s") % primary.login_name)
- else:
- user_summary.append(_(" No user account"))
- return user_summary
-
- def get_disk_summary(self):
- '''Return a string summary of the disk selection'''
- disk = self.install_profile.disk
-
- solaris_data = disk.get_solaris_data()
- if isinstance(solaris_data, SliceInfo):
- slice_data = solaris_data
- part_data = None
- else:
- part_data = solaris_data
- slice_data = part_data.get_solaris_data()
-
- format_dict = {}
- disk_string = [_("Disk: %(disk-size).1fGB %(disk-type)s")]
- format_dict['disk-size'] = disk.size.size_as("gb")
- format_dict['disk-type'] = disk.type
-
- if part_data is not None:
- disk_string.append(_("Partition: %(part-size).1fGB %(part-type)s"))
- format_dict['part-size'] = part_data.size.size_as("gb")
- format_dict['part-type'] = part_data.get_description()
-
- if part_data is None or not part_data.use_whole_segment:
- disk_string.append(_("Slice %(slice-num)i: %(slice-size).1fGB"
- " %(pool)s"))
- format_dict['slice-num'] = slice_data.number
- format_dict['slice-size'] = slice_data.size.size_as("gb")
- format_dict['pool'] = slice_data.type[1]
-
- return "\n".join(disk_string) % format_dict
-
- def get_tz_summary(self):
- '''Return a string summary of the timezone selection'''
- timezone = self.sysconfig.system.tz_timezone
- return _("Time Zone: %s") % timezone
-
- @staticmethod
- def get_release():
- '''Read in the release information from /etc/release'''
- try:
- try:
- release_file = open("/etc/release")
- except IOError:
- logging.warn("Could not read /etc/release")
- release_file = None
- release = RELEASE['release']
- else:
- release = release_file.readline()
- finally:
- if release_file is not None:
- release_file.close()
- return release.strip()
--- a/usr/src/cmd/text-install/osol_install/text_install/test/test_disk_select.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-#!/usr/bin/python2.6
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-To run these tests:
-
-1) nightly -n developer.sh # build the gate
-2) export PYTHONPATH=${WS}/proto/root_i386/usr/snadm/lib:${WS}/proto/root_i386/usr/lib/python2.6/vendor-packages
-3) pfexec python2.6 test_disk_select.py
-
-A single test may be run by specifying the test as an argument to step 3, e.g.:
-pfexec python2.6 test_disk_select.py OnActivateTest.test_on_activate_default
-
-Since the proto area is used for the PYTHONPATH, the gate must be rebuilt for
-these tests to pick up any changes in the tested code.
-
-'''
-
-import numbers
-import unittest
-
-import osol_install.text_install.disk_selection as disk_selection
-from osol_install.profile.disk_info import DiskInfo
-import terminalui
-from terminalui.base_screen import BaseScreen
-
-
-terminalui.init_logging("test")
-BaseScreen.set_default_quit_text("test", "test", "test", "test")
-
-class MockCenterWin(object):
- '''Mocks an InnerWindow as used by a MainWindow'''
-
- def add_paragraph(self, *args, **kwargs):
- pass
-
-class MockDiskInfo(object):
- '''Mocks a DiskInfo object'''
- do_copy = False
- label = []
- was_blank = False
- use_whole_segment = False
-
- def create_default_layout(self):
- pass
-
-class MockDiskDetail(object):
- '''Mocks a DiskWindow object'''
-
- def set_disk_info(self, *args):
- pass
-
-class MockDiskScreen(object):
- '''Mocks the DiskScreen'''
- win_size_x = 0
- proposed_text = ""
- found_text = ""
-
-class MockInstallProfile(object):
- '''Mocks an InstallProfile'''
-
- disk = None
- original_disk = None
-
-class MockAll(object):
- '''Generic Mock object that 'never' raises an AttributeError'''
-
- def __getattr__(self, name):
- return self
-
- def __call__(self, *args, **kwargs):
- return None
-
-class OnActivateTest(unittest.TestCase):
- '''Test disk_selection.on_activate'''
-
- def setUp(self):
- self.screen = MockDiskScreen()
- self.disk = MockDiskInfo()
- self.screen.center_win = MockCenterWin()
- self.screen.disk_detail = MockDiskDetail()
-
- def tearDown(self):
- self.screen = None
- self.disk = None
-
- def test_on_activate_default(self):
- '''Ensure that do_copy flag is set after calls to on_activate'''
- disk_selection.on_activate(disk_info=self.disk,
- disk_select=self.screen)
- self.assertFalse(self.disk.use_whole_segment)
- self.assertTrue(self.screen.do_copy)
-
- def test_on_activate_GPT(self):
- '''Ensure use_whole_segment is set if the disk was GPT labeled'''
-
- self.disk.label = [DiskInfo.GPT]
-
- disk_selection.on_activate(disk_info=self.disk,
- disk_select=self.screen)
- self.assertTrue(self.disk.use_whole_segment)
-
- def test_on_activate_was_blank(self):
- '''Ensure use_whole_segment is set when the disk was initially blank'''
- self.disk.was_blank = True
-
- disk_selection.on_activate(disk_info=self.disk,
- disk_select=self.screen)
- self.assertTrue(self.disk.use_whole_segment)
-
-
-class DiskSelectTest(unittest.TestCase):
- '''Test the DiskScreen'''
-
- def setUp(self):
- self.screen = disk_selection.DiskScreen(MockAll())
- self.screen.disk_win = MockAll()
-
- def test_on_change_screen_disk_detail_none(self):
- '''Ensure selected_disk is set properly by on_change_screen'''
- self.screen.disk_detail = None
- obj = object()
- self.screen.selected_disk = obj
-
- self.screen.on_change_screen()
- self.assertTrue(self.screen.selected_disk is obj)
-
- def test_on_change_screen_do_copy(self):
- '''Ensure disk is copied when do_copy flag is set'''
- self.screen.install_profile = MockInstallProfile()
- self.screen.install_profile.disk = True
-
- obj = object()
- self.screen.disk_win.active_object = obj
- self.screen.selected_disk = None
-
- disk = MockDiskInfo()
- self.screen.disk_detail = MockAll()
- self.screen.disk_detail.disk_info = disk
- self.screen.do_copy = True
-
- self.screen.on_change_screen()
-
- self.assertTrue(self.screen.selected_disk is obj)
- self.assertTrue(self.screen.install_profile.original_disk is disk)
-
- def test_on_change_screen_disk_is_none(self):
- '''Check DiskScreen.on_change_screen when disk is None'''
- self.screen.install_profile = MockInstallProfile()
-
- disk = MockDiskInfo()
- self.screen.disk_detail = MockAll()
- self.screen.disk_detail.disk_info = disk
-
- self.screen.on_change_screen()
-
- self.assertTrue(self.screen.install_profile.original_disk is disk)
-
- def test_size_line(self):
- '''Ensure that DiskScreen._size_line is created and is a string after
- calling get_size_line. Also verify that subsequent calls do not modify
- the _size_line
-
- '''
- self.assertTrue(self.screen._size_line is None)
- self.screen.get_size_line()
- self.assertTrue(isinstance(self.screen._size_line, basestring))
-
- obj = object()
- self.screen._size_line = obj
- self.screen.get_size_line()
- self.assertTrue(obj is self.screen._size_line)
-
- def test_determine_size_data(self):
- '''Ensure that recommended_size and minimum_size are accessible after
- a call to determine_size_data(), and that they are numbers'''
-
- self.assertTrue(self.screen._recommended_size is None)
- self.assertTrue(self.screen._minimum_size is None)
- self.screen.determine_size_data()
- self.assertTrue(isinstance(self.screen.minimum_size, numbers.Real))
- self.assertTrue(isinstance(self.screen.recommended_size, numbers.Real))
-
-
-if __name__ == '__main__':
- unittest.main()
--- a/usr/src/cmd/text-install/osol_install/text_install/test/test_disk_window.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,273 +0,0 @@
-#!/usr/bin/python2.6
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-To run these tests:
-
-1) nightly -n developer.sh # build the gate
-2) export PYTHONPATH=${WS}/proto/root_i386/usr/snadm/lib:${WS}/proto/root_i386/usr/lib/python2.6/vendor-packages
-3) python2.6 test_disk_window.py
-
-A single test may be run by specifying the test as an argument to step 3:
-python2.6 test_disk_window.py UpdateEditField.test_part_info_not_editable
-
-Since the proto area is used for the PYTHONPATH, the gate must be rebuilt for
-these tests to pick up any changes in the tested code.
-
-'''
-
-import unittest
-
-from osol_install.profile.disk_info import DiskInfo
-from osol_install.text_install.disk_window import DiskWindow
-import terminalui
-from terminalui.color_theme import ColorTheme
-from terminalui.window_area import WindowArea
-from terminalui.inner_window import InnerWindow
-
-
-terminalui.init_logging("test")
-
-
-class MockAll(object):
- '''Generic Mock object that 'never' raises an AttributeError'''
-
- def __getattr__(self, name):
- return self
-
- def __call__(self, *args, **kwargs):
- return self
-
-class MockInnerWin(object):
- '''Class for mock inner win'''
- def __init__(self):
- self.active_object = None
- self.objects = []
- self.text = None
- self.scrollable_lines = 10
-
- def activate_object(self):
- '''Set active_object to 0'''
- self.active_object = 0
-
- def get_active_object(self):
- '''Get active_object'''
- if self.active_object is not None:
- return self.objects[self.active_object]
- else:
- return None
-
- def add_text(self, text=None, y_loc=0, x_loc=0):
- '''Append passed in text to self.text'''
- if self.text is None:
- self.text = str(y_loc) + text
- else:
- self.text = self.text + str(y_loc) + text
-
- def get_text(self):
- '''Get the current value of self.text'''
- return self.text
-
-class MockDiskInfo(object):
- '''Class for mock disk info'''
-
- def __init__(self, logicals=0):
- self.logicals = []
- for i in range(logicals):
- self.logicals.append(object())
- def get_logicals(self):
- '''get logical partitions'''
- return self.logicals
-
-class MockPartInfo(object):
- '''Class for mock part info field'''
- def __init__(self):
- self.is_editable = True
-
- def set_editable(self, editable=True):
- '''Set editable property'''
- self.is_editable = editable
-
- def editable(self, editable):
- ''' get editable setting'''
- return self.is_editable
-
-class MockEditField(object):
- '''Class for mock edit field'''
- def __init__(self):
- self.active = False
-
- def make_inactive(self, inactive=True):
- ''' Set active property'''
- self.active = not inactive
-
-class MockPartField(object):
- '''Class for mock part field'''
- def __init__(self):
- self.edit_field = None
- self.active_object = None
- self.objects = []
- def activate_object(self, tobject):
- '''activate object'''
- self.active_object = 0
- self.edit_field = tobject
- self.edit_field.make_inactive(False)
- def get_active_object(self):
- '''Get active_object'''
- if self.active_object is not None:
- return self.objects[self.active_object]
- else:
- return None
-
-def do_nothing(*args, **kwargs):
- '''does nothing'''
- pass
-
-
-class TestDiskWindow(unittest.TestCase):
- '''Class to test DiskWindow'''
-
- def setUp(self):
- '''unit test set up
- Sets several functions to call do_nothing to allow
- test execution in non-curses environment. Original
- functions are saved so they can be later restored in
- tearDown.
-
- '''
- self.inner_window_init_win = InnerWindow._init_win
- self.disk_window_init_win = DiskWindow._init_win
- self.inner_window_set_color = InnerWindow.set_color
- InnerWindow._init_win = do_nothing
- InnerWindow.set_color = do_nothing
- DiskWindow._init_win = do_nothing
- self.disk_win = DiskWindow(WindowArea(70, 70, 0, 0), DiskInfo(),
- color_theme=ColorTheme(force_bw=True),
- window=MockAll())
- self.edit_field = MockEditField()
- self.part_field = MockPartField()
- self.part_info = MockPartInfo()
- self.inner_win = MockInnerWin()
- self.disk_win.objects = []
- self.part_field.objects = []
-
- def tearDown(self):
- '''unit test tear down
- Functions originally saved in setUp are restored to their
- original values.
- '''
- InnerWindow._init_win = self.inner_window_init_win
- InnerWindow.set_color = self.inner_window_set_color
- DiskWindow._init_win = self.disk_window_init_win
- self.disk_win = None
- self.edit_field = None
- self.part_field = None
- self.part_info = None
- self.inner_win = None
-
-
-class UpdateEditField(TestDiskWindow):
- '''Tests for _update_edit_field'''
-
- def test_part_info_not_editable(self):
- '''Ensure edit field updated correctly if part_info not editable'''
- self.part_info.set_editable(False)
- self.edit_field.make_inactive(False)
- self.part_field.activate_object(self.edit_field)
- self.part_field.objects.append(self.edit_field)
-
- self.disk_win._update_edit_field(self.part_info, self.part_field,
- self.edit_field)
- self.assertFalse(self.edit_field.active)
- self.assertEquals(len(self.part_field.objects), 0)
- self.assertTrue(self.part_field.active_object is None)
-
- def test_part_info_editable_no_active_win(self):
- '''Ensure edit field not updated if no active right/left win'''
- self.part_info.set_editable(True)
- self.edit_field.make_inactive(True)
- self.assertTrue(self.part_field.active_object is None)
- self.disk_win._update_edit_field(self.part_info, self.part_field,
- self.edit_field)
- self.assertTrue(self.part_field.active_object is None)
- self.assertFalse(self.edit_field.active)
-
- def test_part_info_editable_active_win_no_active_obj(self):
- '''Ensure edit field updated correctly if active obj not part_field'''
- self.part_info.set_editable(True)
- self.edit_field.make_inactive(True)
- self.disk_win.objects.append(self.inner_win)
- self.disk_win.active_object = 0
- my_part_field = MockPartField()
- self.inner_win.objects.append(my_part_field)
- self.inner_win.active_object = 0
-
- self.assertTrue(self.inner_win.get_active_object() is my_part_field)
- self.assertTrue(self.part_field.active_object is None)
-
- self.disk_win._update_edit_field(self.part_info, self.part_field,
- self.edit_field)
- self.assertTrue(self.part_field.active_object is None)
- self.assertFalse(self.edit_field.active)
-
- def test_part_info_editable_active_win(self):
- '''Ensure edit field updated correctly if part_info is editable'''
- self.part_info.set_editable(True)
- self.edit_field.make_inactive(True)
- self.disk_win.objects.append(self.inner_win)
- self.disk_win.active_object = 0
- self.inner_win.objects.append(self.part_field)
- self.inner_win.active_object = 0
-
- self.assertEquals(self.disk_win.active_object, 0)
- self.assertTrue(self.part_field.active_object is None)
-
- self.disk_win._update_edit_field(self.part_info, self.part_field,
- self.edit_field)
- self.assertEquals(self.part_field.active_object, 0)
- self.assertTrue(self.edit_field.active)
-
-
-class UpdateAllAvailSpace(TestDiskWindow):
- '''Tests for _update_all_avail_space'''
-
- def test_blank_new_logicals(self):
- '''Ensure new logical sizes are blanked'''
- self.disk_win.headers = DiskWindow.EDIT_PARTITION_HEADERS
- self.disk_win.left_win = self.inner_win
- self.disk_win.right_win = self.inner_win
- self.disk_win._orig_data = MockDiskInfo(logicals=6)
- my_area = MockInnerWin()
- self.disk_win.right_win.area = my_area
-
- self.disk_win._update_all_avail_space()
- num_logicals = len(self.disk_win._orig_data.logicals)
- idx = my_area.scrollable_lines - num_logicals
- text_expect = ""
- for i in range(num_logicals, my_area.scrollable_lines):
- text_expect = text_expect + str(i) + " "
- self.assertEqual(text_expect, self.disk_win.right_win.get_text())
-
-if __name__ == '__main__':
- unittest.main()
--- a/usr/src/cmd/text-install/osol_install/text_install/test/test_text_install.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-#!/usr/bin/python2.6
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-To run these tests, see the instructions in usr/src/tools/tests/README.
-Remember that since the proto area is used for the PYTHONPATH, the gate
-must be rebuilt for these tests to pick up any changes in the tested code.
-
-'''
-
-
-import unittest
-
-import osol_install.text_install as text_install
-
-
-class TestTextInstall(unittest.TestCase):
-
- def test_reboot_cmds(self):
- '''_reboot_cmds(x86) returns expected list of potential reboot commands
- * /usr/sbin/reboot -f -- rpool/ROOT/<BE>
- * /usr/sbin/reboot
-
- '''
- cmds = text_install._reboot_cmds(True)
-
- # First command should be: /usr/sbin/reboot -f -- rpool/ROOT/<BE>
- self.assertEqual(cmds[0][0], text_install.REBOOT)
- self.assertEqual(cmds[0][1], "-f")
- self.assertEqual(cmds[0][2], "--")
- # Last item will be something like: rpool/ROOT/active-on-reboot-BE
- # Just ensure that there's something there
- self.assertTrue(cmds[0][3])
- self.assertEqual(len(cmds[0]), 4)
-
- # Second command should be just: /usr/sbin/reboot
- self.assertEqual(cmds[1][0], text_install.REBOOT)
- self.assertEqual(len(cmds[1]), 1)
-
- self.assertEqual(len(cmds), 2)
-
- def test_reboot_cmds_sparc(self):
- '''_reboot_cmds(sparc) returns expected list of reboot commands
- (SPARC requires the additional "-Z")
- * /usr/sbin/reboot -f -- -Z rpool/ROOT/<BE>
- * /usr/sbin/reboot
-
- '''
- cmds = text_install._reboot_cmds(False)
-
- # First command should be: /usr/sbin/reboot -f -- -Z rpool/ROOT/<BE>
- self.assertEqual(cmds[0][0], text_install.REBOOT)
- self.assertEqual(cmds[0][1], "-f")
- self.assertEqual(cmds[0][2], "--")
- self.assertEqual(cmds[0][3], "-Z")
- # Last item will be something like: rpool/ROOT/active-on-reboot-BE
- # Just ensure that there's something there
- self.assertTrue(cmds[0][4])
- self.assertEqual(len(cmds[0]), 5)
-
- # Second command should be just: /usr/sbin/reboot
- self.assertEqual(cmds[1][0], text_install.REBOOT)
- self.assertEqual(len(cmds[1]), 1)
-
- self.assertEqual(len(cmds), 2)
--- a/usr/src/cmd/text-install/osol_install/text_install/text-install Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#!/usr/bin/python2.6
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-import osol_install.text_install as text_install
-
-text_install.main()
--- a/usr/src/cmd/text-install/osol_install/text_install/ti_install.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,594 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Installation engine for Text Installer
-'''
-
-import os
-import logging
-import commands
-import datetime
-import platform
-import shutil
-import subprocess as sp
-
-from libbe_py import beUnmount
-from osol_install.install_utils import exec_cmd_outputs_to_log
-from osol_install.libzoneinfo import tz_isvalid
-from osol_install.profile.disk_info import PartitionInfo
-from osol_install.text_install import RELEASE
-from osol_install.text_install import ti_install_utils as ti_utils
-from osol_install import tgt
-from osol_install.transfer_mod import tm_perform_transfer, tm_abort_transfer
-from osol_install.transfer_defs import TM_ATTR_MECHANISM, \
- TM_PERFORM_CPIO, TM_CPIO_ACTION, TM_CPIO_ENTIRE, TM_CPIO_SRC_MNTPT, \
- TM_CPIO_DST_MNTPT, TM_UNPACK_ARCHIVE, TM_SUCCESS
-from solaris_install.engine import InstallEngine
-
-import solaris_install.sysconfig as sysconfig
-
-#
-# RTC command to run
-#
-RTC_CMD = "/usr/sbin/rtc"
-
-#
-# Program used for calling the C ICT functions.
-# When all the C-based ICT functions are converted to Python (bug 6256),
-# this can be removed.
-#
-ICT_PROG = "/opt/install-test/bin/ict_test"
-
-
-# The following is defined for using the ICT program. It can be removed
-# once the ict_test program is not used.
-CPIO_TRANSFER = "0"
-
-INSTALL_FINISH_PROG = "/usr/sbin/install-finish"
-
-# Initial BE name
-INIT_BE_NAME = "solaris"
-
-# definitions for ZFS pool
-INSTALL_SNAPSHOT = "install"
-
-INSTALLED_ROOT_DIR = "/a"
-
-# these directories must be defined in this order, otherwise,
-# "zfs unmount" fails
-ZFS_SHARED_FS = ["/export/home", "/export"]
-
-#
-# Handle for accessing the InstallStatus class
-#
-INSTALL_STATUS = None
-
-
-class InstallStatus(object):
- '''Stores information on the installation progress, and provides a
- hook for updating the screen.
-
- '''
- TI = "ti"
- TM = "tm"
- ICT = "ict"
-
- def __init__(self, screen, update_status_func, quit_event):
- '''screen and update_status_func are values passed in from
- the main app
-
- '''
-
- # Relative ratio used for computing the overall progress of the
- # installation. All numbers must add up to 100%
- self.ratio = {InstallStatus.TI: 0.05,
- InstallStatus.TM: 0.93,
- InstallStatus.ICT: 0.02}
-
- self.screen = screen
- self.update_status_func = update_status_func
- self.quit_event = quit_event
- self.previous_step_name = None
- self.step_percent_completed = 0
- self.previous_overall_progress = 0
-
- def update(self, step_name, percent_completed, message):
- '''Update the install status. Also checks the quit_event to see
- if the installation should be aborted.
-
- '''
- if self.quit_event.is_set():
- logging.debug("User selected to quit")
- raise ti_utils.InstallationError
- if (self.previous_step_name is None):
- self.previous_step_name = step_name
- elif (self.previous_step_name != step_name):
- self.previous_step_name = step_name
- self.step_percent_completed = self.previous_overall_progress
- overall_progress = (percent_completed * (self.ratio[step_name])) \
- + self.step_percent_completed
- self.update_status_func(self.screen, overall_progress, message)
- self.previous_overall_progress = overall_progress
-
-
-def transfer_mod_callback(percent, message):
- '''Callback for transfer module to indicate percentage complete.'''
- logging.debug("tm callback: %s: %s", percent, message)
- try:
- INSTALL_STATUS.update(InstallStatus.TM, percent, message)
- except ti_utils.InstallationError:
- # User selected to quit the transfer
- tm_abort_transfer()
-
-
-def exec_cmd(cmd, description):
- ''' Execute the given command.
-
- Args:
- cmd: Command to execute. The command and it's arguments
- should be provided as a list, suitable for used
- with subprocess.Popen(shell=False)
- description: Description to use for printing errors.
-
- Raises:
- InstallationError
-
- '''
- logging.debug("Executing: %s", " ".join(cmd))
- if exec_cmd_outputs_to_log(cmd, logging) != 0:
- logging.error("Failed to %s", description)
- raise ti_utils.InstallationError
-
-
-def cleanup_existing_install_target(install_profile, inst_device):
- ''' If installer was restarted after the failure, it is necessary
- to destroy the pool previously created by the installer.
-
- If there is a root pool manually imported by the user with
- the same name which will be used by the installer
- for target root pool, we don't want to destroy user's data.
- So, we will log warning message and abort the installation.
-
- '''
-
- # Umount /var/run/boot_archive, which might be mounted by
- # previous x86 installations.
- # Error from this command is intentionally ignored, because the
- # previous invocation of the transfer module might or might not have
- # mounted on the mount point.
- if platform.processor() == "i386":
- with open("/dev/null", "w") as null_handle:
- sp.Popen(["/usr/sbin/umount", "-f", "/var/run/boot_archive"],
- stdout=null_handle, stderr=null_handle)
-
- rootpool_name = install_profile.disk.get_install_root_pool()
-
- cmd = "/usr/sbin/zpool list " + rootpool_name
- logging.debug("Executing: %s", cmd)
- status = commands.getstatusoutput(cmd)[0]
- if status != 0:
- logging.debug("Root pool %s does not exist", rootpool_name)
- return # rpool doesn't exist, no need to clean up
-
- # Check value of rpool's org.opensolaris.caiman:install property
- # If it is busy, that means the pool is left over from an aborted install.
- # If the property doesn't exist or has another value, we assume
- # that the root pool contains valid Solaris instance.
- cmd = "/usr/sbin/zfs get -H -o value org.opensolaris.caiman:install " + \
- rootpool_name
- logging.debug("Executing: %s", cmd)
- (status, pool_status) = commands.getstatusoutput(cmd)
- logging.debug("Return code: %s", status)
- logging.debug("Pool status: %s", pool_status)
- if (status != 0) or (pool_status != "busy"):
- logging.error("Root pool %s exists.", rootpool_name)
- logging.error("Installation can not proceed")
- raise ti_utils.InstallationError
-
- try:
- rpool = tgt.Zpool(rootpool_name, inst_device)
- tgt.release_zfs_root_pool(rpool)
- logging.debug("Completed release_zfs_root_pool")
- except TypeError, te:
- logging.error("Failed to release existing rpool.")
- logging.exception(te)
- raise ti_utils.InstallationError
-
- # clean up the target mount point
- exec_cmd(["/usr/bin/rm", "-rf", INSTALLED_ROOT_DIR + "/*"],
- "clean up existing mount point")
-
-
-def do_ti(install_profile, swap_dump):
- '''Call the ti module to create the disk layout, create a zfs root
- pool, create zfs volumes for swap and dump, and to create a be.
-
- '''
- diskname = install_profile.disk.name
- logging.debug("Diskname: %s", diskname)
- mesg = "Preparing disk for %(release)s installation" % RELEASE
- try:
- (inst_device, inst_device_size) = \
- install_profile.disk.get_install_dev_name_and_size()
-
- # The installation size we provide already included the required
- # swap size
- (swap_type, swap_size, dump_type, dump_size) = \
- swap_dump.calc_swap_dump_size(ti_utils.get_minimum_size(swap_dump),
- inst_device_size, swap_included=True)
-
- tgt_disk = install_profile.disk.to_tgt()
- tgt.create_disk_target(tgt_disk, False)
- logging.debug("Completed create_disk_target")
- INSTALL_STATUS.update(InstallStatus.TI, 20, mesg)
-
- rootpool_name = install_profile.disk.get_install_root_pool()
- rpool = tgt.Zpool(rootpool_name, inst_device)
- tgt.create_zfs_root_pool(rpool)
- logging.debug("Completed create_zfs_root_pool")
- INSTALL_STATUS.update(InstallStatus.TI, 40, mesg)
-
- create_swap = False
- if (swap_type == ti_utils.SwapDump.ZVOL):
- create_swap = True
-
- create_dump = False
- if (dump_type == ti_utils.SwapDump.ZVOL):
- create_dump = True
-
- logging.debug("Create swap %s Swap size: %s", create_swap, swap_size)
- logging.debug("Create dump %s Dump size: %s", create_dump, dump_size)
-
- tgt.create_zfs_volume(rootpool_name, create_swap, swap_size,
- create_dump, dump_size)
- logging.debug("Completed create swap and dump")
- INSTALL_STATUS.update(InstallStatus.TI, 70, mesg)
-
- zfs_datasets = ()
- # must traverse it in reversed order
- for ds in reversed(ZFS_SHARED_FS):
- zd = tgt.ZFSDataset(mountpoint=ds)
- zfs_datasets += (zd,)
- tgt.create_be_target(rootpool_name, INIT_BE_NAME, INSTALLED_ROOT_DIR,
- zfs_datasets)
-
- logging.debug("Completed create_be_target")
- INSTALL_STATUS.update(InstallStatus.TI, 100, mesg)
- except TypeError, te:
- logging.error("Failed to initialize disk")
- logging.exception(te)
- raise ti_utils.InstallationError
-
-
-def do_transfer():
- '''Call libtransfer to transfer the bits to the system via cpio.'''
- # transfer the bits
- tm_argslist = [(TM_ATTR_MECHANISM, TM_PERFORM_CPIO),
- (TM_CPIO_ACTION, TM_CPIO_ENTIRE),
- (TM_CPIO_SRC_MNTPT, "/"),
- (TM_CPIO_DST_MNTPT, INSTALLED_ROOT_DIR)]
-
- logging.debug("Going to call TM with this list: %s", tm_argslist)
-
- try:
- status = tm_perform_transfer(tm_argslist,
- callback=transfer_mod_callback)
- except Exception, ex:
- logging.exception(ex)
- status = 1
-
- if status != TM_SUCCESS:
- logging.error("Failed to transfer bits to the target")
- raise ti_utils.InstallationError
-
-
-def do_ti_install(install_profile, screen, update_status_func, quit_event,
- time_change_event):
- '''Installation engine for text installer.
-
- Raises InstallationError for any error occurred during install.
-
- '''
- try:
- sysconfig_profile = sysconfig.profile.from_engine()
-
- #
- # The following information is needed for installation.
- # Make sure they are provided before even starting
- #
-
- # locale
- locale = sysconfig_profile.system.locale
- logging.debug("default locale: %s", locale)
-
- # timezone
- timezone = sysconfig_profile.system.tz_timezone
- logging.debug("time zone: %s", timezone)
-
- # hostname
- hostname = sysconfig_profile.system.hostname
- logging.debug("hostname: %s", hostname)
-
- (inst_device, inst_device_size) = \
- install_profile.disk.get_install_dev_name_and_size()
- logging.debug("Installation Device Name: %s", inst_device)
- logging.debug("Installation Device Size: %sMB", inst_device_size)
-
- swap_dump = ti_utils.SwapDump()
-
- min_inst_size = ti_utils.get_minimum_size(swap_dump)
- logging.debug("Minimum required size: %sMB", min_inst_size)
- if (inst_device_size < min_inst_size):
- logging.error("Size of device specified for installation "
- "is too small")
- logging.error("Size of install device: %sMB", inst_device_size)
- logging.error("Minimum required size: %sMB", min_inst_size)
- raise ti_utils.InstallationError
-
- recommended_size = ti_utils.get_recommended_size(swap_dump)
- logging.debug("Recommended size: %sMB", recommended_size)
- if (inst_device_size < recommended_size):
- # Warn users that their install target size is not optimal
- # Just log the warning, but continue with the installation.
- logging.warning("Size of device specified for installation is "
- "not optimal")
- logging.warning("Size of install device: %sMB", inst_device_size)
- logging.warning("Recommended size: %sMB", recommended_size)
-
- # Validate the value specified for timezone
- if not tz_isvalid(timezone):
- logging.error("Timezone value specified (%s) is not valid",
- timezone)
- raise ti_utils.InstallationError
-
- # Compute the time to set here. It will be set after the rtc
- # command is run, if on x86.
- install_time = datetime.datetime.now() + \
- sysconfig_profile.system.time_offset
-
- if platform.processor() == "i386":
- #
- # At this time, the /usr/sbin/rtc command does not work in
- # alternate root. It hard codes to use /etc/rtc_config.
- # Therefore, we set the value for rtc_config in the live
- # environment so it will get copied over to the alternate root.
- #
- exec_cmd([RTC_CMD, "-z", timezone], "set timezone")
- exec_cmd([RTC_CMD, "-c"], "set timezone")
-
- #
- # Set the system time to the time specified by the user
- # The value to set the time to is computed before the "rtc" commands.
- # This is required because rtc will mess up the computation of the
- # time to set. The rtc command must be run before the command
- # to set time. Otherwise, the time that we set will be overwritten
- # after running /usr/sbin/rtc.
- #
- cmd = ["/usr/bin/date", install_time.strftime("%m%d%H%M%y")]
- exec_cmd(cmd, "set system time")
-
- finally:
- # Must signal to the main thread to 'wake' whether
- # the prior lines have succeeded or failed
- time_change_event.set()
-
- global INSTALL_STATUS
- INSTALL_STATUS = InstallStatus(screen, update_status_func, quit_event)
-
- rootpool_name = install_profile.disk.get_install_root_pool()
-
- cleanup_existing_install_target(install_profile, inst_device)
-
- do_ti(install_profile, swap_dump)
-
- do_transfer()
-
- ict_mesg = "Completing transfer process"
- INSTALL_STATUS.update(InstallStatus.ICT, 0, ict_mesg)
-
- # Save the timezone in the installed root's /etc/default/init file
- ti_utils.save_timezone_in_init(INSTALLED_ROOT_DIR, timezone)
-
- # If swap was created, add appropriate entry to <target>/etc/vfstab
- swap_device = swap_dump.get_swap_device(rootpool_name)
- logging.debug("Swap device: %s", swap_device)
- ti_utils.setup_etc_vfstab_for_swap(swap_device, INSTALLED_ROOT_DIR)
-
- # Generate the SC Profile by running the InstallEngine
- # This is here temporarily, until the main thread
- # takes control of running the entire install through the
- # InstallEngine.
- eng = InstallEngine.get_instance()
- eng.execute_checkpoints(start_from=sysconfig.GENERATE_SC_PROFILE_CHKPOINT)
-
- try:
- run_ICTs(install_profile, hostname, ict_mesg, inst_device,
- locale, rootpool_name)
- finally:
- post_install_cleanup(install_profile, rootpool_name)
-
- INSTALL_STATUS.update(InstallStatus.ICT, 100, ict_mesg)
-
-
-def post_install_cleanup(install_profile, rootpool_name):
- '''Do final cleanup to prep system for first boot, such as resetting
- the ZFS dataset mountpoints
-
- '''
- # reset_zfs_mount_property
- # Setup mountpoint property back to "/" from "/a" for
- # /, /export, /export/home
-
- # make sure we are not in the alternate root.
- # Otherwise, be_unmount() fails
- os.chdir("/root")
-
- # since be_unmount() can not currently handle shared filesystems,
- # it's necesary to manually set their mountpoint to the appropriate value
-
- # Limitations of current implementation:
- #
- # Following assumptions have to be met with respect to structure of
- # shared filesystems:
- #
- # - list of shared filesystems is ordered hierarchically AND
- # - it contains only one stream of hierarchy.
- #
- # List of shared datasets is currently hardcoded and meets those
- # assumptions. The implementation has to be revisited once creating more
- # complex structures of ZFS datasets is supported.
-
- # Unmount the oldest ancestor dataset.
- # It also unmounts all children datasets.
- exec_cmd(["/usr/sbin/zfs", "unmount", rootpool_name + ZFS_SHARED_FS[-1]],
- "unmount " + rootpool_name + ZFS_SHARED_FS[-1])
-
- # Reset mountpoint just for ancestor dataset. Child datasets inherit
- # mounpoint from their ancestors.
-
- exec_cmd(["/usr/sbin/zfs", "set", "mountpoint=" + ZFS_SHARED_FS[-1],
- rootpool_name + ZFS_SHARED_FS[-1]],
- "change mount point for " +
- rootpool_name + ZFS_SHARED_FS[-1])
-
- # Transfer the log file
- final_log_loc = INSTALLED_ROOT_DIR + install_profile.log_final
- logging.debug("Copying %s to %s", install_profile.log_location,
- final_log_loc)
- try:
- shutil.copyfile(install_profile.log_location, final_log_loc)
- except (IOError, OSError), err:
- logging.error("Failed to copy %s to %s", install_profile.log_location,
- install_profile.log_final)
- logging.exception(err)
- raise ti_utils.InstallationError
-
- # 0 for the 2nd argument because force-umount need to be 0
- if beUnmount(INIT_BE_NAME, 0) != 0:
- logging.error("beUnmount failed for %s", INIT_BE_NAME)
- raise ti_utils.InstallationError
-
-
-# pylint: disable-msg=C0103
-def run_ICTs(install_profile, hostname, ict_mesg, inst_device, locale,
- rootpool_name):
- '''Run all necessary ICTs. This function ensures that each ICT is run,
- regardless of the success/failure of any others. After running all ICTs
- (including those supplied by install-finish), if any of them failed,
- an InstallationError is raised.
-
- '''
-
- failed_icts = 0
-
- #
- # set the language locale
- #
- if (locale != ""):
- try:
- exec_cmd([ICT_PROG, "ict_set_lang_locale", INSTALLED_ROOT_DIR,
- locale, CPIO_TRANSFER],
- "execute ict_set_lang_locale() ICT")
- except ti_utils.InstallationError:
- failed_icts += 1
-
- #
- # associate system hostname with loopback address in hosts(4) file.
- # This is just a temporary solution until CR6996436 is fixed.
- # Once it happens, it will be taken care by svc:/network/install smf(5)
- # service and this ICT task can be removed.
- #
- try:
- exec_cmd([ICT_PROG, "ict_set_hosts", INSTALLED_ROOT_DIR, hostname],
- "execute ict_set_hosts() ICT")
- except ti_utils.InstallationError:
- failed_icts += 1
-
- # Setup bootfs property so that newly created Solaris instance is booted
- # appropriately
- initial_be = rootpool_name + "/ROOT/" + INIT_BE_NAME
- try:
- exec_cmd(["/usr/sbin/zpool", "set", "bootfs=" + initial_be,
- rootpool_name], "activate BE")
- except ti_utils.InstallationError:
- failed_icts += 1
-
- is_logical = "0"
- part_info = install_profile.disk.get_solaris_data()
- if isinstance(part_info, PartitionInfo) and part_info.is_logical():
- is_logical = "1"
-
- try:
- exec_cmd([ICT_PROG, "ict_installboot", INSTALLED_ROOT_DIR, inst_device,
- is_logical], "execute ict_installboot() ICT")
- except ti_utils.InstallationError:
- failed_icts += 1
-
- INSTALL_STATUS.update(InstallStatus.ICT, 50, ict_mesg)
-
- # Run the install-finish script
- cmd = [INSTALL_FINISH_PROG, "-B", INSTALLED_ROOT_DIR, "-I", "TEXT"]
-
- try:
- exec_cmd(cmd, "execute INSTALL_FINISH_PROG")
- except ti_utils.InstallationError:
- failed_icts += 1
-
- # Take a snapshot of the installation
- try:
- exec_cmd([ICT_PROG, "ict_snapshot", INIT_BE_NAME, INSTALL_SNAPSHOT],
- "execute ict_snapshot() ICT")
- except ti_utils.InstallationError:
- failed_icts += 1
-
- # Mark ZFS root pool "ready" - it was successfully populated and contains
- # valid Solaris instance
- try:
- exec_cmd([ICT_PROG, "ict_mark_root_pool_ready", rootpool_name],
- "execute ict_mark_root_pool_ready() ICT")
- except ti_utils.InstallationError:
- failed_icts += 1
-
- if failed_icts != 0:
- logging.error("One or more ICTs failed. See previous log messages")
- raise ti_utils.InstallationError
- else:
- logging.info("All ICTs completed successfully")
-
-
-def perform_ti_install(install_profile, screen, update_status_func, quit_event,
- time_change_event):
- '''Wrapper to call the do_ti_install() function.
- Sets the variable indicating whether the installation is successful or
- not.
-
- '''
-
- try:
- do_ti_install(install_profile, screen, update_status_func, quit_event,
- time_change_event)
- install_profile.install_succeeded = True
- except ti_utils.InstallationError:
- install_profile.install_succeeded = False
--- a/usr/src/cmd/text-install/osol_install/text_install/ti_install_utils.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,428 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Utility functions
-'''
-
-import logging
-import os
-import shutil
-from tempfile import NamedTemporaryFile
-from subprocess import Popen
-from subprocess import PIPE
-from osol_install.profile.disk_space import DiskSpace
-import osol_install.tgt as tgt
-
-class InstallationError(Exception):
- '''Some sort of error occurred during installation. The exact
- cause of the error should have been logged. So, this
- just indicates that something is wrong
-
- '''
- pass
-
-class NotEnoughSpaceError(Exception):
- '''There's not enough space in the target disk for successful installation
-
- '''
- pass
-
-# All sizes are in MB
-MIN_SWAP_SIZE = 512
-MAX_SWAP_SIZE = DiskSpace("32gb").size_as("mb") #32G
-MIN_DUMP_SIZE = 256
-MAX_DUMP_SIZE = DiskSpace("16gb").size_as("mb") #16G
-OVERHEAD = 1024
-FUTURE_UPGRADE_SPACE = DiskSpace("2gb").size_as("mb") #2G
-ZVOL_REQ_MEM = 900 # Swap ZVOL is required if memory is below this
-
-class SwapDump:
- ''' All information associated with swap and dump'''
-
- ''' The type of swap/dump. Define them as strings so debugging output
- is easier to read.
- '''
- SLICE = "Slice"
- ZVOL = "ZVOL"
- NONE = "None"
-
- mem_size = 0
- swap_type = ""
- swap_size = 0
-
- dump_type = ""
- dump_size = 0
-
- swap_dump_computed = False
-
- def __init__(self):
- ''' Initialize swap/dump calculation. This will get the size of
- running's system's memory
- '''
- self.mem_size = get_system_memory()
- self.swap_type = SwapDump.NONE
- self.swap_size = 0
- self.dump_type = SwapDump.NONE
- self.dump_size = 0
- self.swap_dump_computed = False
-
- def get_required_swap_size(self):
- ''' Determines whether swap is required. If so, the amount of
- space used for swap is returned. If swap is not required,
- 0 will be returned. Value returned is in MB.
-
- If system memory is less than 900mb, swap is required.
- Minimum required space for swap is 0.5G (MIN_SWAP_SIZE).
- '''
-
- if (self.mem_size < ZVOL_REQ_MEM):
- return MIN_SWAP_SIZE
-
- return 0
-
-
- def calc_swap_dump_size(self, installation_size, available_size,
- swap_included=False):
- '''Calculate swap/dump, based on the amount of
- system memory, installation size and available size.
-
- The following rules are used for
- determining the type of swap to be created, whether swap zvol
- is required and the size of swap to be created.
-
- memory type required size
- --------------------------------------------------
- <900mb zvol yes 0.5G (MIN_SWAP_SIZE)
- 900mb-1G zvol no 0.5G (MIN_SWAP_SIZE)
- 1G-64G zvol no (0.5G-32G) 1/2 of memory
- >64G zvol no 32G (MAX_SWAP_SIZE)
-
- The following rules are used for calculating the amount
- of required space for dump
-
- memory type size
- --------------------------------------------------
- <0.5G zvol 256MB (MIN_DUMP_SIZE)
- 0.5G-32G zvol 256M-16G (1/2 of memory)
- >32G zvol 16G (MAX_DUMP_SIZE)
-
- If slice/zvol is required, and there's not enough space in the,
- target, an error will be raised. If swap zvol is
- not required, and there's not enough space in the target, as much
- space as available will be utilized for swap/dump
-
- Size of all calculation is done in MB
-
- Input:
- installation_size: size required for installation (MB)
- available_size: available size in the target disk. (MB)
- swap_included: Indicate whether required swap space is already
- included and validated in the installation size.
- Default to false.
-
- Output:
- returns a tuple (swap_type, swap_size, dump_type, dump_size)
-
- Raise:
- NotEnoughSpaceError
- '''
- if self.swap_dump_computed:
- return(self.swap_type, self.swap_size, self.dump_type,
- self.dump_size)
-
- swap_required = False
-
- if (installation_size > available_size):
- logging.error("Space required for installation: %s",
- installation_size)
- logging.error("Total available space: %s", available_size)
- raise NotEnoughSpaceError
-
- self.swap_size = self.get_required_swap_size()
- if self.swap_size != 0:
- swap_required = True
-
- logging.debug("Installation size: %sMB", installation_size)
- logging.debug("Available size: %sMB", available_size)
- logging.debug("Memory: %sMB. Swap Required: %s",
- self.mem_size, swap_required)
-
- if swap_required:
- # Make sure target disk has enough space for both swap and software
- if swap_included:
- with_swap_size = installation_size
- else:
- with_swap_size = installation_size + self.swap_size
- if (available_size < with_swap_size):
- logging.error("Space required for installation "
- "with required swap: %s", with_swap_size)
- logging.error("Total available space: %s", available_size)
- raise NotEnoughSpaceError
-
- # calculate the size for dump
- self.dump_size = self.__calc_size(available_size - with_swap_size,
- MIN_DUMP_SIZE, MAX_DUMP_SIZE)
- else:
- free_space = available_size - installation_size
- self.swap_size = self.__calc_size(((free_space * MIN_SWAP_SIZE) /
- (MIN_SWAP_SIZE + MIN_DUMP_SIZE)),
- MIN_SWAP_SIZE, MAX_SWAP_SIZE)
- self.dump_size = self.__calc_size(((free_space * MIN_DUMP_SIZE) /
- (MIN_SWAP_SIZE + MIN_DUMP_SIZE)),
- MIN_DUMP_SIZE, MAX_DUMP_SIZE)
- if (self.dump_size > 0):
- self.dump_type = SwapDump.ZVOL
-
- if (self.swap_size > 0):
- self.swap_type = SwapDump.ZVOL
-
- logging.debug("Swap Type: %s", self.swap_type)
- logging.debug("Swap Size: %s", self.swap_size)
- logging.debug("Dump Type: %s", self.dump_type)
- logging.debug("Dump Size: %s", self.dump_size)
- self.swap_dump_computed = True
-
- return(self.swap_type, self.swap_size, self.dump_type, self.dump_size)
-
- def __calc_size(self, available_space, min_size, max_size):
- '''Calculates size of swap or dump in MB based on amount of
- physical memory available.
-
- If less than calculated space is available, swap/dump size will be
- trimmed down to the avaiable space. If calculated space
- is more than the max size to be used, the swap/dump size will
- be trimmed down to the maximum size to be used for swap/dump
-
- Args:
- available_swap_space: space that can be dedicated to swap in MB
- min_size: minimum size to use
- max_size: maximum size to use
-
- Returns:
- size of swap in MB
-
- '''
-
- if available_space == 0:
- return (0)
-
- if (self.mem_size < min_size):
- size = min_size
- else:
- size = self.mem_size / 2
- if (size > max_size):
- size = max_size
-
- if (available_space < size):
- size = available_space
-
- return (int)(size) # Make sure size is an int
-
- def get_swap_device(self, pool_name):
- ''' Return the string representing the device used for swap '''
- if (self.swap_type == SwapDump.ZVOL):
- return ("/dev/zvol/dsk/" + pool_name + "/swap")
-
- return None
-
-IMAGE_INFO = "/.cdrom/.image_info"
-IMAGE_SIZE_KEYWORD = "IMAGE_SIZE"
-
-def get_image_size():
- '''Total size of the software in the image is stored in the
- /.cdrom/.image_info indicated by the keywoard IMAGE_SIZE.
- This function retrieves that value from the .image_file
- The size recorded in the .image_file is in KB, other functions
- in this file uses the value in MB, so, this function will
- return the size in MB
-
- Returns:
- size of retrieved from the .image_info file in MB
-
- '''
- img_size = 0
- try:
- with open(IMAGE_INFO, 'r') as ih:
- for line in ih:
- (opt, val) = line.split("=")
- if opt == IMAGE_SIZE_KEYWORD:
- # Remove the '\n' character read from
- # the file, and convert to integer
- img_size = int(val.rstrip('\n'))
- break
- except IOError, ioe:
- logging.error("Failed to access %s", IMAGE_INFO)
- logging.exception(ioe)
- raise InstallationError
- except ValueError, ive:
- logging.error("Invalid file format in %s", IMAGE_INFO)
- logging.exception(ive)
- raise InstallationError
-
- if (img_size == 0):
- # We should have read in a size by now
- logging.error("Unable to read the image size from %s", IMAGE_INFO)
- raise InstallationError
-
- return (DiskSpace(str(img_size) +"kb").size_as("mb"))
-
-
-def get_system_memory():
- ''' Returns the amount of memory available in the system
- The value returned is in MB.
-
- '''
- memory_size = 0
- try:
- with os.popen("/usr/sbin/prtconf") as fp:
- for line in fp.readlines():
- # Looking for the line that says "Memory size: xxxxx Megabytes"
- val = line.split()
- if ((len(val) == 4) and ((val[0] + " " + val[1]) == \
- "Memory size:")):
- memory_size = int(val[2]) # convert the size to an integer
- break
- except Exceptions:
- pass
-
- if (memory_size <= 0):
- # We should have a valid size now
- logging.error("Unable to determine amount of system memory")
- raise InstallationError
-
- return memory_size
-
-def get_minimum_size(swap_dump_info):
- ''' Returns the minimum amount of space required to perform an installation
- This does take into account MIN_SWAP_SIZE required for
- low-memory system.
-
- Size is returned in MB.
-
- '''
- swap_size = swap_dump_info.get_required_swap_size()
- return(get_image_size() + OVERHEAD + swap_size)
-
-def get_recommended_size(swap_dump_info):
- '''Returns the recommended size to perform an installation.
- This takes into account estimated space to perform an upgrade.
-
- '''
- return (get_minimum_size(swap_dump_info) + FUTURE_UPGRADE_SPACE)
-
-INIT_FILE = "/etc/default/init"
-TIMEZONE_KW = "TZ"
-def save_timezone_in_init(basedir, timezone):
- '''Save the timezone in /etc/default/init.
-
- '''
- saved_tz = False
- init_file = basedir + INIT_FILE
- try:
- with open(init_file, 'r') as ih:
- with NamedTemporaryFile(dir="/tmp", delete=False) as th:
- tmp_fname = th.name
-
- for line in ih:
- eq = line.split("=")
- if eq[0] == TIMEZONE_KW:
- new_line = TIMEZONE_KW + "=" + timezone + "\n"
- th.write(new_line)
- saved_tz = True
- else:
- th.write(line)
- if not saved_tz:
- new_line = TIMEZONE_KW + "=" + timezone + "\n"
- th.write(new_line)
-
- th.close()
-
- # Set the owner, group and permission bits from original file
- # to temp file. The copystat() call will cause the last
- # access and modification time as well, but it is not
- # important. Capturing the correct file permission is.
- shutil.copymode(init_file, tmp_fname)
- shutil.copystat(init_file, tmp_fname)
- shutil.copy2(tmp_fname, init_file)
- os.remove(tmp_fname)
- except IOError, ioe:
- logging.error("Failed to save timezone into %s", init_file)
- logging.exception(ioe)
- raise InstallationError
-
-VFSTAB_FILE = "/etc/vfstab"
-def setup_etc_vfstab_for_swap(swap_device, basedir):
- '''Add the swap device to /etc/vfstab.
-
- '''
- if swap_device is None:
- return #nothing to do
-
- fname = basedir + VFSTAB_FILE
- try:
- with open (fname, 'a+') as vf:
- vf.write("%s\t%s\t\t%s\t\t%s\t%s\t%s\t%s\n" %
- (swap_device, "-", "-", "swap", "-", "no", "-"))
- except IOError, ioe:
- logging.error("Failed to write to %s", fname)
- logging.exception(ioe)
- raise InstallationError
-
-def pool_list(arg):
- '''Return a list of zpools on the system
-
- '''
- argslist = ['/usr/sbin/zpool', arg]
- pool_names = []
-
- try:
- (zpoolout, zpoolerr) = Popen(argslist, stdout=PIPE,
- stderr=PIPE).communicate()
-
- except OSError, err:
- logging.error("OSError occured during zpool call: %s", err)
- return pool_names
-
- if zpoolerr:
- logging.error("Error occured during zpool call: %s", zpoolerr)
- return pool_names
-
- line = zpoolout.splitlines(False)
- for entry in line:
- if 'pool:' in entry:
- val = entry.split()
- if ((len(val) == 2) and (val[0] == "pool:")):
- pool_names.append(val[1])
-
- return pool_names
-
-def get_zpool_list():
- ''' Get the list of exported (inactive) as well as active
- zpools on the system.
- '''
- zpool_list = pool_list("import")
- zpool_list.extend(pool_list("status"))
-
- return zpool_list
--- a/usr/src/cmd/text-install/osol_install/text_install/welcome.py Wed May 11 14:33:33 2011 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-#!/usr/bin/python
-#
-# CDDL HEADER START
-#
-# The contents of this file are subject to the terms of the
-# Common Development and Distribution License (the "License").
-# You may not use this file except in compliance with the License.
-#
-# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
-# or http://www.opensolaris.org/os/licensing.
-# See the License for the specific language governing permissions
-# and limitations under the License.
-#
-# When distributing Covered Code, include this CDDL HEADER in each
-# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
-# If applicable, add the following below this CDDL HEADER, with the
-# fields enclosed by brackets "[]" replaced with your own identifying
-# information: Portions Copyright [yyyy] [name of copyright owner]
-#
-# CDDL HEADER END
-#
-# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-'''
-Contains the Welcome Screen for the Text Installer
-'''
-
-from osol_install.profile.install_profile import INSTALL_PROF_LABEL
-from osol_install.text_install import _, RELEASE, TUI_HELP
-from solaris_install.engine import InstallEngine
-from terminalui.base_screen import BaseScreen
-
-
-class WelcomeScreen(BaseScreen):
- '''First screen of the text installer.
- No special __init__ needed beyond that provided by BaseScreen
-
- '''
-
- HEADER_TEXT = _("Welcome to %(release)s") % RELEASE
- WELCOME_TEXT = _("Thanks for choosing to install %(release)s! This "
- "installer enables you to install the %(release)s "
- "Operating System (OS) on SPARC or x86 systems.\n\n"
- "The installation log will be at %(log)s.\n\n"
- "How to navigate through this installer:")
- BULLET_ITEMS = [_("Use the function keys listed at the bottom of each "
- "screen to move from screen to screen and to perform "
- "other operations."),
- _("Use the up/down arrow keys to change the selection "
- "or to move between input fields."),
- _("If your keyboard does not have function keys, or "
- "they do not respond, press ESC; the legend at the "
- "bottom of the screen will change to show the ESC keys"
- " for navigation and other functions.")]
- BULLET = "- "
- HELP_DATA = (TUI_HELP + "/%s/welcome.txt",
- _("Welcome and Navigation Instructions"))
-
- def set_actions(self):
- '''Remove the F3_Back Action from the first screen'''
- self.main_win.actions.pop(self.main_win.back_action.key, None)
-
- def _show(self):
- '''Display the static paragraph WELCOME_TEXT'''
- doc = InstallEngine.get_instance().doc
- self.install_profile = doc.get_descendants(name=INSTALL_PROF_LABEL,
- not_found_is_err=True)[0]
-
- log_file = self.install_profile.log_location
- y_loc = 1
- fmt = {"log" : log_file}
- fmt.update(RELEASE)
- text = WelcomeScreen.WELCOME_TEXT % fmt
- y_loc += self.center_win.add_paragraph(text, start_y=y_loc)
- x_loc = len(WelcomeScreen.BULLET)
- for bullet in WelcomeScreen.BULLET_ITEMS:
- self.center_win.add_text(WelcomeScreen.BULLET, start_y=y_loc)
- y_loc += self.center_win.add_paragraph(bullet, start_y=y_loc,
- start_x=x_loc)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/partition_edit_screen.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,292 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+UI Components for displaying a screen allowing the user to edit
+partition and slice information
+'''
+
+import curses
+import logging
+import platform
+
+from solaris_install.engine import InstallEngine
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.target.controller import DEFAULT_VDEV_NAME
+from solaris_install.target.libadm.const import V_ROOT
+from solaris_install.target.physical import Partition, Slice
+from solaris_install.target.size import Size
+from solaris_install.text_install import _, RELEASE, TUI_HELP
+from solaris_install.text_install.disk_window import DiskWindow
+from solaris_install.text_install.ti_target_utils import \
+ get_desired_target_disk, get_solaris_partition, perform_final_validation, \
+ ROOT_POOL
+from terminalui import LOG_LEVEL_INPUT
+from terminalui.action import Action
+from terminalui.base_screen import BaseScreen, SkipException
+from terminalui.window_area import WindowArea
+
+LOGGER = None
+
+
+class PartEditScreen(BaseScreen):
+ '''Allows user editing of partitions on a disk, or slices on a
+ disk/partition
+
+ '''
+
+ PARTITION_PARAGRAPH = _("Oracle Solaris will be installed into the Solaris"
+ " partition. A partition's type can be changed"
+ " using the F5 key.\n\n"
+ "A partition's size can be increased "
+ "up to its Avail space. Avail space can be "
+ "increased by deleting an adjacent partition. "
+ "Delete a partition by changing it to \"Unused\""
+ " using the F5 key.\n\n"
+ "The four primary partition slots are listed on "
+ "the left. If one is an \"Extended\" partition "
+ "its logical partitions are listed on the "
+ "right.") % RELEASE
+ SLICE_PARAGRAPH = _("%(release)s will be installed in the \"%(pool)s\" "
+ "slice. Use the F5 key to change a slice to "
+ "\"%(pool)s.\"\n\n"
+ "A slice's size can be increased up to its Avail "
+ "size. Avail can be increased by deleting an adjacent"
+ " slice. Use the F5 key to delete a slice by changing"
+ " it to \"Unused.\"\n\n"
+ "Slices are listed in disk layout order.")
+
+ HEADER_x86_PART = _("Select Partition: %(size).1fGB %(type)s "
+ "%(bootable)s")
+ HEADER_x86_SLICE = _("Select Slice in Fdisk Partition")
+ HEADER_SPARC_SLICE = _("Select Slice: %(size).1fGB %(type)s"
+ "%(bootable)s")
+ SLICE_DESTROY_TEXT = _("indicates the slice's current content will be "
+ "destroyed")
+ PART_DESTROY_TEXT = _("indicates the partition's current content will "
+ "be destroyed")
+ BOOTABLE = _("Boot")
+
+ SPARC_HELP = (TUI_HELP + "/%s/"
+ "sparc_solaris_slices_select.txt",
+ _("Select Slice"))
+ X86_PART_HELP = (TUI_HELP + "/%s/"
+ "x86_fdisk_partitions_select.txt",
+ _("Select Partition"))
+ X86_SLICE_HELP = (TUI_HELP + "/%s/"
+ "x86_fdisk_slices_select.txt",
+ _("Select Slice"))
+
+ HELP_FORMAT = " %s"
+
+ def __init__(self, main_win, target_controller, x86_slice_mode=False):
+ super(PartEditScreen, self).__init__(main_win)
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ self.x86_slice_mode = x86_slice_mode
+ self.is_x86 = (platform.processor() == "i386")
+ self.header_text = platform.processor()
+
+ if self.x86_slice_mode: # x86, Slice within a partition
+ self.instance = ".slice"
+ self.header_text = PartEditScreen.HEADER_x86_SLICE
+ self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH
+ self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT
+ self.help_data = PartEditScreen.X86_SLICE_HELP
+ elif self.is_x86: # x86, Partition on disk
+ self.header_text = PartEditScreen.HEADER_x86_PART
+ self.paragraph_text = PartEditScreen.PARTITION_PARAGRAPH
+ self.destroy_text = PartEditScreen.PART_DESTROY_TEXT
+ self.help_data = PartEditScreen.X86_PART_HELP
+ else: # SPARC (Slice on disk)
+ self.header_text = PartEditScreen.HEADER_SPARC_SLICE
+ self.paragraph_text = PartEditScreen.SLICE_PARAGRAPH
+ self.destroy_text = PartEditScreen.SLICE_DESTROY_TEXT
+ self.help_data = PartEditScreen.SPARC_HELP
+ self.help_format = " %s"
+
+ self.disk_win = None
+ self.tc = target_controller
+
+ def set_actions(self):
+ '''Edit Screens add 'Reset' and 'Change Type' actions. Since these
+ do not manipulate screen direction, they are captured during
+ processing by adding them to center_win's key_dict.
+
+ '''
+ super(PartEditScreen, self).set_actions()
+ reset_action = Action(curses.KEY_F7, _("Reset"))
+ change_action = Action(curses.KEY_F5, _("Change Type"))
+ self.main_win.actions[reset_action.key] = reset_action
+ self.main_win.actions[change_action.key] = change_action
+ self.center_win.key_dict[curses.KEY_F7] = self.on_key_F7
+
+ # pylint: disable-msg=C0103
+ # F7 is the keyname and appropriate here
+ def on_key_F7(self, dummy):
+ '''F7 -> Reset the DiskWindow'''
+ self.disk_win.reset()
+ return None
+
+ def _show(self):
+ '''Display the explanatory paragraph and create the DiskWindow'''
+
+ doc = InstallEngine.get_instance().doc
+
+ if self.x86_slice_mode:
+
+ LOGGER.debug("in x86 slice mode")
+
+ disk = get_desired_target_disk(doc)
+ if disk.whole_disk:
+ LOGGER.debug("disk.whole_disk=True, skip editting")
+ disk.whole_disk = False
+
+ # perform final target validation
+ perform_final_validation(doc)
+
+ raise SkipException
+
+ part = get_solaris_partition(doc)
+
+ LOGGER.debug(str(part))
+ if part is None:
+ err_msg = "Critical error - no Solaris partition found"
+ LOGGER.error(err_msg)
+ raise ValueError(err_msg)
+ if part.in_zpool is not None:
+ LOGGER.debug("Whole partition selected. Skipping slice edit")
+ LOGGER.debug(str(part))
+
+ #
+ # remove the in_zpool value from partition, delete
+ # any existing slices, and create
+ # the needed underneath slices
+ #
+ # All the logic from here to the part.bootid line
+ # can be removed when GPT partitions is supported.
+ #
+
+ existing_slices = part.get_children(class_type=Slice)
+ if not existing_slices:
+ for ex_slice in existing_slices:
+ part.delete_slice(ex_slice)
+
+ pool_name = part.in_zpool
+ slice0 = part.add_slice(0, part.start_sector, \
+ part.size.sectors, Size.sector_units, in_zpool=pool_name, \
+ in_vdev=DEFAULT_VDEV_NAME)
+ slice0.tag = V_ROOT
+ LOGGER.debug(str(part))
+
+ part.in_zpool = None
+ part.bootid = Partition.ACTIVE
+
+ # perform final target validation
+ perform_final_validation(doc)
+
+ raise SkipException
+ else:
+
+ # get selected disk from desired target
+ disk = get_desired_target_disk(doc)
+
+ LOGGER.debug("disk.whole_disk: %s", disk.whole_disk)
+ LOGGER.debug(str(disk))
+
+ if disk.whole_disk:
+ LOGGER.debug("disk.whole_disk true, skipping editing")
+
+ if not self.is_x86:
+ # Unset this so Target Instantiation works correctly
+ disk.whole_disk = False
+
+ # perform final target validation
+ perform_final_validation(doc)
+
+ raise SkipException
+
+ part = disk
+
+ if self.x86_slice_mode:
+ header = self.header_text
+ else:
+ bootable = ""
+ if self.is_x86 and disk.is_boot_disk():
+ bootable = PartEditScreen.BOOTABLE
+ disk_size = disk.disk_prop.dev_size.get(Size.gb_units)
+ header = self.header_text % {"size": disk_size,
+ "type": disk.disk_prop.dev_type,
+ "bootable": bootable}
+ self.main_win.set_header_text(header)
+
+ y_loc = 1
+ fmt_dict = {'pool': ROOT_POOL}
+ fmt_dict.update(RELEASE)
+ y_loc += self.center_win.add_paragraph(self.paragraph_text % fmt_dict,
+ y_loc)
+
+ y_loc += 1
+ disk_win_area = WindowArea(6, 70, y_loc, 0)
+
+ self.disk_win = DiskWindow(disk_win_area, part,
+ window=self.center_win,
+ editable=True,
+ error_win=self.main_win.error_line,
+ target_controller=self.tc)
+ y_loc += disk_win_area.lines
+
+ y_loc += 1
+ LOGGER.log(LOG_LEVEL_INPUT, "calling addch with params start_y=%s,"
+ "start_x=%s, ch=%c", y_loc, self.center_win.border_size[1],
+ DiskWindow.DESTROYED_MARK)
+ self.center_win.window.addch(y_loc, self.center_win.border_size[1],
+ DiskWindow.DESTROYED_MARK,
+ self.center_win.color_theme.inactive)
+ self.center_win.add_text(self.destroy_text, y_loc, 2)
+
+ self.main_win.do_update()
+ self.center_win.activate_object(self.disk_win)
+
+ def validate(self):
+ ''' Perform final validation of the desired target
+ '''
+
+ if self.is_x86 and not self.x86_slice_mode:
+ # delay final validation for x86 until slice mode
+ # is completed.
+ return
+
+ # perform final target validation
+ doc = InstallEngine.get_instance().doc
+
+ if self.is_x86:
+ solaris_part = get_solaris_partition(doc)
+ if solaris_part is None:
+ raise RuntimeError("No Solaris2 partition in desired target")
+ solaris_part.bootid = Partition.ACTIVE
+
+ perform_final_validation(doc)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/progress.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,151 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+import random
+import socket
+import struct
+import sys
+import thread
+import time
+
+from select import select
+
+from solaris_install.logger import ProgressHandler
+
+''' Progress handler for the text installer '''
+
+
+class InstallProgressHandler(ProgressHandler):
+ """ Server and message receiver functionality for the ProgressHandler. """
+
+ def __init__(self, logger, hostname="localhost", portno=None):
+
+ self.server_up = False
+ self.logger = logger
+ self.hostname = hostname
+ self.engine_skt = None
+ self.msg_buf = ""
+
+ # Get a port number
+ self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ if portno is not None:
+ self.portno = portno
+ try:
+ self.skt.bind((self.hostname, self.portno))
+ self.skt.listen(5)
+ except socket.error:
+ self.logger.exception("InstallProgresHandler init failed.")
+ return None
+ else:
+ random.seed()
+ # Continue looping until skt.listen(5) does not cause socket.error
+ while True:
+ try:
+ self.portno = random.randint(10000, 30000)
+ self.skt.bind((self.hostname, self.portno))
+ self.skt.listen(5)
+ break
+ except socket.error:
+ self.skt.close()
+ self.skt = None
+
+ ProgressHandler.__init__(self, self.hostname, self.portno)
+
+ def startProgressServer(self, cb=None):
+ """ Starts the socket server stream to receive progress messages. """
+
+ if not self.server_up:
+ self.server_up = True
+ self.engine_skt, address = self.skt.accept()
+ if cb is not None:
+ thread.start_new_thread(self.progressServer, (cb, ))
+ time.sleep(1)
+
+ def stopProgressServer(self):
+ """ Stop the socket server stream. """
+ if self.server_up:
+ self.server_up = False
+
+ def progressServer(self, cb):
+ """ Actual spawned progressServer process. """
+ try:
+ while self.server_up:
+ ready_to_read = select([self.engine_skt], [], [], 0.25)[0]
+ if len(ready_to_read) > 0:
+ percentage, mssg = self.parseProgressMsg( \
+ ready_to_read[0], cb)
+ self.engine_skt.close()
+ except Exception:
+ self.logger.exception("progressServer Error")
+
+ def get_one_message(self):
+
+ self.logger.debug("1-msg: %s", self.msg_buf)
+ if len(self.msg_buf) > 4:
+ # Something is available to be processed. Try to process it.
+ size = struct.unpack('@i', self.msg_buf[:4])[0]
+ self.logger.debug("size of message expected: %s", size)
+
+ # does the buffer have enough data?
+ if len(self.msg_buf[4:]) >= size:
+ msg = self.msg_buf[4:4 + size]
+ self.msg_buf = self.msg_buf[(4 + size):]
+ self.logger.debug("message: %s", msg)
+ self.logger.debug("msg_buf: %s", self.msg_buf)
+ return msg
+ else:
+ return None
+ else:
+ return None
+
+ def parseProgressMsg(self, skt, cb=None):
+ """Parse the messages sent by the client."""
+ recv_size = 8192
+ percent = None
+ msg = None
+
+ message = self.get_one_message()
+ self.logger.debug("step 1: message: %s", message)
+ while not message:
+ try:
+ sock_data = skt.recv(recv_size)
+ except:
+ pass
+
+ self.logger.debug("sock: %s", sock_data)
+ self.msg_buf += sock_data
+ self.logger.debug("after sock: %s", self.msg_buf)
+ message = self.get_one_message()
+ self.logger.debug("step 2: message: %s", message)
+
+ self.logger.debug("parseProgressMsg: %s", message)
+ if cb:
+ cb(message)
+
+ percent, msg = message.split(' ', 1)
+ return percent, msg
+
+ def progressReceiver(self, msg):
+ print "%s" % (msg)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/summary.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,234 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Display a summary of the user's selections
+'''
+
+import curses
+import logging
+
+import solaris_install.sysconfig.profile
+
+from solaris_install.engine import InstallEngine
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.sysconfig.profile.network_info import NetworkInfo
+from solaris_install.sysconfig.profile.user_info import UserInfo
+from solaris_install.target.libdiskmgt import const as libdiskmgt_const
+from solaris_install.target.size import Size
+from solaris_install.text_install import _, RELEASE, TUI_HELP
+from solaris_install.text_install.ti_target_utils import \
+ get_desired_target_disk, get_solaris_partition, get_solaris_slice
+from terminalui.action import Action
+from terminalui.base_screen import BaseScreen
+from terminalui.i18n import convert_paragraph
+from terminalui.window_area import WindowArea
+from terminalui.scroll_window import ScrollWindow
+
+LOGGER = None
+
+
+class SummaryScreen(BaseScreen):
+ '''Display a summary of the install profile to the user
+ InnerWindow.__init__ is sufficient to initalize an instance
+ of SummaryScreen
+
+ '''
+
+ HEADER_TEXT = _("Installation Summary")
+ PARAGRAPH = _("Review the settings below before installing."
+ " Go back (F3) to make changes.")
+
+ HELP_DATA = (TUI_HELP + "/%s/summary.txt",
+ _("Installation Summary"))
+
+ INDENT = 2
+
+ def set_actions(self):
+ '''Replace the default F2_Continue with F2_Install'''
+ install_action = Action(curses.KEY_F2, _("Install"),
+ self.main_win.screen_list.get_next)
+ self.main_win.actions[install_action.key] = install_action
+
+ def _show(self):
+ '''Prepare a text summary and display it to the user in a ScrollWindow
+
+ '''
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ self.sysconfig = solaris_install.sysconfig.profile.from_engine()
+
+ y_loc = 1
+ y_loc += self.center_win.add_paragraph(SummaryScreen.PARAGRAPH, y_loc)
+
+ y_loc += 1
+ summary_text = self.build_summary()
+
+ LOGGER.info("The following configuration is used for "
+ "installation: %s\n", summary_text)
+ # Wrap the summary text, accounting for the INDENT (used below in
+ # the call to add_paragraph)
+ max_chars = self.win_size_x - SummaryScreen.INDENT - 1
+ summary_text = convert_paragraph(summary_text, max_chars)
+ area = WindowArea(x_loc=0, y_loc=y_loc,
+ scrollable_lines=(len(summary_text) + 1))
+ area.lines = self.win_size_y - y_loc
+ area.columns = self.win_size_x
+ scroll_region = ScrollWindow(area, window=self.center_win)
+ scroll_region.add_paragraph(summary_text, start_x=SummaryScreen.INDENT)
+
+ self.center_win.activate_object(scroll_region)
+
+ def build_summary(self):
+ '''Build a textual summary from the DOC data'''
+ lines = []
+
+ lines.append(_("Software: %s") % self.get_release())
+ lines.append("")
+ lines.append(self.get_disk_summary())
+ lines.append("")
+ lines.append(self.get_tz_summary())
+ lines.append("")
+ lines.append(_("Language: *The following can be changed when "
+ "logging in."))
+ if self.sysconfig.system.locale is None:
+ self.sysconfig.system.determine_locale()
+ lines.append(_(" Default language: %s") %
+ self.sysconfig.system.actual_lang)
+ lines.append("")
+ lines.append(_("Keyboard layout: *The following can be "
+ "changed when logging in."))
+ lines.append(_(" Default keyboard layout: %s") %
+ self.sysconfig.system.keyboard)
+ lines.append("")
+ lines.append(_("Terminal type: %s") %
+ self.sysconfig.system.terminal_type)
+ lines.append("")
+ lines.append(_("Users:"))
+ lines.extend(self.get_users())
+ lines.append("")
+ lines.append(_("Network:"))
+ lines.extend(self.get_networks())
+
+ return "\n".join(lines)
+
+ def get_networks(self):
+ '''Build a summary of the networks from the DOC data,
+ returned as a list of strings
+
+ '''
+ network_summary = []
+ network_summary.append(_(" Computer name: %s") %
+ self.sysconfig.system.hostname)
+ nic = self.sysconfig.nic
+
+ if nic.type == NetworkInfo.AUTOMATIC:
+ network_summary.append(_(" Network Configuration: Automatic"))
+ elif nic.type == NetworkInfo.NONE:
+ network_summary.append(_(" Network Configuration: None"))
+ else:
+ network_summary.append(_(" Manual Configuration: %s")
+ % nic.nic_name)
+ network_summary.append(_(" IP Address: %s") % nic.ip_address)
+ network_summary.append(_(" Netmask: %s") % nic.netmask)
+ if nic.gateway:
+ network_summary.append(_(" Router: %s") % nic.gateway)
+ if nic.dns_address:
+ network_summary.append(_(" DNS: %s") % nic.dns_address)
+ if nic.domain:
+ network_summary.append(_(" Domain: %s") % nic.domain)
+ return network_summary
+
+ def get_users(self):
+ '''Build a summary of the user information, and return it as a list
+ of strings
+
+ '''
+ root = self.sysconfig.users[UserInfo.ROOT_IDX]
+ primary = self.sysconfig.users[UserInfo.PRIMARY_IDX]
+ user_summary = []
+ if not root.password:
+ user_summary.append(_(" Warning: No root password set"))
+ if primary.login_name:
+ user_summary.append(_(" Username: %s") % primary.login_name)
+ else:
+ user_summary.append(_(" No user account"))
+ return user_summary
+
+ def get_disk_summary(self):
+ '''Return a string summary of the disk selection'''
+
+ doc = InstallEngine.get_instance().doc
+ disk = get_desired_target_disk(doc)
+
+ format_dict = dict()
+ disk_string = [_("Disk: %(disk-size).1fGB %(disk-type)s")]
+ format_dict['disk-size'] = disk.disk_prop.dev_size.get(Size.gb_units)
+ format_dict['disk-type'] = disk.disk_prop.dev_type
+
+ if not disk.whole_disk:
+
+ part_data = get_solaris_partition(doc)
+
+ if part_data is not None:
+ disk_string.append(\
+ _("Partition: %(part-size).1fGB %(part-type)s"))
+ format_dict['part-size'] = part_data.size.get(Size.gb_units)
+ part_type = libdiskmgt_const.PARTITION_ID_MAP[\
+ part_data.part_type]
+ format_dict['part-type'] = part_type
+
+ if part_data is None or not part_data.in_zpool:
+ slice_data = get_solaris_slice(doc)
+ disk_string.append(_("Slice %(slice-num)s: %(slice-size).1fGB"
+ " %(pool)s"))
+ format_dict['slice-num'] = slice_data.name
+ format_dict['slice-size'] = slice_data.size.get(Size.gb_units)
+ format_dict['pool'] = slice_data.in_zpool
+
+ return "\n".join(disk_string) % format_dict
+
+ def get_tz_summary(self):
+ '''Return a string summary of the timezone selection'''
+ timezone = self.sysconfig.system.tz_timezone
+ return _("Time Zone: %s") % timezone
+
+ @staticmethod
+ def get_release():
+ '''Read in the release information from /etc/release'''
+ try:
+ try:
+ release_file = open("/etc/release")
+ except IOError:
+ LOGGER.warn("Could not read /etc/release")
+ release_file = None
+ release = RELEASE['release']
+ else:
+ release = release_file.readline()
+ finally:
+ if release_file is not None:
+ release_file.close()
+ return release.strip()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/test/test_disk_select.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,116 @@
+#!/usr/bin/python2.6
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+To run these tests:
+
+1) nightly -n developer.sh # build the gate
+2) export PYTHONPATH=${WS}/proto/root_i386/usr/snadm/lib:\
+${WS}/proto/root_i386/usr/lib/python2.6/vendor-packages
+3) pfexec python2.6 test_disk_select.py
+
+A single test may be run by specifying the test as an argument to
+step 3, e.g.:
+
+pfexec python2.6 test_disk_select.py OnActivateTest.test_on_activate_default
+
+Since the proto area is used for the PYTHONPATH, the gate must be rebuilt for
+these tests to pick up any changes in the tested code.
+
+'''
+
+import numbers
+import unittest
+
+import solaris_install.text_install.disk_selection as disk_selection
+import terminalui
+from terminalui.base_screen import BaseScreen
+from solaris_install.engine.test.engine_test_utils import \
+ get_new_engine_instance
+from solaris_install.target.size import Size
+
+terminalui.init_logging("test")
+BaseScreen.set_default_quit_text("test", "test", "test", "test")
+
+
+class MockCenterWin(object):
+ '''Mocks an InnerWindow as used by a MainWindow'''
+
+ def add_paragraph(self, *args, **kwargs):
+ pass
+
+
+class MockAll(object):
+ '''Generic Mock object that 'never' raises an AttributeError'''
+
+ def __getattr__(self, name):
+ return self
+
+ def __call__(self, *args, **kwargs):
+ return None
+
+
+class MockTC(object):
+ ''' Mocks the target controller '''
+
+ minimum_target_size = Size("3gb")
+ recommended_target_size = Size("6gb")
+
+
+class DiskSelectTest(unittest.TestCase):
+ '''Test the DiskScreen'''
+
+ def setUp(self):
+ self.engine = get_new_engine_instance()
+ self.screen = disk_selection.DiskScreen(MockAll(), MockTC())
+ self.screen.disk_win = MockAll()
+
+ def test_size_line(self):
+ '''Ensure that DiskScreen._size_line is created and is a string after
+ calling get_size_line. Also verify that subsequent calls do not modify
+ the _size_line
+
+ '''
+ self.assertTrue(self.screen._size_line is None)
+ self.screen.get_size_line()
+ self.assertTrue(isinstance(self.screen._size_line, basestring))
+
+ obj = object()
+ self.screen._size_line = obj
+ self.screen.get_size_line()
+ self.assertTrue(obj is self.screen._size_line)
+
+ def test_determine_size_data(self):
+ '''Ensure that recommended_size and minimum_size are accessible after
+ a call to determine_size_data(), and that they are numbers'''
+
+ self.assertTrue(self.screen._recommended_size is None)
+ self.assertTrue(self.screen._minimum_size is None)
+ self.screen.determine_size_data()
+ self.assertTrue(isinstance(self.screen.minimum_size, Size))
+ self.assertTrue(isinstance(self.screen.recommended_size, Size))
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/test/test_disk_window.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,250 @@
+#!/usr/bin/python2.6
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+To run these tests:
+
+1) nightly -n developer.sh # build the gate
+2) export PYTHONPATH=${WS}/proto/root_i386/usr/snadm/lib:\
+${WS}/proto/root_i386/usr/lib/python2.6/vendor-packages
+3) python2.6 test_disk_window.py
+
+A single test may be run by specifying the test as an argument to step 3:
+python2.6 test_disk_window.py UpdateEditField.test_part_info_not_editable
+
+Since the proto area is used for the PYTHONPATH, the gate must be rebuilt for
+these tests to pick up any changes in the tested code.
+
+'''
+
+import unittest
+
+from solaris_install.target.physical import Disk
+from solaris_install.text_install.disk_window import DiskWindow
+import terminalui
+from terminalui.color_theme import ColorTheme
+from terminalui.window_area import WindowArea
+from terminalui.inner_window import InnerWindow
+
+
+terminalui.init_logging("test")
+
+
+class MockAll(object):
+ '''Generic Mock object that 'never' raises an AttributeError'''
+
+ def __getattr__(self, name):
+ return self
+
+ def __call__(self, *args, **kwargs):
+ return self
+
+
+class MockInnerWin(object):
+ '''Class for mock inner win'''
+ def __init__(self):
+ self.active_object = None
+ self.objects = []
+ self.text = None
+ self.scrollable_lines = 10
+
+ def activate_object(self):
+ '''Set active_object to 0'''
+ self.active_object = 0
+
+ def get_active_object(self):
+ '''Get active_object'''
+ if self.active_object is not None:
+ return self.objects[self.active_object]
+ else:
+ return None
+
+ def add_text(self, text=None, y_loc=0, x_loc=0):
+ '''Append passed in text to self.text'''
+ if self.text is None:
+ self.text = str(y_loc) + text
+ else:
+ self.text = self.text + str(y_loc) + text
+
+ def get_text(self):
+ '''Get the current value of self.text'''
+ return self.text
+
+
+class MockPartInfo(object):
+ '''Class for mock part info field'''
+ def __init__(self):
+ self.is_editable = True
+
+ def set_editable(self, editable=True):
+ '''Set editable property'''
+ self.is_editable = editable
+
+ def editable(self):
+ ''' get editable setting'''
+ return self.is_editable
+
+
+class MockEditField(object):
+ '''Class for mock edit field'''
+ def __init__(self):
+ self.active = False
+
+ def make_inactive(self, inactive=True):
+ ''' Set active property'''
+ self.active = not inactive
+
+
+class MockPartField(object):
+ '''Class for mock part field'''
+ def __init__(self):
+ self.edit_field = None
+ self.active_object = None
+ self.objects = []
+
+ def activate_object(self, tobject):
+ '''activate object'''
+ self.active_object = 0
+ self.edit_field = tobject
+ self.edit_field.make_inactive(False)
+
+ def get_active_object(self):
+ '''Get active_object'''
+ if self.active_object is not None:
+ return self.objects[self.active_object]
+ else:
+ return None
+
+
+def do_nothing(*args, **kwargs):
+ '''does nothing'''
+ pass
+
+
+class TestDiskWindow(unittest.TestCase):
+ '''Class to test DiskWindow'''
+
+ def setUp(self):
+ '''unit test set up
+ Sets several functions to call do_nothing to allow
+ test execution in non-curses environment. Original
+ functions are saved so they can be later restored in
+ tearDown.
+
+ '''
+ self.inner_window_init_win = InnerWindow._init_win
+ self.disk_window_init_win = DiskWindow._init_win
+ self.inner_window_set_color = InnerWindow.set_color
+ InnerWindow._init_win = do_nothing
+ InnerWindow.set_color = do_nothing
+ DiskWindow._init_win = do_nothing
+ self.disk_win = DiskWindow(WindowArea(70, 70, 0, 0), Disk("MockDisk"),
+ color_theme=ColorTheme(force_bw=True),
+ window=MockAll())
+ self.edit_field = MockEditField()
+ self.part_field = MockPartField()
+ self.part_info = MockPartInfo()
+ self.inner_win = MockInnerWin()
+ self.disk_win.objects = []
+ self.part_field.objects = []
+
+ def tearDown(self):
+ '''unit test tear down
+ Functions originally saved in setUp are restored to their
+ original values.
+ '''
+ InnerWindow._init_win = self.inner_window_init_win
+ InnerWindow.set_color = self.inner_window_set_color
+ DiskWindow._init_win = self.disk_window_init_win
+ self.disk_win = None
+ self.edit_field = None
+ self.part_field = None
+ self.part_info = None
+ self.inner_win = None
+
+
+class UpdateEditField(TestDiskWindow):
+ '''Tests for _update_edit_field'''
+
+ def test_part_info_not_editable(self):
+ '''Ensure edit field updated correctly if part_info not editable'''
+ self.part_info.set_editable(False)
+ self.edit_field.make_inactive(False)
+ self.part_field.activate_object(self.edit_field)
+ self.part_field.objects.append(self.edit_field)
+
+ self.disk_win._update_edit_field(self.part_info, self.part_field,
+ self.edit_field)
+ self.assertFalse(self.edit_field.active)
+ self.assertEquals(len(self.part_field.objects), 0)
+ self.assertTrue(self.part_field.active_object is None)
+
+ def test_part_info_editable_no_active_win(self):
+ '''Ensure edit field not updated if no active right/left win'''
+ self.part_info.set_editable(True)
+ self.edit_field.make_inactive(True)
+ self.assertTrue(self.part_field.active_object is None)
+ self.disk_win._update_edit_field(self.part_info, self.part_field,
+ self.edit_field)
+ self.assertTrue(self.part_field.active_object is None)
+ self.assertFalse(self.edit_field.active)
+
+ def test_part_info_editable_active_win_no_active_obj(self):
+ '''Ensure edit field updated correctly if active obj not part_field'''
+ self.part_info.set_editable(True)
+ self.edit_field.make_inactive(True)
+ self.disk_win.objects.append(self.inner_win)
+ self.disk_win.active_object = 0
+ my_part_field = MockPartField()
+ self.inner_win.objects.append(my_part_field)
+ self.inner_win.active_object = 0
+
+ self.assertTrue(self.inner_win.get_active_object() is my_part_field)
+ self.assertTrue(self.part_field.active_object is None)
+
+ self.disk_win._update_edit_field(self.part_info, self.part_field,
+ self.edit_field)
+ self.assertTrue(self.part_field.active_object is None)
+ self.assertFalse(self.edit_field.active)
+
+ def test_part_info_editable_active_win(self):
+ '''Ensure edit field updated correctly if part_info is editable'''
+ self.part_info.set_editable(True)
+ self.edit_field.make_inactive(True)
+ self.disk_win.objects.append(self.inner_win)
+ self.disk_win.active_object = 0
+ self.inner_win.objects.append(self.part_field)
+ self.inner_win.active_object = 0
+
+ self.assertEquals(self.disk_win.active_object, 0)
+ self.assertTrue(self.part_field.active_object is None)
+
+ self.disk_win._update_edit_field(self.part_info, self.part_field,
+ self.edit_field)
+ self.assertEquals(self.part_field.active_object, 0)
+ self.assertTrue(self.edit_field.active)
+
+
+if __name__ == '__main__':
+ unittest.main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/test/test_text_install.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,86 @@
+#!/usr/bin/python2.6
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+To run these tests, see the instructions in usr/src/tools/tests/README.
+Remember that since the proto area is used for the PYTHONPATH, the gate
+must be rebuilt for these tests to pick up any changes in the tested code.
+
+'''
+
+
+import unittest
+
+import solaris_install.text_install as text_install
+
+
+class TestTextInstall(unittest.TestCase):
+
+ def test_reboot_cmds(self):
+ '''_reboot_cmds(x86) returns expected list of potential reboot commands
+ * /usr/sbin/reboot -f -- rpool/ROOT/<BE>
+ * /usr/sbin/reboot
+
+ '''
+ cmds = text_install._reboot_cmds(True)
+
+ # First command should be: /usr/sbin/reboot -f -- rpool/ROOT/<BE>
+ self.assertEqual(cmds[0][0], text_install.REBOOT)
+ self.assertEqual(cmds[0][1], "-f")
+ self.assertEqual(cmds[0][2], "--")
+ # Last item will be something like: rpool/ROOT/active-on-reboot-BE
+ # Just ensure that there's something there
+ self.assertTrue(cmds[0][3])
+ self.assertEqual(len(cmds[0]), 4)
+
+ # Second command should be just: /usr/sbin/reboot
+ self.assertEqual(cmds[1][0], text_install.REBOOT)
+ self.assertEqual(len(cmds[1]), 1)
+
+ self.assertEqual(len(cmds), 2)
+
+ def test_reboot_cmds_sparc(self):
+ '''_reboot_cmds(sparc) returns expected list of reboot commands
+ (SPARC requires the additional "-Z")
+ * /usr/sbin/reboot -f -- -Z rpool/ROOT/<BE>
+ * /usr/sbin/reboot
+
+ '''
+ cmds = text_install._reboot_cmds(False)
+
+ # First command should be: /usr/sbin/reboot -f -- -Z rpool/ROOT/<BE>
+ self.assertEqual(cmds[0][0], text_install.REBOOT)
+ self.assertEqual(cmds[0][1], "-f")
+ self.assertEqual(cmds[0][2], "--")
+ self.assertEqual(cmds[0][3], "-Z")
+ # Last item will be something like: rpool/ROOT/active-on-reboot-BE
+ # Just ensure that there's something there
+ self.assertTrue(cmds[0][4])
+ self.assertEqual(len(cmds[0]), 5)
+
+ # Second command should be just: /usr/sbin/reboot
+ self.assertEqual(cmds[1][0], text_install.REBOOT)
+ self.assertEqual(len(cmds[1]), 1)
+
+ self.assertEqual(len(cmds), 2)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/text-install Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,27 @@
+#!/usr/bin/python2.6
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+import solaris_install.text_install as text_install
+
+text_install.main()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/ti_install.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,375 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Installation engine for Text Installer
+'''
+
+import curses
+import os
+import logging
+import datetime
+import platform
+import shutil
+
+from select import select
+
+import osol_install.errsvc as errsvc
+import osol_install.liberrsvc as liberrsvc
+import solaris_install.sysconfig as sysconfig
+
+from libbe_py import beUnmount
+from osol_install.libzoneinfo import tz_isvalid
+from solaris_install import Popen
+from solaris_install.engine import InstallEngine
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.sysconfig.profile import ConfigProfile
+from solaris_install.target.controller import TargetController
+from solaris_install.target.size import Size
+from solaris_install.target.libbe.be import be_unmount
+from solaris_install.text_install import TRANSFER_PREP, \
+ TARGET_INIT, CLEANUP_CPIO_INSTALL
+from solaris_install.text_install import ti_install_utils as ti_utils
+from solaris_install.text_install.progress import InstallProgressHandler
+from solaris_install.text_install.ti_target_utils import \
+ get_desired_target_zpool, get_desired_target_be, get_solaris_slice, \
+ ROOT_POOL
+from solaris_install.transfer.info import Software, IPSSpec
+
+LOGGER = None
+
+#
+# RTC command to run
+#
+RTC_CMD = "/usr/sbin/rtc"
+
+#
+# Program used for calling the C ICT functions.
+# When all the C-based ICT functions are converted to Python (bug 6256),
+# this can be removed.
+#
+ICT_PROG = "/opt/install-test/bin/ict_test"
+
+#
+# Handle for accessing the InstallStatus class
+#
+INSTALL_STATUS = None
+
+
+def exec_callback(status, failed_cp):
+
+ LOGGER.debug("exec_callback parameters:")
+ LOGGER.debug("status: %s", status)
+ LOGGER.debug("failed_cp: %s", failed_cp)
+
+ # check the result of execution
+ INSTALL_STATUS.exec_status = status
+
+ INSTALL_STATUS.stop_report_status()
+
+ if status == InstallEngine.EXEC_FAILED:
+ for fcp in failed_cp:
+ err_data = (errsvc.get_errors_by_mod_id(fcp))[0]
+ LOGGER.error("Checkpoint %s failed" % fcp)
+ err = err_data.error_data[liberrsvc.ES_DATA_EXCEPTION]
+ LOGGER.error(err)
+
+
+class InstallStatus(object):
+ '''Stores information on the installation progress, and provides a
+ hook for updating the screen.
+
+ '''
+
+ def __init__(self, screen, update_status_func):
+ '''screen and update_status_func are values passed in from
+ the main app
+
+ '''
+
+ self.screen = screen
+ self.update_status_func = update_status_func
+ self.prog_handler = InstallProgressHandler(LOGGER)
+ self.prog_handler.startProgressServer()
+ self.exec_status = InstallEngine.EXEC_SUCCESS
+ LOGGER.addHandler(self.prog_handler)
+
+ def report_status(self):
+ '''Update the install status. Also checks the quit_event to see
+ if the installation should be aborted.
+
+ '''
+ try:
+ processing_quit = False
+ while self.prog_handler.server_up:
+ if not processing_quit:
+ ready_to_read = select([self.prog_handler.engine_skt],
+ [], [], 0.25)[0]
+ if len(ready_to_read) > 0:
+ percent, msg = self.prog_handler.parseProgressMsg(\
+ ready_to_read[0])
+ LOGGER.debug("message = %s", msg)
+ LOGGER.debug("percent = %s", percent)
+ self.update_status_func(float(percent), msg)
+
+ # check whether F9 is pressed
+ input_key = self.screen.main_win.getch()
+ if input_key == curses.KEY_F9:
+ LOGGER.info("User selected Quit")
+ really_quit = self.screen.confirm_quit()
+ if really_quit:
+ LOGGER.info("User confirmed Quit")
+ engine = InstallEngine.get_instance()
+ engine.cancel_checkpoints()
+ processing_quit = True
+ except Exception, e:
+ LOGGER.exception("progressServer Error")
+
+ def stop_report_status(self):
+ self.prog_handler.stopProgressServer()
+
+
+def do_ti_install(install_data, screen, update_status_func):
+ '''Installation engine for text installer.
+ Raises InstallationError for any error occurred during install.
+
+ '''
+
+ sysconfig_profile = sysconfig.profile.from_engine()
+
+ #
+ # The following information is needed for installation.
+ # Make sure they are provided before even starting
+ #
+
+ # timezone
+ timezone = sysconfig_profile.system.tz_timezone
+ LOGGER.debug("time zone: %s", timezone)
+
+ # Validate the value specified for timezone
+ if not tz_isvalid(timezone):
+ LOGGER.error("Timezone value specified (%s) is not valid", timezone)
+ raise ti_utils.InstallationError
+
+ # Compute the time to set here. It will be set after the rtc
+ # command is run, if on x86.
+ install_time = datetime.datetime.now() + \
+ sysconfig_profile.system.time_offset
+
+ if platform.processor() == "i386":
+ #
+ # At this time, the /usr/sbin/rtc command does not work in alternate
+ # root. It hard codes to use /etc/rtc_config.
+ # Therefore, we set the value for rtc_config in the live environment
+ # so it will get copied over to the alternate root.
+ #
+ cmd = [RTC_CMD, "-z", timezone]
+ Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=LOGGER)
+
+ cmd = [RTC_CMD, "-c"]
+ Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=LOGGER)
+
+ #
+ # Set the system time to the time specified by the user
+ # The value to set the time to is computed before the "rtc" commands.
+ # This is required because rtc will mess up the computation of the
+ # time to set. The rtc command must be run before the command
+ # to set time. Otherwise, the time that we set will be overwritten
+ # after running /usr/sbin/rtc.
+ #
+ cmd = ["/usr/bin/date", install_time.strftime("%m%d%H%M%y")]
+ Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=LOGGER)
+
+ hostname = sysconfig_profile.system.hostname
+ LOGGER.debug("hostname: " + hostname)
+
+ engine = InstallEngine.get_instance()
+ doc = engine.doc
+ solaris_slice = get_solaris_slice(doc)
+ if solaris_slice is None:
+ raise ti_utils.InstallationError("Unable to find solaris slice")
+ inst_device_size = solaris_slice.size
+
+ LOGGER.info("Installation Device Size: %sMB", inst_device_size)
+
+ minimum_size = screen.tc.minimum_target_size
+ LOGGER.info("Minimum required size: %s", minimum_size)
+ if (inst_device_size < minimum_size):
+ LOGGER.error("Size of device specified for installation "
+ "is too small")
+ LOGGER.error("Size of install device: %s", inst_device_size)
+ LOGGER.error("Minimum required size: %s", minimum_size)
+ raise ti_utils.InstallationError
+
+ recommended_size = screen.tc.recommended_target_size
+ LOGGER.info("Recommended size: %s", recommended_size)
+ if (inst_device_size < recommended_size):
+ # Warn users that their install target size is not optimal
+ # Just log the warning, but continue with the installation.
+ LOGGER.warning("Size of device specified for installation is "
+ "not optimal")
+ LOGGER.warning("Size of install device: %s", inst_device_size)
+ LOGGER.warning("Recommended size: %s", recommended_size)
+
+ (swap_type, swap_size, dump_type, dump_size) = \
+ screen.tc.calc_swap_dump_size(minimum_size, inst_device_size,
+ swap_included=True)
+
+ desired_zpool = get_desired_target_zpool(doc)
+ if (swap_type == TargetController.SWAP_DUMP_ZVOL):
+ swap_zvol = desired_zpool.add_zvol("swap", \
+ swap_size.get(Size.mb_units), Size.mb_units, use="swap")
+
+ if (dump_type == TargetController.SWAP_DUMP_ZVOL):
+ dump_zvol = desired_zpool.add_zvol("dump", \
+ dump_size.get(Size.mb_units), Size.mb_units, use="dump")
+
+ LOGGER.info("Swap type: %s", swap_type)
+ LOGGER.info("Swap size: %s", swap_size)
+ LOGGER.info("Dump type: %s", dump_type)
+ LOGGER.info("Dump size: %s", dump_size)
+
+ # Specify for the shared datasets <root_pool>/export and
+ # <root_pool>/export/home be created. We will specify
+ # a mountpoint for <root_pool>/export dataset.
+ # We must not specify a mountpoint for <root_pool>/export/home.
+ # It should inherit the mountpoint from <root_pool>/export.
+
+ desired_zpool.add_filesystem("export", mountpoint="/export")
+ desired_zpool.add_filesystem("export/home")
+
+ # Add the list of packages to be removed after the install to the DOC
+ pkg_remove_list = ['pkg:/system/install/media/internal',
+ 'pkg:/system/install/text-install']
+ pkg_spec = IPSSpec(action=IPSSpec.UNINSTALL, contents=pkg_remove_list)
+ pkg_rm_node = Software(CLEANUP_CPIO_INSTALL, type="IPS")
+ pkg_rm_node.insert_children(pkg_spec)
+ doc.volatile.insert_children(pkg_rm_node)
+
+ # execute the prepare transfer checkpoint. This checkpoint must
+ # be executed by itself, before executing any of the transfer
+ # related checkpoints. The transfer checkpoints requires
+ # data setup from the prepare transfer checkpoint.
+
+ (status, failed_cp) = engine.execute_checkpoints(\
+ start_from=TRANSFER_PREP, pause_before=TARGET_INIT)
+
+ if status != InstallEngine.EXEC_SUCCESS:
+ err_data = (errsvc.get_errors_by_mod_id(TRANSFER_PREP))[0]
+ LOGGER.error("%s checkpoint failed" % TRANSFER_PREP)
+ err = err_data.error_data[liberrsvc.ES_DATA_EXCEPTION]
+ LOGGER.error(err)
+ raise ti_utils.InstallationError("Failed to execute checkpoint "
+ "%s", TRANSFER_PREP)
+
+ global INSTALL_STATUS
+ INSTALL_STATUS = InstallStatus(screen, update_status_func)
+
+ LOGGER.debug("Executing rest of checkpoints")
+
+ engine.execute_checkpoints(callback=exec_callback, \
+ dry_run=install_data.no_install_mode)
+
+ INSTALL_STATUS.report_status()
+
+ if INSTALL_STATUS.exec_status is InstallEngine.EXEC_CANCELED:
+ raise ti_utils.InstallationCanceledError("User selected cancel.")
+
+ if INSTALL_STATUS.exec_status is InstallEngine.EXEC_FAILED:
+ raise ti_utils.InstallationError("Failed executing checkpoints")
+
+ if install_data.no_install_mode:
+ # all subsequent code depends on the install target being
+ # setup, so, can not execute them.
+ return
+
+ new_be = get_desired_target_be(doc)
+ install_mountpoint = new_be.mountpoint
+
+ #
+ # associate system hostname with loopback address in hosts(4) file.
+ # This is just a temporary solution until CR6996436 is fixed.
+ # Once it happens, it will be taken care by svc:/network/install smf(5)
+ # service and this ICT task can be removed.
+ #
+ cmd = [ICT_PROG, "ict_set_hosts", install_mountpoint, hostname]
+ Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=LOGGER)
+
+ # If swap was created, add appropriate entry to <target>/etc/vfstab
+ LOGGER.debug("install mountpoint: %s", install_mountpoint)
+ LOGGER.debug("new_be: %s", new_be)
+ screen.tc.setup_vfstab_for_swap(ROOT_POOL, install_mountpoint)
+
+ post_install_cleanup(install_data)
+
+
+def post_install_cleanup(install_data):
+ '''Do final cleanup to prep system for first boot '''
+
+ # make sure we are not in the alternate root.
+ # Otherwise, be_unmount() fails
+ os.chdir("/root")
+
+ doc = InstallEngine.get_instance().doc
+ new_be = get_desired_target_be(doc)
+
+ # Transfer the log file
+ final_log_loc = new_be.mountpoint + install_data.log_final
+ LOGGER.debug("Copying %s to %s" % (install_data.log_location,
+ final_log_loc))
+ try:
+ shutil.copyfile(install_data.log_location, final_log_loc)
+ except (IOError, OSError), err:
+ LOGGER.error("Failed to copy %s to %s" % (install_data.log_location,
+ install_data.log_final))
+ LOGGER.exception(err)
+ raise ti_utils.InstallationError
+
+ be_unmount(new_be.name)
+
+
+def perform_ti_install(install_data, screen, update_status_func):
+ '''Wrapper to call the do_ti_install() function.
+ Sets the variable indicating whether the installation is successful or
+ not.
+
+ '''
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ try:
+ do_ti_install(install_data, screen, update_status_func)
+ install_data.install_succeeded = True
+ LOGGER.info("Install completed successfully")
+ except ti_utils.InstallationCanceledError:
+ install_data.install_succeeded = False
+ LOGGER.info("Install canceled by user.")
+ except Exception, ex:
+ LOGGER.exception("Install FAILED.")
+ install_data.install_succeeded = False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/ti_install_utils.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Utility functions
+'''
+
+
+class InstallationError(Exception):
+ '''Some sort of error occurred during installation. The exact
+ cause of the error should have been logged. So, this
+ just indicates that something is wrong
+
+ '''
+ pass
+
+
+class InstallationCanceledError(Exception):
+ '''User selected to cancel the installation.
+
+ '''
+ pass
+
+
+class InstallData(object):
+ '''
+ This object is used for storing information that need to be
+ shared between different components of the Text Installer
+ '''
+
+ def __init__(self):
+ self.install_succeeded = False
+ self.log_location = None
+ self.log_final = None
+ self.no_install_mode = False
+
+ def __str__(self):
+ result = ["Install Data:"]
+ result.append("Install Completed - %s" % str(self.install_succeeded))
+ result.append("Log Location - %s" % str(self.log_location))
+ result.append("Log Final - %s" % str(self.log_final))
+ result.append("No Install Mode - %s" % (self.no_install_mode))
+ return "\n".join(result)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/ti_target_utils.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,1260 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+
+'''
+Functions for mapping UI objects and DOC objects.
+'''
+
+import logging
+import platform
+
+import osol_install.errsvc as errsvc
+import osol_install.liberrsvc as liberrsvc
+from solaris_install.engine import InstallEngine
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.target import Target
+from solaris_install.target.controller import TargetController, \
+ DEFAULT_VDEV_NAME, DEFAULT_ZPOOL_NAME
+from solaris_install.target.libadm.const import FD_NUMPART as MAX_PRIMARY_PARTS
+from solaris_install.target.libadm.const import MAX_EXT_PARTS
+from solaris_install.target.libadm.const import V_BACKUP, V_ROOT
+from solaris_install.target.libdiskmgt import const as libdiskmgt_const
+from solaris_install.target.logical import Zpool, BE
+from solaris_install.target.physical import Disk, Partition, Slice, \
+ BACKUP_SLICE, BOOT_SLICE
+from solaris_install.target.shadow.physical import ShadowPhysical
+from solaris_install.target.size import Size
+
+
+ROOT_POOL = DEFAULT_ZPOOL_NAME
+
+PART_TYPE_SOLARIS = 191 #191 is type "solaris"
+BACKUP_SLICE_TEXT = "backup"
+UNUSED_TEXT = "Unused"
+EXTENDED_TEXT = "Extended"
+
+UI_PRECISION = "0.05gb"
+
+UI_TYPE_IN_USE = "UI Object In-use" # partition/slice in use
+UI_TYPE_EMPTY_SPACE = "UI Object EMPTY" # unused components
+UI_TYPE_NOT_USED = "UI Object not-in-use"
+
+MAX_VTOC = "2tb"
+
+# Can not use the V_NUMPAR value defined in solaris_install.target.libadm.const
+# because it returns 16 slices for x86. Define the number of slices
+# here so it is 8 for both x86 and sparc.
+MAX_SLICES = 8
+
+LOGGER = None
+
+
+def get_desired_target_disk(doc):
+ ''' Returns the first disk in the desired target subtree of the DOC. '''
+
+ desired_root = doc.persistent.get_descendants(name=Target.DESIRED, \
+ class_type=Target, max_depth=2, not_found_is_err=True)[0]
+
+ return(desired_root.get_descendants(class_type=Disk,
+ not_found_is_err=True)[0])
+
+
+def get_desired_target_zpool(doc):
+ ''' Returns the first zpool in the desired target subtree of the DOC. '''
+
+ desired_root = doc.persistent.get_descendants(name=Target.DESIRED, \
+ class_type=Target, max_depth=2, not_found_is_err=True)[0]
+
+ return(desired_root.get_descendants(name=ROOT_POOL, class_type=Zpool,
+ not_found_is_err=True)[0])
+
+
+def get_desired_target_be(doc):
+ ''' Returns the first BE in the desired target subtree of the DOC. '''
+ desired_root = doc.persistent.get_descendants(name=Target.DESIRED, \
+ class_type=Target, max_depth=2, not_found_is_err=True)[0]
+
+ return(desired_root.get_descendants(class_type=BE,
+ not_found_is_err=True)[0])
+
+
+def get_solaris_partition(doc):
+ '''Returns the partition with type "solaris", if it exists '''
+
+ desired_disk = get_desired_target_disk(doc)
+
+ all_partitions = desired_disk.get_children(class_type=Partition)
+
+ if not all_partitions:
+ return None
+
+ for part in all_partitions:
+ if part.is_solaris:
+ return part
+
+ return None
+
+
+def get_solaris_slice(doc):
+ '''Finds the slice that have the root pool for installation, if exists '''
+
+ if platform.processor() == "i386":
+ desired_part = get_solaris_partition(doc)
+ else:
+ desired_part = get_desired_target_disk(doc)
+
+ all_slices = desired_part.get_children(class_type=Slice)
+
+ if not all_slices:
+ return None
+
+ for slice in all_slices:
+ if slice.in_zpool == ROOT_POOL:
+ return slice
+
+ return None
+
+
+def find_discovered_disk(disk, doc):
+ ''' Search for the disk that's the same as the given disk
+ found during Target Discovery
+
+ Returns the object in the discovered target tree of the DOC.
+
+ Raise RuntimeError() if disk is not found in discovered target.
+ '''
+ disc_root = doc.persistent.get_descendants(name=Target.DISCOVERED, \
+ class_type=Target, max_depth=2, not_found_is_err=True)[0]
+ discovered_disks = disc_root.get_children(class_type=Disk)
+
+ if not discovered_disks:
+ raise RuntimeError("Disk not found in discovered target")
+
+ # look through all the disks, and find the one matching this one.
+ # The "devid" string is compared to identify
+ for disc_disk in discovered_disks:
+ if disc_disk.devid == disk.devid:
+ return disc_disk
+
+ # A disk should be found, so, it's an error to be here
+ raise RuntimeError("Disk not found in discovered target")
+
+
+def discovered_obj_was_empty(obj, doc):
+ '''Determine whether the given disk was empty when it was discovered '''
+
+ discovered_obj = None
+ if isinstance(obj, Disk):
+ discovered_disk = find_discovered_disk(obj, doc)
+ parts = discovered_disk.get_children(class_type=Partition)
+ if (parts is not None) and (len(parts) > 0):
+ return False
+ discovered_obj = discovered_disk
+ else:
+ # find the discovered partition
+ discovered_disk = find_discovered_disk(obj.parent, doc)
+
+ parts = discovered_disk.get_children(class_type=Partition)
+ if not parts:
+ raise RuntimeError("Discovered disk should have partitions. "
+ "However, no partition is found.")
+ discovered_obj = get_part_by_num(obj.name, parts)
+ if discovered_obj is None:
+ raise RuntimeError("Discovered disk should have partitions %s. "
+ "However, unable to find the partition.",
+ obj.name)
+
+ slices = discovered_obj.get_children(class_type=Slice)
+ if not slices:
+ return False
+
+ return True
+
+
+def perform_final_validation(doc):
+
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ LOGGER.info("Going to perform final validation of desired target")
+
+ desired_root = doc.persistent.get_descendants(name=Target.DESIRED,
+ class_type=Target,
+ max_depth=2,
+ not_found_is_err=True)[0]
+
+ LOGGER.debug(str(desired_root))
+
+ errsvc.clear_error_list()
+
+ if not desired_root.final_validation():
+ all_errors = errsvc.get_all_errors()
+
+ for err in all_errors:
+
+ if not isinstance(err.error_data[liberrsvc.ES_DATA_EXCEPTION],
+ ShadowPhysical.SliceInUseError):
+ LOGGER.error("Error module ID: %s.. error type: %s" % \
+ (err.get_mod_id(), str(err.get_error_type())))
+ LOGGER.error("Error class: %r",
+ err.error_data[liberrsvc.ES_DATA_EXCEPTION])
+ LOGGER.error("Exception value: %s",
+ err.error_data[liberrsvc.ES_DATA_EXCEPTION].value)
+ raise ValueError("Desired target doesn't pass "
+ "final validation")
+ else:
+ LOGGER.debug("SliceInUseError is OK")
+ LOGGER.debug("Error module ID: %s.. error type: %s" % \
+ (err.get_mod_id(), str(err.get_error_type())))
+ LOGGER.debug("Exception value: %s",
+ err.error_data[liberrsvc.ES_DATA_EXCEPTION].value)
+ else:
+ LOGGER.debug("No error from final validation")
+
+
+def dump_doc(msg):
+
+ if not LOGGER.isEnabledFor(logging.DEBUG):
+ return
+
+ LOGGER.debug(msg)
+ doc = InstallEngine.get_instance().doc
+ disk = get_desired_target_disk(doc)
+ LOGGER.debug(str(disk))
+
+
+def name_sort(a, b):
+ return cmp(a.name, b.name)
+
+
+def size_sort(a, b):
+ return cmp(a.size, b.size)
+
+
+def get_part_by_num(number, existing_parts):
+ ''' Return the partition/slice that have the given index.
+ If none of the existing partition/slice have the given
+ index, None will be returned.
+ '''
+ for ep in existing_parts:
+ if str(ep.name) == str(number):
+ return ep
+ return None
+
+
+def add_missed_parts(numbers, existing_parts, gaps, min_size, all_parts,
+ check_extended=False, adding_logical=False):
+
+ have_extended = False
+
+ LOGGER.debug("Enter add_missed_parts")
+ LOGGER.debug("numbers: %s", numbers)
+ LOGGER.debug("Existing parts: %s", existing_parts)
+ LOGGER.debug("Gaps: %s", gaps)
+ LOGGER.debug("Min size: %s", min_size)
+
+ for num in numbers:
+ part = get_part_by_num(num, existing_parts)
+ if part is not None:
+ all_parts[num].doc_obj = part
+ LOGGER.debug(str(all_parts[num].doc_obj))
+ if check_extended:
+ # check whether this is an extended partition
+ if part.is_extended:
+ have_extended = True
+ elif len(gaps) > 0:
+ # some space is still left
+ if (gaps[-1]).size >= min_size:
+ # see if the largest one is big enough
+ one_gap = gaps.pop()
+ empty_space = EmptySpace(num, one_gap.start_sector,
+ one_gap.size)
+ all_parts[num].empty_space_obj = empty_space
+ else:
+ if adding_logical:
+ # do not create empty space objects for logical partitions
+ return None
+ empty_space = EmptySpace(num, 0, Size("0gb"))
+ all_parts[num].empty_space_obj = empty_space
+ else:
+ if adding_logical:
+ # do not create empty space objects for logical partitions
+ return None
+ # no space is left. Create 0 size empty spaces
+ empty_space = EmptySpace(num, 0, Size("0gb"))
+ all_parts[num].empty_space_obj = empty_space
+
+ return have_extended
+
+
+class EmptySpace(object):
+
+ def __init__(self, name, start_sector, size):
+ self.name = name
+ self.start_sector = start_sector
+ self.size = size
+
+
+class UIDisk(object):
+
+ ''' Object used to keep track of disk related information for UI '''
+
+ def __init__(self, target_controller, parent=None, doc_obj=None):
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ if doc_obj is None:
+ raise ValueError("doc_obj for UIDisk is needed")
+ self.doc_obj = doc_obj
+ self.tc = target_controller
+ self.all_parts = list()
+ self.have_logical = False
+
+ if platform.processor() == "i386":
+ max_all_parts = MAX_PRIMARY_PARTS + MAX_EXT_PARTS + 1
+ for i in range(max_all_parts):
+ part = UIPartition(self.tc, parent=self)
+ self.all_parts.append(part)
+ else:
+ max_all_parts = MAX_SLICES
+ for i in range(max_all_parts):
+ part = UISlice(self.tc, parent=self)
+ self.all_parts.append(part)
+
+ def get_parts_in_use(self):
+ in_use_parts = []
+ for part in self.all_parts:
+ if (part.ui_type == UI_TYPE_IN_USE) or \
+ (part.ui_type == UI_TYPE_EMPTY_SPACE):
+ in_use_parts.append(part)
+
+ return in_use_parts
+
+ def add_unused_parts(self, no_part_ok=False):
+ '''On x86: Sort through the logical and non-logical partitions and
+ find the largest gaps. For non-logical partitions, create additional
+ Unused partitions such that the number of
+ non-logical partitions == MAX_STANDARD_PARTITIONS. For logical
+ partitions, create additional Unused partitions such that the number of
+ logical partitions == MAX_LOGICAL_PARTITIONS
+
+ On SPARC: Create Unused slices in the largest gaps of empty space,
+ so that there are exactly 8 slices
+
+ For non-logical partitions, gaps smaller than 1 GB are ignored.
+ For logical partitions, gaps smaller than 0.1 GB are ignored.
+
+ '''
+
+ # reset everything to be unused
+ for part in self.all_parts:
+ part.ui_type = UI_TYPE_NOT_USED
+
+ existing_parts = self.doc_obj.get_children(class_type=Partition)
+
+ if not existing_parts:
+ # should have some slices
+ existing_parts = self.doc_obj.get_children(class_type=Slice)
+ if not existing_parts:
+ if no_part_ok:
+ if platform.processor() == "i386":
+ numbers = range(1, MAX_PRIMARY_PARTS + 1)
+ use_partitions = True
+ else:
+ numbers = range(MAX_SLICES)
+ use_partitions = False
+ else:
+ raise ValueError("Cannot determine if this disk has " \
+ "partitions or slices")
+ existing_parts = []
+ else:
+ use_partitions = False
+ numbers = range(MAX_SLICES)
+
+ else:
+ # have partitions
+ use_partitions = True
+ numbers = range(1, MAX_PRIMARY_PARTS + 1)
+
+ LOGGER.debug("num_existing parts = %d", len(existing_parts))
+
+ existing_parts.sort(name_sort)
+
+ LOGGER.debug(str(self.doc_obj))
+
+ existing_gaps = self.doc_obj.get_gaps()
+
+ # sort the gaps by size, smallest gaps first. When using
+ # the existing_gaps list, the largest gaps is used first, they
+ # will be popped off the end of the list.
+ existing_gaps.sort(size_sort)
+
+ self.have_logical = add_missed_parts(numbers, existing_parts, \
+ existing_gaps, Size("1" + Size.gb_units), self.all_parts, \
+ check_extended=use_partitions)
+
+ if use_partitions == False:
+ # If there's a backup slice, move it to end of the list for display
+ slice2 = self.all_parts[2]
+ if slice2.ui_type == UI_TYPE_IN_USE:
+ self.all_parts.remove(slice2)
+ self.all_parts.append(slice2)
+
+ if LOGGER.isEnabledFor(logging.DEBUG):
+ for part in self.all_parts:
+ try:
+ LOGGER.debug("After fill unused...%s: %s..size=%s",
+ str(part.name), part.get_description(),
+ str(part.size))
+ except Exception, ex:
+ LOGGER.debug("after fill unused...%s", ex)
+ pass
+
+ if not self.have_logical:
+ return
+
+ first_logical = MAX_PRIMARY_PARTS + 1
+ numbers = range(first_logical, first_logical + MAX_EXT_PARTS)
+
+ logical_part_gaps = self.doc_obj.get_logical_partition_gaps()
+
+ # sort the gaps by size, smallest gaps first. When using
+ # the existing_gaps list, the largest gaps is used first, they
+ # will be popped off the end of the list.
+ logical_part_gaps.sort(size_sort)
+
+ # Fill in all the missing logical if necessary
+ add_missed_parts(numbers, existing_parts, logical_part_gaps,
+ Size("0.1" + Size.gb_units), self.all_parts,
+ adding_logical=True)
+
+ @property
+ def name(self):
+ return self.doc_obj.name
+
+ @property
+ def size(self):
+ return self.doc_obj.disk_prop.dev_size
+
+ def get_extended_partition(self):
+ if not self.have_logical:
+ return None
+
+ for part in self.all_parts:
+ if part.ui_type == UI_TYPE_IN_USE and part.doc_obj.is_extended:
+ return part
+ return None
+
+ def get_logicals(self):
+ '''Retrieve all the logicals on this disk'''
+ logicals = []
+ for part in self.all_parts:
+ if ((part.ui_type == UI_TYPE_IN_USE) or \
+ (part.ui_type == UI_TYPE_EMPTY_SPACE)) and \
+ (part.is_logical()):
+ logicals.append(part)
+ return logicals
+
+ def get_standards(self):
+ standards = []
+ for part in self.all_parts:
+ if ((part.ui_type == UI_TYPE_IN_USE) or \
+ (part.ui_type == UI_TYPE_EMPTY_SPACE)) and \
+ (not part.is_logical()):
+ standards.append(part)
+ return standards
+
+ def get_solaris_data(self):
+ if platform.processor() == "i386":
+ for part in self.all_parts:
+ if part.ui_type == UI_TYPE_IN_USE and part.doc_obj.is_solaris:
+ return part
+ else:
+ for part in self.all_parts:
+ if (part.ui_type == UI_TYPE_IN_USE) and \
+ (part.doc_obj.in_zpool == ROOT_POOL):
+ return part
+
+ return None
+
+ @property
+ def discovered_doc_obj(self):
+ ''' Search for the same disk found during Target Discovery
+
+ Returns the object in the discovered target tree of the DOC.
+
+ Raise RuntimeError() if disk is not found in discovered target.
+ '''
+ doc = InstallEngine.get_instance().doc
+ return(find_discovered_disk(self.doc_obj, doc))
+
+
+class UIPartition(object):
+ ''' Object used to keep track of partition related information for UI '''
+
+ SOLARIS = 0xBF
+ EXT_DOS = 0x05
+ FDISK_EXTLBA = 0x0f
+ UNUSED = None
+
+ EDITABLE_PART_TYPES = [SOLARIS,
+ EXT_DOS,
+ FDISK_EXTLBA]
+
+ KNOWN_LOGIC_TYPES = [UNUSED, SOLARIS]
+ KNOWN_PART_TYPES = [UNUSED, SOLARIS, EXT_DOS]
+
+ def __init__(self, target_controller, parent=None, doc_obj=None):
+
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ self._doc_obj = None
+ self._empty_space_obj = None
+ self.tc = target_controller
+ self.all_parts = list()
+ self._unused_parts_added = False
+ self.parent = parent
+ self.ui_type = UI_TYPE_NOT_USED
+ self.cycle_types = None
+ self.prev_cycle_idx = 0
+
+ max_all_parts = MAX_SLICES
+
+ for i in range(max_all_parts):
+ part = UISlice(self.tc, parent=self)
+ self.all_parts.append(part)
+
+ if doc_obj != None:
+ self.doc_obj = doc_obj
+
+ @property
+ def doc_obj(self):
+ return self._doc_obj
+
+ @doc_obj.setter
+ def doc_obj(self, obj):
+ if not isinstance(obj, Partition):
+ raise TypeError("Object must be type Partition")
+
+ self._doc_obj = obj
+ self.ui_type = UI_TYPE_IN_USE
+ if self.is_logical():
+ self.cycle_types = UIPartition.KNOWN_LOGIC_TYPES
+ else:
+ self.cycle_types = UIPartition.KNOWN_PART_TYPES
+
+ @property
+ def empty_space_obj(self):
+ return self._empty_space_obj
+
+ @empty_space_obj.setter
+ def empty_space_obj(self, obj):
+ if not isinstance(obj, EmptySpace):
+ raise TypeError("Object must be type EmptySpace")
+
+ self._empty_space_obj = obj
+ self.ui_type = UI_TYPE_EMPTY_SPACE
+ self.cycle_types = UIPartition.KNOWN_PART_TYPES
+
+ def set_unused(self):
+ self.ui_type = UI_TYPE_NOT_USED
+ self._empty_space_obj = None
+ self._doc_obj = None
+
+ @property
+ def name(self):
+ if self.ui_type == UI_TYPE_IN_USE:
+ return self.doc_obj.name
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ return self.empty_space_obj.name
+ else:
+ raise RuntimeError("Partition not in use. No name")
+
+ @property
+ def size(self):
+ if self.ui_type == UI_TYPE_IN_USE:
+ return self.doc_obj.size
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ return Size("0gb")
+ else:
+ raise RuntimeError("Partition not in use. No size information.")
+
+ @property
+ def start_sector(self):
+ if self.ui_type == UI_TYPE_IN_USE:
+ return self.doc_obj.start_sector
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ return self.empty_space_obj.start_sector
+ else:
+ raise RuntimeError("Partition not in use. No size information.")
+
+ def get_end_sector(self):
+
+ if self.ui_type == UI_TYPE_IN_USE:
+ obj = self.doc_obj
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ obj = self.empty_space_obj
+ else:
+ raise RuntimeErorr("Partition not in used")
+
+ size_in_sector = (obj.size).get(Size.sector_units)
+ return (obj.start_sector + size_in_sector)
+
+ def get_parts_in_use(self):
+ in_use_parts = list()
+ for part in self.all_parts:
+ LOGGER.debug("looking at ui_type: %s", part.ui_type)
+ if (part.ui_type == UI_TYPE_IN_USE) or \
+ (part.ui_type == UI_TYPE_EMPTY_SPACE):
+ in_use_parts.append(part)
+
+ return in_use_parts
+
+ def add_unused_parts(self, no_part_ok=False):
+
+ existing_parts = self.doc_obj.get_children(class_type=Slice)
+ if (existing_parts is None) or (len(existing_parts) == 0):
+ if no_part_ok:
+ existing_parts = list()
+ else:
+ raise ValueError("Can't determine if this partition "
+ "has slices")
+
+ # remove the boot slice from the list of slices that gets displayed.
+ boot_slice = None
+ for slice in existing_parts:
+ if int(slice.name) == BOOT_SLICE:
+ boot_slice = slice
+ break
+ if boot_slice is not None:
+ existing_parts.remove(boot_slice)
+
+ numbers = range(MAX_SLICES)
+ existing_parts.sort(name_sort)
+
+ existing_gaps = self.doc_obj.get_gaps()
+ # sort the gaps by size, smallest gaps first. When using
+ # the existing_gaps list, the largest gaps is used first, they
+ # will be popped off the end of the list.
+ existing_gaps.sort(size_sort)
+
+ add_missed_parts(numbers, existing_parts, existing_gaps,
+ Size("1gb"), self.all_parts)
+
+ # If there's a backup slice, move it to end of the list for display
+ slice2 = self.all_parts[2]
+ if slice2.ui_type == UI_TYPE_IN_USE:
+ self.all_parts.remove(slice2)
+ self.all_parts.append(slice2)
+
+ if LOGGER.isEnabledFor(logging.DEBUG):
+ for part in self.all_parts:
+ try:
+ LOGGER.debug("UIPart: after fill unused...%s: %s..size=%s",
+ str(part.name), part.get_description(),
+ str(part.size))
+ except Exception, ex:
+ LOGGER.debug("UIPart: after fill unused..." + str(ex))
+ pass
+
+ def get_description(self):
+
+ if self.ui_type == UI_TYPE_IN_USE:
+ if self.doc_obj.is_extended:
+ return EXTENDED_TEXT
+ else:
+ return libdiskmgt_const.PARTITION_ID_MAP[\
+ self.doc_obj.part_type]
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ return UNUSED_TEXT
+ else:
+ raise RuntimeError("Partition not in use. No descrption")
+
+ @property
+ def discovered_doc_obj(self):
+ ''' Find the same object, if it exists, in the discovered
+ object tree.
+
+ Returns the object in discovered subtree, if found.
+ Returns None otherwise.
+ '''
+
+ if self.ui_type != UI_TYPE_IN_USE:
+ return None
+
+ if self.parent is None:
+
+ # The parent of the partition currently being worked on must be
+ # the disk selected at the desired target
+ doc = InstallEngine.get_instance().doc
+ desired_disk = get_desired_target_disk(doc)
+
+ # find the disk in discovered target
+ parent_disc_obj = find_discovered_disk(desired_disk, doc)
+
+ else:
+ parent_disc_obj = self.parent.discovered_doc_obj
+
+ # get all the discovered partitions
+ discovered_parts = parent_disc_obj.get_children(class_type=Partition)
+
+ if not discovered_parts:
+ return None
+
+ # look through all the partitions, and find the one that
+ # matches this one. The partition name string is used
+ # to identify the matching partition
+ for part in discovered_parts:
+ if str(part.name) == str(self.doc_obj.name):
+ return part
+
+ # Partition is not found.
+ LOGGER.debug("can't find any partitions")
+ return None
+
+ def modified(self):
+ ''' compare with the same object when it is discovered.
+ only partition type and partition size is checked
+ since that's the only thing the text installer allows
+ users to change
+
+ Returns True if size or partition type is changed compared to the
+ discovered object.
+
+ Returns False otherwise.
+ '''
+ if self.ui_type is not UI_TYPE_IN_USE:
+ return False
+
+ # object is newly added to the desired target.
+ if self.discovered_doc_obj is None:
+ return True
+
+ if self.doc_obj.part_type != self.discovered_doc_obj.part_type:
+ return True
+
+ precision = Size(UI_PRECISION).get(Size.byte_units)
+ discovered_size_byte = self.discovered_doc_obj.size.get(\
+ Size.byte_units)
+ size_byte = self.doc_obj.size.get(Size.byte_units)
+
+ return (abs(discovered_size_byte - size_byte) > precision)
+
+ def get_max_size(self):
+ '''Analyze nearby partitions and determine the total unused, available
+ space that this partition could consume without affecting other
+ partitions.
+
+ Result is in gigabytes
+
+ '''
+ if self.is_logical():
+ parts = self.parent.get_logicals()
+ ext_part = self.parent.get_extended_partition()
+ else:
+ parts = self.parent.get_standards()
+ if self not in parts:
+ raise ValueError("This partition was not found on the "
+ "supplied disk")
+
+ self_idx = parts.index(self)
+ prev_part = None
+ next_part = None
+
+ for part in reversed(parts[:self_idx]):
+ if part.ui_type == UI_TYPE_IN_USE:
+ prev_part = part
+ break
+ for part in parts[self_idx + 1:]:
+ if part.ui_type == UI_TYPE_IN_USE:
+ next_part = part
+ break
+
+ LOGGER.debug("part:%s, idx: %s" % (self.get_description(), self_idx))
+
+ msg_str = self.get_description() + ":"
+ if prev_part is None:
+ LOGGER.debug("No prev part")
+ if self.is_logical():
+ begin_avail_space = ext_part.doc_obj.start_sector
+ else:
+ begin_avail_space = 0
+ else:
+ try:
+ begin_avail_space = prev_part.get_end_sector()
+ except Exception:
+ LOGGER.error("%s", prev_part)
+ raise
+ LOGGER.debug("begin_available space: %d", begin_avail_space)
+
+ if next_part is None:
+ LOGGER.debug("No next part")
+ if self.is_logical():
+ end_avail_space = ext_part.get_end_sector()
+ else:
+ disk_size = self.parent.doc_obj.disk_prop.dev_size
+ end_avail_space = disk_size.get(Size.sector_units)
+ else:
+ end_avail_space = next_part.start_sector
+
+ LOGGER.debug("end_available space: %d", end_avail_space)
+
+ avail = end_avail_space - begin_avail_space
+
+ LOGGER.debug("avail: %d", avail)
+
+ if avail < 0:
+ avail = 0
+
+ return (Size(str(avail) + Size.sector_units))
+
+ def editable(self):
+ '''Returns True if it is possible to edit this partition's size'''
+
+ if self.ui_type == UI_TYPE_IN_USE:
+ if int(self.doc_obj.name) > MAX_PRIMARY_PARTS:
+ return False
+
+ if self.doc_obj.part_type in UIPartition.EDITABLE_PART_TYPES:
+ return True
+ else:
+ return False
+
+ return True
+
+ def is_extended(self):
+ if self.ui_type == UI_TYPE_IN_USE and self.doc_obj.is_extended:
+ return True
+
+ return False
+
+ def is_logical(self):
+ if self.ui_type == UI_TYPE_IN_USE:
+ return self.doc_obj.is_logical
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ if int(self.empty_space_obj.name) > MAX_PRIMARY_PARTS:
+ return True
+ return False
+ else:
+ raise RuntimeErorr("Partition not in used")
+
+ def cycle_type(self, new_type=None, extra_type=None):
+ '''Cycle this partition's type. Potential types are based on
+ whether or not another Solaris partition exists, whether an extended
+ partition exists, and whether or not this is a logical partition.
+
+ If extra_types is passed in, it should be a list of other potential
+ types. These types will also be considered when cycling.
+
+ '''
+
+ dump_doc("before change type")
+
+ sol2_part = self.parent.get_solaris_data()
+ has_solaris_part = (sol2_part is not None)
+
+ ext_part = self.parent.get_extended_partition()
+ has_extended = (ext_part is not None)
+
+ # If this is the extended partition, and the Solaris2 partition
+ # is a logical partition, allow for cycling from Extended to Solaris2
+ if (has_extended and ext_part is self and
+ has_solaris_part and sol2_part.is_logical()):
+ has_solaris_part = False
+
+ if new_type is not None:
+ type_index = self.cycle_types.index(new_type)
+ type_index = (type_index + 1) % len(self.cycle_types)
+ else:
+ if self.ui_type == UI_TYPE_EMPTY_SPACE:
+ type_index = self.cycle_types.index(UIPartition.UNUSED)
+ type_index = (type_index + 1) % len(self.cycle_types)
+ else:
+ # should have a doc_obj to reference
+ if self.doc_obj.part_type in self.cycle_types:
+ type_index = self.cycle_types.index(self.doc_obj.part_type)
+ type_index = (type_index + 1) % len(self.cycle_types)
+ else:
+ type_index = 0
+
+ new_type = self.cycle_types[type_index]
+
+ if new_type == UIPartition.UNUSED:
+ LOGGER.debug("new type == unused")
+ else:
+ LOGGER.debug("new type == %s",
+ libdiskmgt_const.PARTITION_ID_MAP[new_type])
+
+ if has_solaris_part and new_type == PART_TYPE_SOLARIS:
+ self.cycle_type(new_type=new_type, extra_type=extra_type)
+ elif has_extended and new_type == UIPartition.EXT_DOS:
+ self.cycle_type(new_type=new_type, extra_type=extra_type)
+ else:
+ if self.ui_type == UI_TYPE_EMPTY_SPACE:
+ LOGGER.debug("Partition used to be unsed. Add partition with"
+ "type: %s, start_sec=%s, size=%s" %
+ (libdiskmgt_const.PARTITION_ID_MAP[new_type],
+ self.empty_space_obj.start_sector,
+ self.get_max_size()))
+
+ size_in_sector = self.empty_space_obj.size.get(\
+ Size.sector_units)
+
+ new_part = self.parent.doc_obj.add_partition(self.name, \
+ self.empty_space_obj.start_sector, size_in_sector, \
+ size_units=Size.sector_units, partition_type=new_type)
+ if new_part.is_solaris:
+ new_part.bootid = Partition.ACTIVE
+ else:
+ if new_type == UIPartition.UNUSED:
+ LOGGER.debug("Changing type to unused deleting %s",
+ self.name)
+ self.parent.doc_obj.delete_partition(self.doc_obj)
+ else:
+ LOGGER.debug("Changing type: %s to type %s",
+ self.name,
+ libdiskmgt_const.PARTITION_ID_MAP[new_type])
+ self.doc_obj.change_type(new_type)
+ if self.doc_obj.is_solaris:
+ self.doc_obj.bootid = Partition.ACTIVE
+ dump_doc("after change type")
+
+ def get_solaris_data(self):
+ for part in self.all_parts:
+ if part.ui_type == UI_TYPE_IN_USE and \
+ part.doc_obj.in_zpool == ROOT_POOL:
+ return part
+
+ return None
+
+ def __str__(self):
+ result = ["UI Partiton: %s" % self.name]
+ if self.ui_type == UI_TYPE_IN_USE:
+ result.append("UI type: in use")
+ result.append("Size: %s" % self.size)
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ result.append("UI type: empty space")
+ result.append("Size: %s" % self.size)
+ elif self.ui_type == UI_TYPE_NOT_USED:
+ result.append("UI type: not used")
+ else:
+ result.append("UI type: UNKNOWN")
+ return "\n".join(result)
+
+
+class UISlice(object):
+ ''' Object used to keep track of slice related information for UI '''
+
+ UNUSED = None
+ TYPES = [UNUSED, ROOT_POOL]
+
+ def __init__(self, target_controller, parent=None):
+ global LOGGER
+ LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ self._doc_obj = None
+ self._empty_space_obj = None
+ self.tc = target_controller
+ self.all_parts = list()
+ self._unused_parts_added = False
+ self.parent = parent
+ self.ui_type = UI_TYPE_NOT_USED
+
+ @property
+ def doc_obj(self):
+ return self._doc_obj
+
+ @doc_obj.setter
+ def doc_obj(self, obj):
+ if not isinstance(obj, Slice):
+ raise TypeError("Object must be type Slice")
+
+ self._doc_obj = obj
+ self.ui_type = UI_TYPE_IN_USE
+
+ @property
+ def empty_space_obj(self):
+ return self._empty_space_obj
+
+ @empty_space_obj.setter
+ def empty_space_obj(self, obj):
+ if not isinstance(obj, EmptySpace):
+ raise TypeError("Object must be type EmptySpace")
+
+ self._empty_space_obj = obj
+ self.ui_type = UI_TYPE_EMPTY_SPACE
+
+ def set_unused(self):
+ self.ui_type = UI_TYPE_NOT_USED
+ self._empty_space_obj = None
+ self._doc_obj = None
+
+ @property
+ def name(self):
+ if self.ui_type == UI_TYPE_IN_USE:
+ return self.doc_obj.name
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ return self.empty_space_obj.name
+ else:
+ raise RuntimeError("Partition not in use. No name")
+
+ @property
+ def size(self):
+ if self.ui_type == UI_TYPE_IN_USE:
+ return self.doc_obj.size
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ return Size("0gb")
+ else:
+ raise RuntimeError("Partition not in use. No size information.")
+
+ def get_end_sector(self):
+
+ if self.ui_type == UI_TYPE_IN_USE:
+ obj = self.doc_obj
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ obj = self.empty_space_obj
+ else:
+ raise RuntimeErorr("Partition not in used")
+
+ size_in_sector = (obj.size).get(Size.sector_units)
+ return (obj.start_sector + size_in_sector)
+
+ def get_description(self):
+ if self.ui_type == UI_TYPE_IN_USE:
+ if self.doc_obj.tag == V_BACKUP:
+ return BACKUP_SLICE_TEXT
+
+ if self.doc_obj.in_zpool is not None:
+ return self.doc_obj.in_zpool
+
+ # try to get value from in_use dictionary
+ in_use = self.doc_obj.in_use
+ if in_use is None:
+ return self.doc_obj.name
+
+ if in_use['used_name']:
+ return (in_use['used_name'])[0]
+
+ if in_use['used_by']:
+ return (in_use['used_by'])[0]
+
+ return self.doc_obj.name
+ elif self.ui_type == UI_TYPE_EMPTY_SPACE:
+ return UNUSED_TEXT
+ else:
+ raise RuntimeError("Slice not in use.")
+
+ def get_max_size(self):
+ '''Return the maximum possible size this slice could consume,
+ in gigabytes, based on adjacent unused space
+
+ '''
+
+ if int(self.name) == BACKUP_SLICE:
+ return self.size
+
+ slices = self.parent.all_parts
+ if self not in slices:
+ raise ValueError("This slice not in the parent!")
+
+ self_idx = slices.index(self)
+ prev_slice = None
+ next_slice = None
+
+ # Search for the slice prior to this one with the largest "endblock"
+ # Since existing slices may overlap, this could be any slice prior
+ # to the current one.
+ for slice_info in reversed(slices[:self_idx]):
+ if (slice_info.ui_type != UI_TYPE_EMPTY_SPACE and \
+ int(slice_info.name) != BACKUP_SLICE):
+ if (prev_slice is None or
+ slice_info.get_end_sector() > prev_slice.get_end_sector()):
+ prev_slice = slice_info
+ for slice_info in slices[self_idx + 1:]:
+ if (slice_info.ui_type != UI_TYPE_EMPTY_SPACE and \
+ int(slice_info.name) != BACKUP_SLICE):
+ next_slice = slice_info
+ break
+ if prev_slice is None:
+ start_pt = 0
+ else:
+ start_pt = prev_slice.get_end_sector()
+
+ if next_slice is None:
+ for slice_info in reversed(slices):
+ # Use the backup slice to define the absolute max size
+ # any given slice can be. (This is usually the last slice,
+ # hence the use of a reversed iterator)
+ if int(slice_info.name) == BACKUP_SLICE:
+ end_pt = slice_info.size.get(Size.sector_units)
+ break
+ else:
+ # Default to the parent's size if there happens to be no S2
+ end_pt = self.parent.size.get(Size.sector_units)
+ else:
+ end_pt = next_slice.get_end_sector()
+
+ max_space = end_pt - start_pt
+
+ if max_space < 0:
+ max_space = 0
+
+ return (Size(str(max_space) + Size.sector_units))
+
+ def editable(self):
+ '''Returns True if it is possible to edit this partition's size'''
+ if self.ui_type == UI_TYPE_IN_USE:
+ if self.doc_obj.in_zpool == "rpool":
+ return True
+
+ return False
+
+ @property
+ def discovered_doc_obj(self):
+ ''' Find the same object, if it exists, in the discovered
+ object tree.
+
+ Returns the object in discovered subtree, if found.
+ Returns None otherwise.
+ '''
+ if self.ui_type != UI_TYPE_IN_USE:
+ return None
+
+ parent_disc_obj = self.parent.discovered_doc_obj
+
+ # get all the discovered partitions
+ discovered_slices = parent_disc_obj.get_children(class_type=Slice)
+
+ if (discovered_slices is None) or (len(discovered_slices) == 0):
+ return None
+
+ # look through all the partitions, and find the one matching that
+ # matches this one. The slice id is used
+ # to identify the matching slice
+ for slice in discovered_slices:
+ if str(slice.name) == str(self.doc_obj.name):
+ return slice
+
+ # Slice is not found.
+ return None
+
+ def modified(self):
+ ''' compare with the same object when it is discovered.
+ only slice size is checked
+ since that's the only thing the text installer allows
+ users to change
+
+ Returns True if slice type or size is changed compared to the
+ discovered object.
+
+ Returns False otherwise.
+ '''
+ if self.ui_type is not UI_TYPE_IN_USE:
+ return False
+
+ # object is newly added to the desired target.
+ if self.discovered_doc_obj is None:
+ return True
+
+ precision = Size(UI_PRECISION).get(Size.byte_units)
+ discovered_size_byte = self.discovered_doc_obj.size.get( \
+ Size.byte_units)
+ size_byte = self.doc_obj.size.get(Size.byte_units)
+
+ if abs(discovered_size_byte - size_byte) > precision:
+ return True
+
+ return False
+
+ def cycle_type(self, new_type=None, extra_type=None):
+ '''Cycle this slide's type.
+
+ If extra_types is passed in, it should be a list of other potential
+ types. These types will also be considered when cycling.
+
+ '''
+
+ dump_doc("before slice.cycle_type")
+
+ has_solaris_data = (self.parent.get_solaris_data() is not None)
+
+ types = set()
+ types.update(UISlice.TYPES)
+ if extra_type is not None:
+ types.update(extra_type)
+ types = list(types)
+ types.sort()
+
+ if new_type is not None:
+ type_index = types.index(new_type)
+ type_index = (type_index + 1) % len(types)
+ else:
+ if self.ui_type == UI_TYPE_EMPTY_SPACE:
+ type_index = types.index(UISlice.UNUSED)
+ type_index = (type_index + 1) % len(types)
+ else:
+ if self.doc_obj.in_zpool in types:
+ type_index = types.index(self.doc_obj.in_zpool)
+ type_index = (type_index + 1) % len(types)
+ else:
+ type_index = 0
+
+ new_type = types[type_index]
+
+ if new_type == UIPartition.UNUSED:
+ LOGGER.debug("new type == unused")
+ else:
+ LOGGER.debug("new type == %s", new_type)
+
+ if has_solaris_data and new_type == ROOT_POOL:
+ self.cycle_type(new_type=new_type, extra_type=extra_type)
+ else:
+ if self.ui_type == UI_TYPE_EMPTY_SPACE:
+ if new_type == ROOT_POOL:
+ # making this the new root pool
+ size_in_sector = self.empty_space_obj.size.get( \
+ Size.sector_units)
+ LOGGER.debug("Used to be unused... adding new slice")
+ new_slice = self.parent.doc_obj.add_slice(self.name, \
+ self.empty_space_obj.start_sector, size_in_sector, \
+ size_units=Size.sector_units)
+ new_slice.in_zpool = ROOT_POOL
+ new_slice.in_vdev = DEFAULT_VDEV_NAME
+ new_slice.tag = V_ROOT
+ else:
+ # setting it back to whatever value was discovered
+ discovered_obj = self.discovered_doc_obj
+ if discovered_obj is not None:
+ self.parent.doc_obj.insert_children(discovered_obj)
+ else:
+ LOGGER.debug("Unable to reset to discovered value")
+
+ else:
+ if new_type == UIPartition.UNUSED:
+ LOGGER.debug("Changing to unused, deleting")
+ LOGGER.debug("Target Call: deleting %s", self.name)
+ self.parent.doc_obj.delete_slice(self.doc_obj)
+ dump_doc("AFTER change type")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/welcome.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+Contains the Welcome Screen for the Text Installer
+'''
+
+from solaris_install.engine import InstallEngine
+from solaris_install.text_install import _, RELEASE, TUI_HELP
+from terminalui.base_screen import BaseScreen
+
+
+class WelcomeScreen(BaseScreen):
+ '''First screen of the text installer.
+ No special __init__ needed beyond that provided by BaseScreen
+
+ '''
+
+ HEADER_TEXT = _("Welcome to %(release)s") % RELEASE
+ WELCOME_TEXT = _("Thanks for choosing to install %(release)s! This "
+ "installer enables you to install the %(release)s "
+ "Operating System (OS) on SPARC or x86 systems.\n\n"
+ "The installation log will be at %(log)s.\n\n"
+ "How to navigate through this installer:")
+ BULLET_ITEMS = [_("Use the function keys listed at the bottom of each "
+ "screen to move from screen to screen and to perform "
+ "other operations."),
+ _("Use the up/down arrow keys to change the selection "
+ "or to move between input fields."),
+ _("If your keyboard does not have function keys, or "
+ "they do not respond, press ESC; the legend at the "
+ "bottom of the screen will change to show the ESC keys"
+ " for navigation and other functions.")]
+ BULLET = "- "
+ HELP_DATA = (TUI_HELP + "/%s/welcome.txt",
+ _("Welcome and Navigation Instructions"))
+
+ def __init__(self, main_win, install_data):
+ super(WelcomeScreen, self).__init__(main_win)
+ self.install_data = install_data
+
+ def set_actions(self):
+ '''Remove the F3_Back Action from the first screen'''
+ self.main_win.actions.pop(self.main_win.back_action.key, None)
+
+ def _show(self):
+ '''Display the static paragraph WELCOME_TEXT'''
+
+ log_file = self.install_data.log_location
+ y_loc = 1
+ fmt = {"log": log_file}
+ fmt.update(RELEASE)
+ text = WelcomeScreen.WELCOME_TEXT % fmt
+ y_loc += self.center_win.add_paragraph(text, start_y=y_loc)
+ x_loc = len(WelcomeScreen.BULLET)
+ for bullet in WelcomeScreen.BULLET_ITEMS:
+ self.center_win.add_text(WelcomeScreen.BULLET, start_y=y_loc)
+ y_loc += self.center_win.add_paragraph(bullet, start_y=y_loc,
+ start_x=x_loc)
--- a/usr/src/lib/install_engine/__init__.py Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/lib/install_engine/__init__.py Wed May 11 15:30:11 2011 -0700
@@ -232,9 +232,11 @@
self.stop_on_error = stop_on_error
self.__currently_executing = None
- # just use 2 decimal precision for progress, and always round
- # down while calculating the progress ratios
- d_context = decimal.Context(prec=2, rounding=decimal.ROUND_DOWN)
+ # Use 8 decimal precision for progress. Using less precision
+ # will cause problems when the estimated progress for some
+ # checkpoints are drastically different than the others.
+ # Always round down while calculating the progress ratios
+ d_context = decimal.Context(prec=8, rounding=decimal.ROUND_DOWN)
decimal.setcontext(d_context)
self.__current_completed = decimal.Decimal("0")
@@ -603,7 +605,7 @@
resumable = self.get_resumable_checkpoints()
if start_from not in resumable:
- raise UsageError("'%s' is not a resumable checkpoint")
+ raise UsageError("'%s' is not a resumable checkpoint" % start_from)
use_latest = (start_from == resumable[-1])
self._rollback(start_from, use_latest)
--- a/usr/src/lib/install_manifest/dtd/Makefile Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/lib/install_manifest/dtd/Makefile Wed May 11 15:30:11 2011 -0700
@@ -37,7 +37,8 @@
configuration.dtd \
execution.dtd \
software.dtd \
- target.dtd
+ target.dtd \
+ media-transfer.dtd
ROOT_DTD_FILES= $(DTD_FILES:%=$(ROOTUSRSHAREINSTALL)/%)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_manifest/dtd/media-transfer.dtd Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,34 @@
+<!--
+ CDDL HEADER START
+
+ The contents of this file are subject to the terms of the
+ Common Development and Distribution License (the "License").
+ You may not use this file except in compliance with the License.
+
+ You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ or http://www.opensolaris.org/os/licensing.
+ See the License for the specific language governing permissions
+ and limitations under the License.
+
+ When distributing Covered Code, include this CDDL HEADER in each
+ file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ If applicable, add the following below this CDDL HEADER, with the
+ fields enclosed by brackets "[]" replaced with your own identifying
+ information: Portions Copyright [yyyy] [name of copyright owner]
+
+ CDDL HEADER END
+
+ Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+
+-->
+
+<!--
+DTD used for specifying software information for media transfer
+-->
+
+<!ELEMENT media_transfer (media_trans)>
+
+<!ENTITY % software SYSTEM "software.dtd">
+%software;
+
+<!ELEMENT media_trans (software+)>
--- a/usr/src/lib/install_target/controller.py Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/lib/install_target/controller.py Wed May 11 15:30:11 2011 -0700
@@ -57,7 +57,7 @@
# Swap ZVOL is required if memory is below this
ZVOL_REQ_MEM = 900
-VFSTAB_FILE = "/etc/vfstab"
+VFSTAB_FILE = "etc/vfstab"
# "TargetController data" is an area in the DataObjectCache
# intended for the TargetController class's private use. It
--- a/usr/src/lib/install_transfer/Makefile Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/lib/install_transfer/Makefile Wed May 11 15:30:11 2011 -0700
@@ -20,13 +20,14 @@
#
#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
#
PYMODS = __init__.py \
cpio.py \
info.py \
ips.py \
+ media_transfer.py \
p5i.py \
prog.py \
svr4.py
--- a/usr/src/lib/install_transfer/cpio.py Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/lib/install_transfer/cpio.py Wed May 11 15:30:11 2011 -0700
@@ -38,6 +38,7 @@
from osol_install.install_utils import file_size
from solaris_install.engine.checkpoint import AbstractCheckpoint as Checkpoint
from solaris_install.engine import InstallEngine
+from solaris_install.target.size import Size
from solaris_install.transfer.info import Args
from solaris_install.transfer.info import CPIOSpec as CPIO
from solaris_install.transfer.info import Destination
@@ -46,6 +47,8 @@
from solaris_install.transfer.info import Source
from solaris_install.transfer.info import ACTION, CONTENTS, CPIO_ARGS
from solaris_install.transfer.prog import ProgressMon
+from solaris_install.transfer.media_transfer import TRANSFER_ROOT, \
+ TRANSFER_MISC, TRANSFER_MEDIA, get_image_size
class AbstractCPIO(Checkpoint):
@@ -92,6 +95,31 @@
# if image_info is used
self._use_image_info = False
+ self._image_size = 0
+
+ def get_media_transfer_size(self):
+ ''' Hack to skip computing size of media transfer checkpoints
+ to we can speed up the start of the install
+ '''
+
+ self.logger.debug("Special size calculation for: " + self.name)
+ if self._image_size == 0:
+ image_info_size = get_image_size(self.logger)
+ size_in_mb = Size(str(image_info_size) + Size.mb_units)
+ self._image_size = size_in_mb.get(Size.kb_units)
+ self.logger.debug("image_info size: %d kb", self._image_size)
+
+ if self.name == TRANSFER_ROOT:
+ weight = 0.75
+ elif self.name == TRANSFER_MISC:
+ weight = 0.05
+ elif self.name == TRANSFER_MEDIA:
+ weight = 0.20
+ else:
+ raise RuntimeError("Not applicable for other checkpoints")
+
+ return int(self._image_size * weight)
+
def get_size(self):
'''Compute the size of the transfer specified'''
@@ -124,31 +152,31 @@
if opt == "IMAGE_SIZE":
# Remove the '\n' character read from
# the file, and convert to integer
- size = int(val.rstrip())
- else:
- # Compute the image size from the data in the transfer install
- # list, if it contains valid data
- for transfer in self._transfer_list:
- if transfer.get(ACTION) == "install":
- file_list = transfer.get(CONTENTS)
- self.logger.debug("Unable to read .image_info file")
- self.logger.debug("Computing distribution size.")
+ return(int(val.rstrip()))
+
+ # Compute the image size from the data in the transfer install
+ # list, if it contains valid data
+ for transfer in self._transfer_list:
+ if transfer.get(ACTION) == "install":
+ file_list = transfer.get(CONTENTS)
+ self.logger.debug("Unable to read .image_info file")
+ self.logger.debug("Computing distribution size.")
- with open(file_list, 'r') as filehandle:
- # Determine the file size for each file listed and sum
- # the sizes.
- try:
- size = size + sum(map(file_size,
- [os.path.join(self.src,
- f.rstrip())
- for f in filehandle.readlines()]))
- except OSError:
- # If the file doesn't exist that's OK.
- pass
+ with open(file_list, 'r') as filehandle:
+ # Determine the file size for each file listed and sum
+ # the sizes.
+ try:
+ size = size + sum(map(file_size,
+ [os.path.join(self.src,
+ f.rstrip())
+ for f in filehandle.readlines()]))
+ except OSError:
+ # If the file doesn't exist that's OK.
+ pass
- # The file_size() function used for calculating size of each
- # file returns the value in bytes. Convert to kilobytes.
- size = size / 1024
+ # The file_size() function used for calculating size of each
+ # file returns the value in bytes. Convert to kilobytes.
+ size = size / 1024
return size
def get_progress_estimate(self):
@@ -157,8 +185,14 @@
'''
if self.distro_size == 0:
- self.distro_size = self.get_size()
+ if self.name == TRANSFER_ROOT or \
+ self.name == TRANSFER_MISC or \
+ self.name == TRANSFER_MEDIA:
+ self.distro_size = self.get_media_transfer_size()
+ else:
+ self.distro_size = self.get_size()
+ self.logger.debug("Distro size: %d KB", self.distro_size)
progress_estimate = \
int((float(self.distro_size) / self.DEFAULT_SIZE) * \
self.DEFAULT_PROG_EST)
@@ -425,14 +459,6 @@
tmp_file = tempfile.mktemp()
with open(tmp_file, 'w') as filehandle:
for file_name in bflist:
- if file_name == "./":
- # This is equivalent to specifying to transfer
- # the entire src. In this case, we can optimize
- # when determining the file size by looking for
- # a .image_info file. Set attribute to indicate
- # to do so.
- self._use_image_info = True
-
filehandle.write(file_name + "\n")
fl_data = tmp_file
sorted_file = tempfile.mktemp()
@@ -482,7 +508,7 @@
# get that size now.
self.distro_size = self.get_size()
- # Start up the ProgressMon to report progress
+ # Start up the ProgressMon to report progress
# while the actual transfer is taking place.
# This needs to be addressed:
@@ -511,7 +537,7 @@
for item in trans.get(CONTENTS):
entry = os.path.join(self.dst, item.rstrip())
try:
- if os.path.isdir(item):
+ if os.path.isdir(entry):
shutil.rmtree(entry)
else:
os.unlink(entry)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_transfer/media_transfer.py Wed May 11 15:30:11 2011 -0700
@@ -0,0 +1,426 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+"""
+media transfer checkpoint. Sub-class of the checkpoint class.
+"""
+
+import os
+import platform
+import logging
+
+from solaris_install import Popen
+from solaris_install.data_object.cache import DataObjectCache
+from solaris_install.engine import InstallEngine
+from solaris_install.engine.checkpoint import AbstractCheckpoint
+from solaris_install.logger import INSTALL_LOGGER_NAME
+from solaris_install.manifest.parser import ManifestParser
+from solaris_install.target.size import Size
+from solaris_install.transfer.info import Software, Source, Destination, \
+ CPIOSpec, Dir
+
+from urllib2 import Request, urlopen
+
+TRANSFER_MANIFEST_NAME = ".transfer-manifest.xml"
+
+TRANSFER_ROOT = "transfer-root"
+TRANSFER_MISC = "transfer-misc"
+TRANSFER_MEDIA = "transfer-media"
+
+INSTALL_TARGET_VAR = "{INSTALL_TARGET}"
+MEDIA_DIR_VAR = "{MEDIA}"
+INSTALL_TARGET = "/a"
+
+NET_SVC = "svc:/system/filesystem/root-assembly:net"
+MEDIA_SVC = "svc:/system/filesystem/root-assembly:media"
+SVCS_CMD = "/bin/svcs"
+SVC_STATUS_DISABLED = "disabled"
+SVC_STATUS_ENABLED = "online"
+
+PRTCONF = "/usr/sbin/prtconf"
+DHCPINFO = "/sbin/dhcpinfo"
+
+IMAGE_INFO_FILENAME = ".image_info"
+IMAGE_SIZE_KEYWORD = "IMAGE_SIZE"
+
+
+class InvalidInstallEnvError(Exception):
+ '''Invalid install environment error
+ '''
+ pass
+
+
+def is_net_booted(logger):
+ '''Determine whether the application is running from a media booted
+ environment or a net booted environment.
+
+ The following SMF services are examined:
+ svc:/system/filesystem/root-assembly:net
+ svc:/system/filesystem/root-assembly:media
+
+ If root-assembly:net is online and root-assembly:media is disabled.
+ The image is running from a net booted environment. This function
+ will return True.
+
+ If root-assembly:net is disabled and root-assembly:media is online.
+ The image is running from a media booted environment. This function
+ will return False.
+
+ If both root-assembly:net and root-assembly:media are
+ online or disabled, that's considered an error condition.
+
+ Exception: InvalidInstallEnvError: if both filesystem/root:media and
+ filesystem/root:net SMF services are online or disabled.
+ '''
+
+ cmd = [SVCS_CMD, "-H", "-o", "STATE", NET_SVC]
+ p = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=logger)
+ net_status = p.stdout.strip()
+
+ logger.debug("%s: %s" % (NET_SVC, net_status))
+
+ cmd = [SVCS_CMD, "-H", "-o", "STATE", MEDIA_SVC]
+ p = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=logger)
+ media_status = (p.stdout).strip()
+ logger.debug("%s: %s" % (MEDIA_SVC, media_status))
+
+ if net_status == SVC_STATUS_ENABLED and \
+ media_status == SVC_STATUS_DISABLED:
+ return True
+
+ if net_status == SVC_STATUS_DISABLED and \
+ media_status == SVC_STATUS_ENABLED:
+ return False
+
+ raise InvalidInstallEnvError("%s is %s, %s is %s" % \
+ (NET_SVC, net_status, MEDIA_SVC, media_status))
+
+
+def get_image_size(logger):
+ '''Total size of the software in the image is stored in the
+ .image_info indicated by the keywoard IMAGE_SIZE.
+ This function retrieves that value from the .image_file
+ The size recorded in the .image_file is in KB, other functions
+ in this file uses the value in MB, so, this function will
+ return the size in MB
+
+ Returns:
+ size of retrieved from the .image_info file in MB
+
+ '''
+
+ # Depending on how we are booted, get the .image_info file accordingly.
+ if is_net_booted(logger):
+ image_info_file = os.path.join(NetPrepareMediaTransfer.MEDIA_SOURCE,
+ IMAGE_INFO_FILENAME)
+ else:
+ image_info_file = os.path.join(PrepareMediaTransfer.MEDIA_SOURCE,
+ IMAGE_INFO_FILENAME)
+
+ img_size = 0
+ with open(image_info_file, 'r') as ih:
+ for line in ih:
+ (opt, val) = line.split("=")
+ if opt == IMAGE_SIZE_KEYWORD:
+ # Remove the '\n' character read from
+ # the file, and convert to integer
+ img_size = int(val.rstrip('\n'))
+ break
+
+ if (img_size == 0):
+ # We should have read in a size by now
+ logger.error("Unable to read the image size from %s", image_info_file)
+ raise RuntimeError
+
+ logger.debug("Read from %s size of %s" % (image_info_file, img_size))
+ return (Size(str(img_size) + Size.kb_units).get(Size.mb_units))
+
+
+def download_files(url, dst, logger):
+ '''Download the file specified in the URL to a
+ specified local location.
+ '''
+
+ logger.debug("Planning to download: " + url)
+
+ request = Request(url)
+
+ url_request = urlopen(request)
+ dst_dir = os.path.dirname(dst)
+ if not os.path.exists(dst_dir):
+ os.makedirs(dst_dir)
+ with open(dst, "w") as dst_file:
+ dst_file.write(url_request.read())
+
+
+def init_prepare_media_transfer(name):
+ '''This function instantiates either the PrepareMediaTransfer
+ or the NetePrepareMediaTransfer checkpoint depending on how the
+ image is booted.
+
+ If the image is booted from the media, the
+ PrepareMediaTransfer checkpoint is instantiated.
+
+ If the image is booted from the net, the
+ NetPrepareMediaTransfer checkpoint is instantiated.
+
+ Arguments: None
+
+ Returns: An instantiated instance of PrepareMediaTransfer checkpoint or
+ NetPrepareMediaTransfer checkpoint
+
+ '''
+
+ logger = logging.getLogger(INSTALL_LOGGER_NAME)
+
+ if is_net_booted(logger):
+ logger.debug("Going to init NetPrepareMediaTransfer")
+ return NetPrepareMediaTransfer(name)
+ else:
+ logger.debug("Going to init PrepareMediaTransfer")
+ return PrepareMediaTransfer(name)
+
+
+def setup_doc_content(manifest_name, media_source):
+ '''Loads the content of media manifest into a
+ SEPARATE DOC instance. Then, copy the data into the
+ DOC that's used by the application, engine and other checkpoints.
+ '''
+
+ # create a separate DOC
+ another_doc = DataObjectCache()
+
+ # load the transfer manifest into the separate DOC
+ manifest_parser = ManifestParser("manifest-parser", manifest_name,
+ validate_from_docinfo=False)
+ manifest_parser.parse(doc=another_doc)
+
+ software_nodes = another_doc.get_descendants(class_type=Software)
+
+ if len(software_nodes) != 3:
+ raise RuntimeError("3 software nodes expected. Only %d found" %
+ len(software_nodes))
+
+ # Modify the install target values and the media mountpoint
+ for software in software_nodes:
+ # get the destination object
+ dst_node = software.get_children(class_type=Destination,
+ not_found_is_err=True)[0]
+ dir_node = dst_node.get_children(class_type=Dir,
+ not_found_is_err=True)[0]
+ path = dir_node.dir_path
+ path = path.replace(INSTALL_TARGET_VAR, INSTALL_TARGET)
+ dir_node.dir_path = path
+
+ # if this is the media transfer software node, also update the
+ # value for source
+ if software._name == TRANSFER_MEDIA:
+ src_node = software.get_children(class_type=Source,
+ not_found_is_err=True)[0]
+ dir_node = src_node.get_children(class_type=Dir,
+ not_found_is_err=True)[0]
+ path = dir_node.dir_path
+ path = path.replace(MEDIA_DIR_VAR, media_source)
+ dir_node.dir_path = path
+
+ # copy the Software classes into the common DOC
+ doc = InstallEngine.get_instance().data_object_cache
+ doc.volatile.insert_children(software_nodes)
+
+
+class PrepareMediaTransfer(AbstractCheckpoint):
+ '''This checkpoint is used by installation environments booted from
+ the media. It prepares the DOC for CPIO based transfer.
+ This checkpoint will load the /.cdrom/.transfer-manifest.xml content
+ into the transient subtree of the DOC and fill in values of
+ various mountpoints in the DOC
+ '''
+
+ MEDIA_SOURCE = "/.cdrom"
+
+ def __init__(self, name):
+ super(PrepareMediaTransfer, self).__init__(name)
+ self.logger.debug("PrepareMediaTransfer init")
+
+ def get_progress_estimate(self):
+ return 5
+
+ def execute(self, dry_run=False):
+
+ manifest_name = os.path.join(PrepareMediaTransfer.MEDIA_SOURCE,
+ TRANSFER_MANIFEST_NAME)
+
+ setup_doc_content(manifest_name, PrepareMediaTransfer.MEDIA_SOURCE)
+
+
+class NetPrepareMediaTransfer(AbstractCheckpoint):
+ ''' The NetPrepareMediaTransfer checkpoint is instantiated by the
+ init_prepare_media_transfer() function for installation environments
+ booted from the net. This checkpoint will first download the
+ transfer-manifest.xml from the server, then, load it into the DOC.
+ Based on content of the DOC, the checkpoint will also download any
+ other files that needs to be copied into the install target,
+ but not yet present in the booted environment. Similar to
+ PrepareMediaTransfer, this checkpoint will also download and
+ mount the root archives we are not booted with and fill in values
+ of mountpoints.
+ '''
+
+ # For SPARC, The URL for the AI server is stored in wanboot.conf.
+ # This file should have been mounted during boot
+ # so, it has to exist
+ WANBOOT_CONF = "/etc/netboot/wanboot.conf"
+
+ MEDIA_SOURCE = "/tmp"
+
+ def __init__(self, name):
+ super(NetPrepareMediaTransfer, self).__init__(name)
+ self.logger.debug("NetPrepareMediaTransfer init")
+
+ def get_progress_estimate(self):
+ return 5
+
+ def get_server_url(self):
+ '''Get server URL for downloading files '''
+ if platform.processor() == "sparc":
+ with open(NetPrepareMediaTransfer.WANBOOT_CONF) as wanboot_conf:
+
+ ai_server = None
+ ai_image = None
+
+ for line in wanboot_conf:
+ if line.startswith("root_server="):
+ # AI server line have the following format:
+ # root_server=http://<ai_server>:<port>/\
+ # <path_to_wanboot-cgi>
+ # and extract out the http://<ai_server>:<port> portion
+ (not_used, val) = line.split("=")
+ split_val = val.split("/", 3)
+ #remove the last part, since it is not useful
+ split_val.remove(split_val[3])
+ self.logger.debug("URL: " + "/".join(split_val))
+ ai_server = "/".join(split_val) #re-create the the URL
+ self.logger.debug("ai_server: " + ai_server)
+ elif line.startswith("root_file="):
+ # AI image line have the following format
+ # root_file=<ai_image>/boot/platform/sun4v/boot_archive
+ # extract out the <ai_image> part
+ (not_used, val) = line.split("=")
+ split_val = val.rsplit("/", 4)
+ ai_image = split_val[0]
+ self.logger.debug("ai_image: " + ai_image)
+
+ if ai_server is None:
+ raise RuntimeError("Unable to find AI server value "
+ "from %s",
+ NetPrepareMediaTransfer.WANBOOT_CONF)
+
+ if ai_image is None:
+ raise RuntimeError("Unable to find AI image value "
+ "from %s",
+ NetPrepareMediaTransfer.WANBOOT_CONF)
+ return(ai_server + ai_image)
+ else:
+ cmd = [PRTCONF, "-v", "/devices"]
+ p = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+ logger=self.logger)
+ use_next_line = False
+ val = None
+ for line in p.stdout.splitlines():
+ line = line.strip()
+ self.logger.debug("line: " + line)
+ if use_next_line:
+ val = line
+ break
+ if line.startswith("name='install_media'"):
+ use_next_line = True
+
+ if val is None:
+ raise RuntimeError("Unable to find install_media line")
+
+ self.logger.debug("found line: " + val)
+
+ # want the value after the equal sign
+ (not_used, url) = val.split("=")
+
+ # remove the "'"
+ url = url.strip("'")
+
+ if len(url) == 0:
+ raise RuntimeError("Unable to find url, found string = " + val)
+
+ # If the $serverIP is specified, need to find the server IP address
+ # and fill it in
+ if url.find("$serverIP") >= 0:
+ cmd = [DHCPINFO, "BootSrvA"]
+ p = Popen.check_call(cmd, stdout=Popen.STORE,
+ stderr=Popen.STORE, logger=self.logger)
+ ip_out = p.stdout.strip()
+ if len(ip_out) == 0:
+ raise RuntimeError("Unable to find server IP address")
+
+ url = url.replace("$serverIP", ip_out)
+
+ self.logger.debug("Going to return URL: " + url)
+ return(url)
+
+ def execute(self, dry_run=False):
+
+ # get AI server URL
+ server_url = self.get_server_url()
+
+ self.logger.debug("server URL: " + server_url)
+
+ manifest_name = os.path.join(NetPrepareMediaTransfer.MEDIA_SOURCE,
+ TRANSFER_MANIFEST_NAME)
+ # download the media manifest from the server
+ download_files(server_url + "/" + TRANSFER_MANIFEST_NAME,
+ manifest_name, self.logger)
+
+ setup_doc_content(manifest_name, NetPrepareMediaTransfer.MEDIA_SOURCE)
+
+ # Take a look at "transfer-media" node of the DOC, and download
+ # all listed files
+
+ doc = InstallEngine.get_instance().data_object_cache
+
+ software_nodes = doc.volatile.get_descendants(class_type=Software)
+
+ for software in software_nodes:
+ if software._name == TRANSFER_MEDIA:
+ cpio_spec = software.get_children(class_type=CPIOSpec,
+ not_found_is_err=True)
+ file_list = (cpio_spec[0]).contents
+ for file in file_list:
+ # download each file
+ dst_name = \
+ os.path.join(NetPrepareMediaTransfer.MEDIA_SOURCE, \
+ file)
+ self.logger.debug("Downloading " + file)
+ download_files(server_url + "/" + file, dst_name,
+ self.logger)
--- a/usr/src/pkg/manifests/install-distribution-constructor.mf Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/pkg/manifests/install-distribution-constructor.mf Wed May 11 15:30:11 2011 -0700
@@ -59,6 +59,7 @@
dir path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const
dir path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints
dir path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/defaultfiles
+dir path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/xslt
dir path=usr/share group=sys
dir path=usr/share/distro_const
dir path=usr/share/distro_const/profile
@@ -116,6 +117,7 @@
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/custom_script.py mode=0444
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/custom_script.pyc mode=0444
file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/defaultfiles/rtc_config.default mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/xslt/doc2_media_transfer.xslt mode=0444
file path=usr/share/man/man1m/distro_const.1m mode=0444
file path=usr/share/man/man4/dc_manifest.4 mode=0444
license cr_Sun license=cr_Sun
--- a/usr/src/pkg/manifests/system-install-text-install.mf Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/pkg/manifests/system-install-text-install.mf Wed May 11 15:30:11 2011 -0700
@@ -43,9 +43,8 @@
dir path=usr/lib
dir path=usr/lib/python2.6
dir path=usr/lib/python2.6/vendor-packages
-dir path=usr/lib/python2.6/vendor-packages/osol_install
-dir path=usr/lib/python2.6/vendor-packages/osol_install/profile
-dir path=usr/lib/python2.6/vendor-packages/osol_install/text_install
+dir path=usr/lib/python2.6/vendor-packages/solaris_install/
+dir path=usr/lib/python2.6/vendor-packages/solaris_install/text_install
dir path=usr/sbin
dir path=usr/share group=sys
dir path=usr/share/text-install group=sys
@@ -56,42 +55,34 @@
file path=lib/svc/manifest/system/text-mode-menu.xml mode=0444 group=sys
file path=opt/install-test/bin/ict_test mode=0555
file path=usr/bin/text-install mode=0555 group=sys
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/__init__.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/__init__.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/disk_info.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/disk_info.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/disk_space.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/disk_space.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/install_profile.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/install_profile.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/partition_info.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/partition_info.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/slice_info.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/profile/slice_info.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/__init__.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/__init__.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/disk_selection.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/disk_selection.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/disk_window.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/disk_window.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/fdisk_partitions.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/fdisk_partitions.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/install_progress.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/install_progress.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/install_status.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/install_status.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/log_viewer.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/log_viewer.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/partition_edit_screen.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/partition_edit_screen.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/summary.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/summary.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/ti_install_utils.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/ti_install_utils.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/ti_install.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/ti_install.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/welcome.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/welcome.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/__init__.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/__init__.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/disk_selection.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/disk_selection.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/disk_window.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/disk_window.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/fdisk_partitions.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/fdisk_partitions.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/install_progress.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/install_progress.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/install_status.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/install_status.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/log_viewer.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/log_viewer.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/partition_edit_screen.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/partition_edit_screen.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/progress.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/progress.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/ti_install.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/ti_install.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/ti_install_utils.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/ti_install_utils.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/ti_target_utils.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/ti_target_utils.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/summary.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/summary.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/welcome.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/solaris_install/text_install/welcome.pyc mode=0444
file path=usr/sbin/text-mode-menu mode=0555
file path=usr/share/text-install/help/C/disks.txt group=sys
--- a/usr/src/pkg/manifests/system-library-install.mf Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/pkg/manifests/system-library-install.mf Wed May 11 15:30:11 2011 -0700
@@ -199,6 +199,8 @@
file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/info.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/ips.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/ips.pyc
+file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/media_transfer.py
+file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/media_transfer.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/p5i.py
file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/p5i.pyc
file path=usr/lib/python2.6/vendor-packages/solaris_install/transfer/prog.py
@@ -258,6 +260,7 @@
file path=usr/share/install/configuration.dtd mode=0444 group=sys
file path=usr/share/install/dc.dtd mode=0444 group=sys
file path=usr/share/install/execution.dtd mode=0444 group=sys
+file path=usr/share/install/media-transfer.dtd mode=0444 group=sys
file path=usr/share/install/software.dtd mode=0444 group=sys
file path=usr/share/install/target.dtd mode=0444 group=sys
file path=usr/snadm/lib/libspmicommon.so.1
--- a/usr/src/tools/tests/tests.nose Wed May 11 14:33:33 2011 -0700
+++ b/usr/src/tools/tests/tests.nose Wed May 11 15:30:11 2011 -0700
@@ -30,4 +30,4 @@
# the files in that directory should begin with "test_". Files
# containing in-line doc-tests should be added explicitly.
-tests=lib/install_common/test/,lib/liberrsvc_pymod/test/,cmd/ai-webserver/test/,cmd/text-install/osol_install/text_install/test/,cmd/installadm/test/,cmd/installadm/installadm_common.py,lib/install_utils/test/,lib/libict_pymod/test/,lib/install_logging_pymod/test,lib/install_doc/test,lib/install_engine/test,lib/install_manifest/test/,lib/install_transfer/test,cmd/distro_const/checkpoints/test,cmd/js2ai/modules/test/test_suite.py,lib/terminalui/test,cmd/system-config/profile/test/,cmd/system-config/test/,lib/install_manifest_input/test/,lib/install_target/test/
+tests=lib/install_common/test/,lib/liberrsvc_pymod/test/,cmd/ai-webserver/test/,cmd/text-install/test/,cmd/installadm/test/,cmd/installadm/installadm_common.py,lib/install_utils/test/,lib/libict_pymod/test/,lib/install_logging_pymod/test,lib/install_doc/test,lib/install_engine/test,lib/install_manifest/test/,lib/install_transfer/test,cmd/distro_const/checkpoints/test,cmd/js2ai/modules/test/test_suite.py,lib/terminalui/test,cmd/system-config/profile/test/,cmd/system-config/test/,lib/install_manifest_input/test/,lib/install_target/test/