def test_textwidth(self): ''' test textwidth() ''' # without tab self.assertEqual(textwidth(u'A\u00c0\u3042'), 1 + 1 + 2) self.assertEqual(textwidth(u'A\u00c0\u3042'.encode(get_encoding())), 1 + 1 + 2) # with tab self.assertEqual(textwidth(u'\tA\u00c0\u3042'), 4 + 1 + 1 + 2) self.assertEqual(textwidth(u'\tA\u00c0\u3042'.encode(get_encoding())), 4 + 1 + 1 + 2)
def test_charwidth(self): ''' test charwidth() ''' # example of 1-column character (ASCII) self.assertEqual(charwidth(u'A'), 1) self.assertEqual(charwidth(u'A'.encode(get_encoding())), 1) # example of 1-column character (non-ASCII) self.assertEqual(charwidth(u'\u00c0'), 1) self.assertEqual(charwidth(u'\u00c0'.encode(get_encoding())), 1) # example of 2-column character self.assertEqual(charwidth(u'\u3042'), 2) self.assertEqual(charwidth(u'\u3042'.encode(get_encoding())), 2)
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" enctext = text.encode(get_encoding()) # encode per locale for floating point conversion edit_field.set_text("%.1f" % locale.atof(enctext)) 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) # encode user input per locale for floating point conversion enctext = new_size_text.encode(get_encoding()) new_size = Size(str(locale.atof(enctext)) + 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: parent_doc_obj = edit_field.data_obj.doc_obj.parent if isinstance(parent_doc_obj, Disk): if isinstance(edit_field.data_obj.doc_obj, Partition): resized_obj = parent_doc_obj.resize_partition( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) else: resized_obj = parent_doc_obj.resize_slice( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) else: resized_obj = parent_doc_obj.resize_slice( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), 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 add_text(self, text, start_y=0, start_x=0, max_chars=None, centered=False): '''Add a single line of text to the window 'text' must fit within the specified space, or it will be truncated ''' win_y, win_x = self.window.getmaxyx() terminalui.LOGGER.log(LOG_LEVEL_INPUT, "start_y=%d, start_x=%d, " "max_chars=%s, centered=%s, win_max_x=%s, " "win_max_y=%s", start_y, start_x, max_chars, centered, win_x, win_y) max_x = self.window.getmaxyx()[1] - self.border_size[1] start_x += self.border_size[1] abs_max_chars = max_x - start_x if max_chars is None: max_chars = abs_max_chars else: max_chars = min(max_chars, abs_max_chars) text = fit_text_truncate(text, max_chars) if centered: start_x = (max_x - textwidth(text)) / 2 + start_x if isinstance(text, unicode): text = text.encode(get_encoding()) terminalui.LOGGER.log(LOG_LEVEL_INPUT, "calling addstr with params start_y=%s," "start_x=%s, text=%s", start_y, start_x, text) self.window.addstr(start_y, start_x, text) self.no_ut_refresh()
def show_actions(self): '''Read through the actions dictionary, displaying all the actions descriptive text along the footer (along with a prefix linked to its associated keystroke) ''' self.footer.window.clear() if InnerWindow.USE_ESC: prefix = " Esc-" else: prefix = " F" strings = [] length = 0 action_format = "%s%i_%s" for key in sorted(self.actions.keys()): key_num = key - curses.KEY_F0 action_text = self.actions[key].text action_str = action_format % (prefix, key_num, action_text) strings.append(action_str) display_str = "".join(strings) max_len = self.footer.window.getmaxyx()[1] length = textwidth(display_str) if not InnerWindow.USE_ESC: length += (len(" Esc-") - len(" F")) * len(self.actions) if length > max_len: raise ValueError("Can't display footer actions - string too long") self.footer.window.addstr(display_str.encode(get_encoding())) self.footer.window.noutrefresh()
def exit_text_installer(logname=None, errcode=0): '''Teardown any existing iSCSI objects, Close out the logger and exit with errcode''' # get the Iscsi object from the DOC, if present. doc = InstallEngine.get_instance().doc iscsi = doc.volatile.get_first_child(name=ISCSI_LABEL, class_type=Iscsi) if iscsi: # The user exited prematurely, so try to tear down the Iscsi object try: LOGGER.debug("User has exited installer. Tearing down " "Iscsi object") iscsi.teardown() except CalledProcessError as err: # only print something to the screen if the errcode is nonzero if errcode != 0: print _("Unable to tear down iSCSI initiator:\n%s" % err) else: LOGGER.debug("Tearing down Iscsi object failed: %s" % err) 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 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 _exit(logname, errcode=0): '''Close out the logger and exit with errcode''' LOGGER.info("**** END ****") # LOGGER.close() # LOGGER.close() is broken - CR 7012566 print _("Exiting System Configuration Tool. Log is available at:\n" "%s") % logname if isinstance(errcode, unicode): # pylint: disable-msg=E1103 errcode = errcode.encode(get_encoding()) sys.exit(errcode)
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() radixchar = locale.localeconv()['decimal_point'] if text.endswith(" "): raise UIMessage(_('Only the digits 0-9 and %s are valid.') % radixchar) vals = text.split(radixchar) if len(vals) > 2: raise UIMessage(_('A number can only have one %s') % radixchar) 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 %s are valid.') % radixchar) 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(radixchar) if not text: text = "0" # encode user input per locale for floating point conversion text = text.encode(get_encoding()) new_size = Size(str(locale.atof(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: locale_new_size = locale.format("%.1f", new_size_rounded) locale_max_size = locale.format("%.1f", max_size_rounded) msg = _("The new size ") + locale_new_size + \ _(" is greater than the available space ") + locale_max_size raise UIMessage(msg) return True
def parse_unconfig_args(parser, args): # Now parse options specific to the subcommand (options, sub_cmd) = parser.parse_args(args) options.alt_root = os.getenv(ALT_ROOT_ENV_VAR) # If operating on alternate root, verify given path. if options.alt_root: if not os.access(options.alt_root, os.W_OK): print _("Root filesystem provided for the non-global zone" " does not exist") sys.exit(SU_FATAL_ERR) # # unconfiguration/reconfiguration is not permitted in ROZR non-global # zone unless such zone is booted in writable mode. # When operating in alternate root mode, skip that check, since in that # case ROZR zone would be manipulated from global zone. That along with # previous check guarantees writable access. # if not options.alt_root and _in_rozr_zone(): print _("Root filesystem mounted read-only, '%s' operation" " not permitted." % sub_cmd[0]) print _("The likely cause is that sysconfig(1m) was invoked" " in ROZR non-global zone.") print _("In that case, see mwac(5) and zonecfg(1m) man pages" " for additional information.") sys.exit(SU_FATAL_ERR) # At this time, it is believed that there is no point in allowing # the prompt to be displayed in the alternate root case. Since there is no # use case, let's not explode our test matrix. if options.alt_root and options.shutdown: parser.error("Invalid to specify -s option with an alternate root") # If no grouping is specified, that implies a system level unconfiguration. # Confirm with the user that this is what they want for security reasons. if not options.grouping: if sub_cmd[0] == CONFIGURE: print _("This program will re-configure your system.") else: print _("This program will unconfigure your system.") print _("The system will be reverted to a \"pristine\" state.") print _("It will not have a name or know about other systems" " or networks.") msg = _("Do you want to continue (y/[n])? ") confirm = raw_input(msg.encode(get_encoding())) if confirm.lower() != "y": sys.exit(SU_OK) # They have confirmed, set grouping to system. options.grouping = [SYSTEM] # Formulate the comma separated string into a list for grp in options.grouping: if COMMA in grp: options.grouping.extend(map(str.strip, grp.split(","))) options.grouping.remove(grp) break # If the user specifies a list of groups and one of them is system set # to just system since it is a superset of all other groupings. if len(options.grouping) > 1 and SYSTEM in options.grouping: options.grouping = [SYSTEM] if options.alt_root: svccfg_repository = os.path.join(options.alt_root, SMF_REPOSITORY) os.environ["SVCCFG_REPOSITORY"] = svccfg_repository # Check that the functional groups requested are valid valid_group_check(options.grouping, sub_cmd, parser) # If not operating on the alternate root, we can and must check to make # sure that the grouping specified has services of that grouping on the # image. if not options.alt_root and SYSTEM not in options.grouping: # Check that there exist unconfigurable services with the specified # grouping on the machine. cmd = [SVCPROP, "-p", "unconfigure/exec", "*"] p_ret = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.DEVNULL, check_result=Popen.ANY) unconfigurable_svcs = p_ret.stdout.split("\n") for opt_grp in options.grouping: found = False for svcs in unconfigurable_svcs: if svcs: svc = svcs.split("/:properties")[0] cmd = [SVCPROP, "-p", "sysconfig/group", svc] p_ret = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.DEVNULL, check_result=Popen.ANY) grping = p_ret.stdout.strip() if grping == opt_grp: found = True if not found: print _("There are no services on the system with grouping " "of %s" % opt_grp) sys.exit(1) # # The user specified a profile file or a directory with profiles to use # on the configure. Verify that supplied profiles contain .xml suffix # (otherwise smf(5) will not apply them) and syntactically # validate them. Then copy them to the temporary location, # /etc/svc/profile/sc/ directory. The unconfig milestone will move # them to the site area when it runs. We can't put profiles # to site directory right now because they may end up applied before # unconfiguration runs and then removed during unconfiguration. # if sub_cmd[0] == CONFIGURE and options.profile: # Verify that supplied path (file or directory) exists. if not os.path.exists(options.profile): parser.error("%s does not exist." % options.profile) # Remove all of /etc/svc/profile/sc custom_profile_dir = CUSTOM_PROFILE_DIR if options.alt_root: custom_profile_dir = os.path.join(options.alt_root, custom_profile_dir) else: custom_profile_dir = os.path.join("/", custom_profile_dir) if os.path.exists(custom_profile_dir): for root, dirs, files in os.walk(custom_profile_dir): for profile_file in files: os.unlink(os.path.join(root, profile_file)) else: os.mkdir(custom_profile_dir) if os.path.isdir(options.profile): # # Profile directory is specified. # Directory has to contain at least one profile. Start with # assumption that given directory does not contain any # profile. # profile_dir_is_empty = True # Validate profiles - all supplied profiles have to validate. for root, dirs, files in os.walk(options.profile): for pfile in files: profile_file = os.path.join(root, pfile) # Abort if profile is invalid. if not profile_is_valid(profile_file): sys.exit(SU_FATAL_ERR) # Place profile in the temporary site profile area. orig_umask = os.umask(0377) shutil.copyfile(profile_file, os.path.join(custom_profile_dir, pfile)) os.umask(orig_umask) profile_dir_is_empty = False # If no profile was found in given directory, abort. if profile_dir_is_empty: print _("Directory %s does not contain any profile." % options.profile) sys.exit(SU_FATAL_ERR) else: # Profile is a file - validate it. if not profile_is_valid(options.profile): sys.exit(SU_FATAL_ERR) # Place profile in the temporary site profile area. orig_umask = os.umask(0377) shutil.copyfile(options.profile, os.path.join(custom_profile_dir, os.path.basename(options.profile))) os.umask(orig_umask) # # if there is a request to re-configure system in interactive way # (using SCI tool), check if we are on console, since this is where SCI # tool will be lauched. If we are not on console, let user confirm this # is really what one wants to do. # if sub_cmd[0] == CONFIGURE and not options.profile \ and not options.alt_root: # For now, sysconfig should halt when the user runs sysconfig configure # after running sysconfig unconfigure. When the functionality that # enables the unconfiguration of individual groupings is implemented, # this may need to be revisited. Currently, system is the only group # that can be configured/unconfigured. cmd = [SVCPROP, "-c", "-p", "sysconfig/unconfigure", "milestone/unconfig"] p_ret = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.DEVNULL, check_result=Popen.ANY) unconfigure_has_occurred = p_ret.stdout.strip() cmd = [SVCPROP, "-c", "-p", "sysconfig/configure", "milestone/config"] p_ret = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.DEVNULL, check_result=Popen.ANY) config_has_occurred = p_ret.stdout.strip() if unconfigure_has_occurred == 'true' \ and config_has_occurred == 'false': print _("Error: system has been unconfigured. Reboot to invoke " "the SCI Tool and configure the system.") sys.exit(SU_FATAL_ERR) print _("Interactive configuration requested.") print _("System Configuration Interactive (SCI) tool will be" + " launched on console.") cmd = [TTY] try: p = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.PIPE, check_result=(Popen.STDERR_EMPTY, 0)) except CalledProcessError as err: print err.popen.stderr # if tty fails, we may not be able to interact with user. # In this case, skip the confirmation prompt. else: terminal_device = p.stdout.strip() if terminal_device != CONSOLE: print _("Since you are currently not logged on console,\n" "you may not be able to navigate SCI tool.") msg = _("Would you like to proceed with re-configuration " "(y/[n])? ") confirm = raw_input(msg.encode(get_encoding())) if confirm.lower() != "y": sys.exit(SU_OK) return (options, sub_cmd)
def resize_validate(edit_field, disk_win=None): '''Check text to see if it is a decimal number of precision no greater than the tenths place. Resize the partition if everything checks out. ''' LOGGER.debug("in resize_validate()") text = edit_field.get_text().lstrip() radixchar = locale.localeconv()['decimal_point'] if text.endswith(" "): raise UIMessage( _('Only the digits 0-9 and "%s" are valid.') % radixchar) vals = text.split(radixchar) if len(vals) > 2: raise UIMessage(_('A number can only have one "%s"') % radixchar) 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 "%s" are valid.') % radixchar) if len(vals) > 1 and len(vals[1]) > 1: raise UIMessage(_("Size can be specified to only one decimal place.")) if disk_win is None: return True text = text.rstrip(radixchar) # If the user deleted all digits, leave partition size alone until user # inputs new digits if not text: LOGGER.debug("No size value digits, skipping resize") return True 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) # encode user input per locale for floating point conversion text = text.encode(get_encoding()) new_size = Size(str(locale.atof(text)) + Size.gb_units) max_size = edit_field.data_obj.get_max_size() # When comparing user input and display sizes, check only to the first # decimal place as that is all the user sees. 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: locale_new_size = locale.format("%.1f", new_size_rounded) locale_max_size = locale.format("%.1f", max_size_rounded) msg = _("The new size %(size)s is greater than " "the available space %(avail)s") % \ {"size": locale_new_size, "avail": locale_max_size} raise UIMessage(msg) new_size_text = text.strip() LOGGER.debug("New size text=%s", new_size_text) old_size = edit_field.data_obj.size new_size_byte = new_size.get(Size.byte_units) # Filter out edits that would result in resizing a partition to zero if new_size_byte == 0: return True old_size_byte = old_size.get(Size.byte_units) precision_bytes = Size(UI_PRECISION).get(Size.byte_units) # Ignore potential rounding artifacts. if abs(new_size_byte - old_size_byte) <= precision_bytes: return True max_size_byte = max_size.get(Size.byte_units) if new_size_byte > max_size_byte: # Allow for loss of precision from rounding errors, but no greater if (new_size_byte - max_size_byte) > precision_bytes: raise RuntimeError("Requested partition resize to %d bytes " "exceeds maximum size available: %d" % (new_size_byte, max_size_byte)) # Clamp the new size at max size otherwise the resize will throw # an InsufficientSpaceError. LOGGER.debug( "Requested partition resize exceeds maximum size: " "Clamping %d bytes to %d bytes", new_size_byte, max_size_byte) new_size = max_size parent_doc_obj = edit_field.data_obj.doc_obj.parent if isinstance(parent_doc_obj, Disk): if isinstance(edit_field.data_obj.doc_obj, GPTPartition): resized_obj = parent_doc_obj.resize_gptpartition( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) elif isinstance(edit_field.data_obj.doc_obj, Partition): resized_obj = parent_doc_obj.resize_partition( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) else: resized_obj = parent_doc_obj.resize_slice( edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) else: resized_obj = parent_doc_obj.resize_slice(edit_field.data_obj.doc_obj, new_size.get(Size.gb_units), size_units=Size.gb_units) if isinstance(resized_obj, Partition): # Don't do this for GPTPartition because there is no guarantee this # will be the installation target partition if there is more than # 1 Solaris partition resized_obj.in_zpool = ROOT_POOL elif isinstance(resized_obj, Slice): if resized_obj.in_zpool == ROOT_POOL: resized_obj.tag = V_ROOT disk_win.set_disk_info(ui_obj=disk_win.ui_obj) disk_win.activate_index(part_order) dump_doc("After resize") return True