From e277af4357f0884b0f71cf00c161728399c689e1 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Wed, 18 Dec 2013 14:54:49 -0600
Subject: [PATCH 07/28] Use blivet.size.Size for all size quantities.

In addition to resolving a host of long-standing issues with handling of
size units, this changes the default unit in the GUI from MB
(SI/decimal) to MiB (IEC/binary). The reason for this is that everything
is binary at the bottom of the stack. I do not care what disk
manufacturers write on their boxes. Disk sectors are normally 512 bytes
-- not 500 bytes. Sizes of disk partitions will rarely look reasonable
expressed as decimal-prefixed units since an even number in base-10 is
almost never evenly divisible by 512. Since we need to be able to detect
resize based on the contents of the size entry, regardless of units, we
cannot lose any precision in our display of sizes, which rules out lying
to users to make it seem like their decimal-prefixed sizes can be
attained. Sizes can still be expressed in decimal units, but we
will display binary units.

(cherry picked from commit 41f87df183d174322aead7f0cd2fe98a914f54e9)
---
 pyanaconda/bootloader.py                  | 19 ++++----
 pyanaconda/installclass.py                | 11 +++--
 pyanaconda/kickstart.py                   | 79 +++++++++++++++++++++----------
 pyanaconda/ui/gui/spokes/custom.glade     |  2 +-
 pyanaconda/ui/gui/spokes/custom.py        | 56 ++++++++++------------
 pyanaconda/ui/gui/spokes/filter.py        |  8 ++--
 pyanaconda/ui/gui/spokes/lib/accordion.py |  7 +--
 pyanaconda/ui/gui/spokes/lib/cart.py      |  5 +-
 pyanaconda/ui/gui/spokes/lib/resize.py    | 65 +++++++++++++------------
 pyanaconda/ui/gui/spokes/storage.py       | 19 ++++----
 pyanaconda/ui/lib/disks.py                | 10 +---
 pyanaconda/ui/tui/spokes/storage.py       |  4 +-
 12 files changed, 149 insertions(+), 136 deletions(-)

diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
index a9a4ec2..db58ab7 100644
--- a/pyanaconda/bootloader.py
+++ b/pyanaconda/bootloader.py
@@ -39,6 +39,7 @@ import pyanaconda.network
 from pyanaconda.packaging.rpmostreepayload import RPMOSTreePayload
 from pyanaconda.nm import nm_device_hwaddress
 from blivet import platform
+from blivet.size import Size
 from pyanaconda.i18n import _, N_
 from blivet.errors import SinglePhysicalVolumeError
 
@@ -243,7 +244,7 @@ class BootLoader(object):
     stage2_bootable = False
     stage2_must_be_primary = True
     stage2_description = N_("/boot file system")
-    stage2_max_end_mb = 2 * 1024 * 1024
+    stage2_max_end = Size(en_spec="2 TiB")
 
     @property
     def stage2_format_types(self):
@@ -487,15 +488,15 @@ class BootLoader(object):
         log.debug("_is_valid_size(%s) returning %s", device.name, ret)
         return ret
 
-    def _is_valid_location(self, device, max_mb=None, desc=""):
+    def _is_valid_location(self, device, max_end=None, desc=""):
         ret = True
-        if max_mb and device.type == "partition" and device.partedPartition:
+        if max_end and device.type == "partition" and device.partedPartition:
             end_sector = device.partedPartition.geometry.end
             sector_size = device.partedPartition.disk.device.sectorSize
-            end_mb = (sector_size * end_sector) / (1024.0 * 1024.0)
-            if end_mb > max_mb:
-                self.errors.append(_("%(desc)s must be within the first %(max_end)dMB of "
-                                     "the disk.") % {"desc": desc, "max_end": max_mb})
+            end = Size(bytes=sector_size * end_sector)
+            if end > max_end:
+                self.errors.append(_("%(desc)s must be within the first %(max_end)s of "
+                                     "the disk.") % {"desc": desc, "max_end": max_end})
                 ret = False
 
         log.debug("_is_valid_location(%s) returning %s", device.name, ret)
@@ -610,7 +611,7 @@ class BootLoader(object):
             valid = False
 
         if not self._is_valid_location(device,
-                                       max_mb=constraint["max_end_mb"],
+                                       max_end=constraint["max_end"],
                                        desc=description):
             valid = False
 
@@ -714,7 +715,7 @@ class BootLoader(object):
             valid = False
 
         if not self._is_valid_location(device,
-                                       max_mb=self.stage2_max_end_mb,
+                                       max_end=self.stage2_max_end,
                                        desc=self.stage2_description):
             valid = False
 
diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py
index 9396426..9ffaa14 100644
--- a/pyanaconda/installclass.py
+++ b/pyanaconda/installclass.py
@@ -29,6 +29,7 @@ import imputil
 from blivet.partspec import PartSpec
 from blivet.devicelibs import swap
 from blivet.platform import platform
+from blivet.size import Size
 
 import logging
 log = logging.getLogger("anaconda")
@@ -80,10 +81,14 @@ class BaseInstallClass(object):
 
     def setDefaultPartitioning(self, storage):
         autorequests = [PartSpec(mountpoint="/", fstype=storage.defaultFSType,
-                                 size=1024, maxSize=50*1024, grow=True,
+                                 size=Size(spec="1GiB"),
+                                 maxSize=Size(spec="50GiB"),
+                                 grow=True,
                                  btr=True, lv=True, thin=True, encrypted=True),
-                        PartSpec(mountpoint="/home", fstype=storage.defaultFSType,
-                                 size=500, grow=True, requiredSpace=50*1024,
+                        PartSpec(mountpoint="/home",
+                                 fstype=storage.defaultFSType,
+                                 size=Size(spec="500MiB"), grow=True,
+                                 requiredSpace=Size(spec="50GiB"),
                                  btr=True, lv=True, thin=True, encrypted=True)]
 
         bootreqs = platform.setDefaultPartitioning()
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index c5c73cb..c409ee5 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -26,6 +26,7 @@ from blivet.devicelibs import swap as swap_lib
 from blivet.formats import getFormat
 from blivet.partitioning import doPartitioning
 from blivet.partitioning import growLVM
+from blivet.size import Size
 from blivet import udev
 import blivet.iscsi
 import blivet.fcoe
@@ -755,12 +756,17 @@ class LogVolData(commands.logvol.F20_LogVolData):
         # we might have truncated or otherwise changed the specified vg name
         vgname = ksdata.onPart.get(self.vgname, self.vgname)
 
+        try:
+            size = Size(spec="%d MiB" % self.size)
+        except Exception:
+            raise KickstartValueError(formatErrorMsg(self.lineno, msg="The size %s is not valid." % self.size))
+
         if self.mountpoint == "swap":
             ty = "swap"
             self.mountpoint = ""
             if self.recommended or self.hibernation:
                 disk_space = getAvailableDiskSpace(storage)
-                self.size = swap_lib.swapSuggestion(hibernation=self.hibernation, disk_space=disk_space)
+                size = swap_lib.swapSuggestion(hibernation=self.hibernation, disk_space=disk_space)
                 self.grow = False
         else:
             if self.fstype != "":
@@ -802,19 +808,19 @@ class LogVolData(commands.logvol.F20_LogVolData):
                 raise KickstartValueError(formatErrorMsg(self.lineno, msg="No preexisting logical volume with the name \"%s\" was found." % self.name))
 
             if self.resize:
-                if self.size < dev.currentSize:
+                if size < dev.currentSize:
                     # shrink
                     try:
-                        devicetree.registerAction(ActionResizeFormat(dev, self.size))
-                        devicetree.registerAction(ActionResizeDevice(dev, self.size))
+                        devicetree.registerAction(ActionResizeFormat(dev, size))
+                        devicetree.registerAction(ActionResizeDevice(dev, size))
                     except ValueError:
                         raise KickstartValueError(formatErrorMsg(self.lineno,
                                 msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
                 else:
                     # grow
                     try:
-                        devicetree.registerAction(ActionResizeDevice(dev, self.size))
-                        devicetree.registerAction(ActionResizeFormat(dev, self.size))
+                        devicetree.registerAction(ActionResizeDevice(dev, size))
+                        devicetree.registerAction(ActionResizeFormat(dev, size))
                     except ValueError:
                         raise KickstartValueError(formatErrorMsg(self.lineno,
                                 msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
@@ -831,9 +837,9 @@ class LogVolData(commands.logvol.F20_LogVolData):
 
             # Size specification checks
             if not self.percent:
-                if not self.size:
+                if not size:
                     raise KickstartValueError(formatErrorMsg(self.lineno, msg="Size required"))
-                elif not self.grow and self.size*1024 < vg.peSize:
+                elif not self.grow and size < vg.peSize:
                     raise KickstartValueError(formatErrorMsg(self.lineno, msg="Logical volume size must be larger than the volume group physical extent size."))
             elif self.percent <= 0 or self.percent > 100:
                 raise KickstartValueError(formatErrorMsg(self.lineno, msg="Percentage must be between 0 and 100"))
@@ -860,7 +866,7 @@ class LogVolData(commands.logvol.F20_LogVolData):
 
             if self.resize:
                 try:
-                    devicetree.registerAction(ActionResizeDevice(device, self.size))
+                    devicetree.registerAction(ActionResizeDevice(device, size))
                 except ValueError:
                     raise KickstartValueError(formatErrorMsg(self.lineno,
                             msg="Invalid target size (%d) for device %s" % (self.size, device.name)))
@@ -884,19 +890,27 @@ class LogVolData(commands.logvol.F20_LogVolData):
                 parents = [vg]
 
             if self.thin_pool:
-                pool_args = { "metadatasize": self.metadata_size,
-                              "chunksize": self.chunk_size / 1024.0 }
+                pool_args = { "metadatasize": Size(spec="%d MiB" % self.metadata_size),
+                              "chunksize": Size(spec="%d KiB" % self.chunk_size) }
             else:
                 pool_args = {}
 
+            if self.maxSizeMB:
+                try:
+                    maxsize = Size(spec="%d MiB" % self.maxSizeMB)
+                except Exception:
+                    raise KickstartValueError(formatErrorMsg(self.lineno, msg="The maximum size %s is not valid." % self.maxSizeMB))
+            else:
+                maxsize = None
+
             request = storage.newLV(format=fmt,
                                     name=self.name,
                                     parents=parents,
-                                    size=self.size,
+                                    size=size,
                                     thin_pool=self.thin_pool,
                                     thin_volume=self.thin_volume,
                                     grow=self.grow,
-                                    maxsize=self.maxSizeMB,
+                                    maxsize=maxsize,
                                     percent=self.percent,
                                     **pool_args)
 
@@ -972,6 +986,11 @@ class PartitionData(commands.partition.F18_PartData):
 
         storage.doAutoPart = False
 
+        try:
+            size = Size(spec="%d MiB" % self.size)
+        except Exception:
+            raise KickstartValueError(formatErrorMsg(self.lineno, msg="The size %s is not valid." % self.size))
+
         if self.onbiosdisk != "":
             for (disk, biosdisk) in storage.eddDict.iteritems():
                 if "%x" % biosdisk == self.onbiosdisk:
@@ -986,7 +1005,7 @@ class PartitionData(commands.partition.F18_PartData):
             self.mountpoint = ""
             if self.recommended or self.hibernation:
                 disk_space = getAvailableDiskSpace(storage)
-                self.size = swap_lib.swapSuggestion(hibernation=self.hibernation, disk_space=disk_space)
+                size = swap_lib.swapSuggestion(hibernation=self.hibernation, disk_space=disk_space)
                 self.grow = False
         # if people want to specify no mountpoint for some reason, let them
         # this is really needed for pSeries boot partitions :(
@@ -1060,19 +1079,19 @@ class PartitionData(commands.partition.F18_PartData):
                 raise KickstartValueError(formatErrorMsg(self.lineno, msg="No preexisting partition with the name \"%s\" was found." % self.onPart))
 
             if self.resize:
-                if self.size < dev.currentSize:
+                if size < dev.currentSize:
                     # shrink
                     try:
-                        devicetree.registerAction(ActionResizeFormat(dev, self.size))
-                        devicetree.registerAction(ActionResizeDevice(dev, self.size))
+                        devicetree.registerAction(ActionResizeFormat(dev, size))
+                        devicetree.registerAction(ActionResizeDevice(dev, size))
                     except ValueError:
                         raise KickstartValueError(formatErrorMsg(self.lineno,
                                 msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
                 else:
                     # grow
                     try:
-                        devicetree.registerAction(ActionResizeDevice(dev, self.size))
-                        devicetree.registerAction(ActionResizeFormat(dev, self.size))
+                        devicetree.registerAction(ActionResizeDevice(dev, size))
+                        devicetree.registerAction(ActionResizeFormat(dev, size))
                     except ValueError:
                         raise KickstartValueError(formatErrorMsg(self.lineno,
                                 msg="Invalid target size (%d) for device %s" % (self.size, dev.name)))
@@ -1087,7 +1106,7 @@ class PartitionData(commands.partition.F18_PartData):
                                      label=self.label,
                                      fsprofile=self.fsprofile,
                                      mountopts=self.fsopts,
-                                     size=self.size)
+                                     size=size)
         if not kwargs["format"].type:
             raise KickstartValueError(formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % ty))
 
@@ -1117,8 +1136,17 @@ class PartitionData(commands.partition.F18_PartData):
                 raise KickstartValueError(formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in partition command" % self.disk))
 
         kwargs["grow"] = self.grow
-        kwargs["size"] = self.size
-        kwargs["maxsize"] = self.maxSizeMB
+        kwargs["size"] = size
+        if self.maxSizeMB:
+            try:
+                maxsize = Size(spec="%d MiB" % self.maxSizeMB)
+            except Exception:
+                raise KickstartValueError(formatErrorMsg(self.lineno, msg="The maximum size %s is not valid." % self.maxSizeMB))
+        else:
+            maxsize = None
+
+        kwargs["maxsize"] = maxsize
+
         kwargs["primary"] = self.primOnly
 
         # If we were given a pre-existing partition to create a filesystem on,
@@ -1133,7 +1161,7 @@ class PartitionData(commands.partition.F18_PartData):
             removeExistingFormat(device, storage)
             if self.resize:
                 try:
-                    devicetree.registerAction(ActionResizeDevice(device, self.size))
+                    devicetree.registerAction(ActionResizeDevice(device, size))
                 except ValueError:
                     raise KickstartValueError(formatErrorMsg(self.lineno,
                             msg="Invalid target size (%d) for device %s" % (self.size, device.name)))
@@ -1511,7 +1539,8 @@ class VolGroupData(commands.volgroup.RHEL7_VolGroupData):
             # default PE size requested -- we use blivet's default in KiB
             self.pesize = LVM_PE_SIZE * 1024
 
-        if self.pesize not in getPossiblePhysicalExtents(floor=1024):
+        pesize = Size(spec="%d KiB" % self.pesize)
+        if pesize not in getPossiblePhysicalExtents(floor=1024):
             raise KickstartValueError(formatErrorMsg(self.lineno, msg="Volume group specified invalid pesize"))
 
         # If --noformat or --useexisting was given, there's really nothing to do.
@@ -1527,7 +1556,7 @@ class VolGroupData(commands.volgroup.RHEL7_VolGroupData):
         else:
             request = storage.newVG(parents=pvs,
                                     name=self.vgname,
-                                    peSize=self.pesize/1024.0)
+                                    peSize=pesize)
 
             storage.createDevice(request)
             if self.reserved_space:
diff --git a/pyanaconda/ui/gui/spokes/custom.glade b/pyanaconda/ui/gui/spokes/custom.glade
index a40b3c4..d02f227 100644
--- a/pyanaconda/ui/gui/spokes/custom.glade
+++ b/pyanaconda/ui/gui/spokes/custom.glade
@@ -1000,7 +1000,7 @@ use.  Try something else?</property>
                                     <property name="can_focus">True</property>
                                     <property name="halign">start</property>
                                     <property name="invisible_char">●</property>
-                                    <property name="width_chars">10</property>
+                                    <property name="width_chars">20</property>
                                     <signal name="changed" handler="on_value_changed" swapped="no"/>
                                   </object>
                                   <packing>
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index eaaa420..916bc7a 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -65,6 +65,7 @@ from blivet.errors import NotEnoughFreeSpaceError
 from blivet.errors import SanityError
 from blivet.errors import SanityWarning
 from blivet.errors import LUKSDeviceWithoutKeyError
+from blivet.errors import SizeParamsError
 from blivet.devicelibs import mdraid
 from blivet.devices import LUKSDevice
 
@@ -80,7 +81,6 @@ from pyanaconda.ui.gui.spokes.lib.summary import ActionSummaryDialog
 from pyanaconda.ui.gui.utils import setViewportBackground, gtk_action_wait, enlightbox, fancy_set_sensitive, ignoreEscape,\
         really_hide, really_show, escape_markup
 from pyanaconda.ui.gui.categories.system import SystemCategory
-from pyanaconda.ui.lib.disks import size_str
 from pyanaconda.kickstart import refreshAutoSwapSize
 
 
@@ -183,13 +183,18 @@ def size_from_entry(entry):
         size_text += "MB"
 
     try:
-        size = Size(spec=size_text)
-    except ValueError:
+        # if no unit was specified, default to MiB. Assume that a string
+        # ending with any kind of a letter has a unit suffix.
+        if size_text and unicodedata.category(size_text[-1]).startswith("L"):
+            size = Size(spec=size_text)
+        else:
+            size = Size(en_spec="%sMiB" % size_text)
+    except (SizeParamsError, ValueError):
         return None
     else:
-        # Minimium size for ui-created partitions is 1MB.
-        if size.convertTo(spec="mb") < 1:
-            size = Size(spec="1mb")
+        # Minimium size for ui-created partitions is 1MiB.
+        if size.convertTo(en_spec="MiB") < 1:
+            size = Size(en_spec="1 MiB")
 
     return size
 
@@ -375,7 +380,7 @@ class DisksDialog(GUIObject):
         # populate the store
         for disk in self._disks:
             self._store.append([disk.description,
-                                str(Size(spec="%dMB" % disk.size)),
+                                str(disk.size),
                                 str(free[disk.name][0]),
                                 disk.serial,
                                 disk.id])
@@ -437,7 +442,7 @@ class ContainerDialog(GUIObject):
         self.exists = kwargs.pop("exists", False)
 
         self.size_policy = kwargs.pop("size_policy", SIZE_POLICY_AUTO)
-        self.size = kwargs.pop("size", 0)
+        self.size = kwargs.pop("size", Size(bytes=0))
 
         self._error = None
         GUIObject.__init__(self, *args, **kwargs)
@@ -461,7 +466,7 @@ class ContainerDialog(GUIObject):
         # populate the store
         for disk in self._disks:
             self._store.append([disk.description,
-                                str(Size(spec="%dMB" % disk.size)),
+                                str(disk.size),
                                 str(free[disk.name][0]),
                                 disk.serial,
                                 disk.id])
@@ -493,8 +498,7 @@ class ContainerDialog(GUIObject):
         self.sizeCombo = self.builder.get_object("containerSizeCombo")
         self.sizeEntry = self.builder.get_object("containerSizeEntry")
         self.sizeLabel = self.builder.get_object("containerSizeLabel")
-        size = Size(spec="%d mb" % self.size)
-        self.sizeEntry.set_text(size.humanReadable(max_places=None))
+        self.sizeEntry.set_text(self.size.humanReadable(max_places=None))
         if self.size_policy == SIZE_POLICY_AUTO:
             self.sizeCombo.set_active(0)
         elif self.size_policy == SIZE_POLICY_MAX:
@@ -548,9 +552,7 @@ class ContainerDialog(GUIObject):
             size = SIZE_POLICY_MAX
         elif idx == 2:
             size = size_from_entry(self.sizeEntry)
-            if size:
-                size = int(size.convertTo(spec="MB"))
-            elif size is None:
+            if size is None:
                 size = SIZE_POLICY_MAX
 
         # now save the changes
@@ -828,12 +830,9 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
     def _currentTotalSpace(self):
         """Add up the sizes of all selected disks and return it as a Size."""
-        totalSpace = 0
-
-        for disk in self._clearpartDevices:
-            totalSpace += disk.size
-
-        return Size(spec="%f MB" % totalSpace)
+        totalSpace = sum(disk.size for disk in self._clearpartDevices,
+                         Size(bytes=0))
+        return totalSpace
 
     def _updateSpaceDisplay(self):
         # Set up the free space/available space displays in the bottom left.
@@ -1166,10 +1165,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # SIZE
         old_size = device.size
         size = size_from_entry(self._sizeEntry)
-        if size:
-            size = int(size.convertTo(spec="MB"))
         changed_size = ((use_dev.resizable or not use_dev.exists) and
-                        size != int(old_size))
+                        size != old_size)
         log.debug("old size: %s", old_size)
         log.debug("new size: %s", size)
 
@@ -1491,7 +1488,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             # And then we need to re-check that the max size is actually
             # different from the current size.
             _changed_size = False
-            if size != device.size and int(size) == int(device.currentSize):
+            if size != device.size and size == device.currentSize:
                 # size has been set back to its original value
                 actions = self.__storage.devicetree.findActions(type="resize",
                                                                 devid=device.id)
@@ -1500,7 +1497,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
                         self.__storage.devicetree.cancelAction(action)
                         _changed_size = True
             elif size != device.size:
-                log.debug("scheduling resize of device %s to %s MB", device.name, size)
+                log.debug("scheduling resize of device %s to %s", device.name, size)
 
                 with ui_storage_logger():
                     try:
@@ -1522,7 +1519,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
                 # update the selector's size property
                 for s in self._accordion.allSelectors:
                     if s._device == device:
-                        s.size = size_str(device.size)
+                        s.size = str(device.size)
 
                 # update size props of all btrfs devices' selectors
                 self._update_selectors()
@@ -1768,7 +1765,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             self._labelEntry.set_text("")
         fancy_set_sensitive(self._labelEntry, True)
 
-        self._sizeEntry.set_text(Size(spec="%d MB" % device.size).humanReadable(max_places=None))
+        self._sizeEntry.set_text(device.size.humanReadable(max_places=None))
 
         self._reformatCheckbox.set_active(not device.format.exists)
         fancy_set_sensitive(self._reformatCheckbox, not device.protected and
@@ -2117,9 +2114,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             encrypted = False
 
         disks = self._clearpartDevices
-        if size is not None:
-            size = float(size.convertTo(spec="mb"))
-
         self.clear_errors()
 
         with ui_storage_logger():
@@ -2475,7 +2469,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
     def _container_store_row(self, name, freeSpace=None):
         if freeSpace is not None:
-            return [name, _("(%s free)") % size_str(freeSpace)]
+            return [name, _("(%s free)") % freeSpace]
         else:
             return [name, ""]
 
diff --git a/pyanaconda/ui/gui/spokes/filter.py b/pyanaconda/ui/gui/spokes/filter.py
index 97b07ca..ef692c5 100644
--- a/pyanaconda/ui/gui/spokes/filter.py
+++ b/pyanaconda/ui/gui/spokes/filter.py
@@ -32,7 +32,7 @@ from blivet.udev import udev_get_device, udev_device_get_wwid
 from pyanaconda.flags import flags
 from pyanaconda.i18n import N_, P_
 
-from pyanaconda.ui.lib.disks import getDisks, size_str
+from pyanaconda.ui.lib.disks import getDisks
 from pyanaconda.ui.gui.utils import enlightbox, escape_markup
 from pyanaconda.ui.gui.spokes import NormalSpoke
 from pyanaconda.ui.gui.spokes.advstorage.fcoe import FCoEDialog
@@ -255,7 +255,7 @@ class MultipathPage(FilterPage):
             selected = disk.name in selectedNames
 
             store.append([True, selected, not disk.protected,
-                          disk.name, "", disk.model, size_str(disk.size),
+                          disk.name, "", disk.model, str(disk.size),
                           disk.vendor, disk.bus, disk.serial,
                           disk.wwid, "\n".join(paths), "", "",
                           "", "", ""])
@@ -335,7 +335,7 @@ class OtherPage(FilterPage):
                 lun = ""
 
             store.append([True, selected, not disk.protected,
-                          disk.name, "", disk.model, size_str(disk.size),
+                          disk.name, "", disk.model, str(disk.size),
                           disk.vendor, disk.bus, disk.serial,
                           self._long_identifier(disk), "", port, getattr(disk, "initiator", ""),
                           lun, "", ""])
@@ -448,7 +448,7 @@ class ZPage(FilterPage):
 
                     # now add it to our store
                     store.append([True, selected, not disk.protected,
-                                  disk.name, "", disk.model, size_str(disk.size),
+                                  disk.name, "", disk.model, disk.size,
                                   disk.vendor, disk.bus, disk.serial, "", "\n".join(paths),
                                   "", "", disk.fcp_lun, disk.hba_id, disk.wwpn])
 
diff --git a/pyanaconda/ui/gui/spokes/lib/accordion.py b/pyanaconda/ui/gui/spokes/lib/accordion.py
index feed953..a340ee5 100644
--- a/pyanaconda/ui/gui/spokes/lib/accordion.py
+++ b/pyanaconda/ui/gui/spokes/lib/accordion.py
@@ -20,7 +20,6 @@
 # Red Hat Author(s): Chris Lumens <clumens@redhat.com>
 #
 
-from blivet.size import Size
 
 from pyanaconda.i18n import _
 from pyanaconda.product import productName, productVersion
@@ -59,14 +58,12 @@ def selectorFromDevice(device, selector=None, mountpoint=""):
     else:
         mp = _("Unknown")
 
-    size = Size(spec="%f MB" % device.size)
-
     if not selector:
-        selector = MountpointSelector(device.name, str(size).upper(), mp)
+        selector = MountpointSelector(device.name, str(device.size), mp)
         selector._root = None
     else:
         selector.props.name = device.name
-        selector.props.size = str(size).upper()
+        selector.props.size = str(device.size)
         selector.props.mountpoint = mp
 
     selector._device = device
diff --git a/pyanaconda/ui/gui/spokes/lib/cart.py b/pyanaconda/ui/gui/spokes/lib/cart.py
index ab1b2ff..b62c0eb 100644
--- a/pyanaconda/ui/gui/spokes/lib/cart.py
+++ b/pyanaconda/ui/gui/spokes/lib/cart.py
@@ -23,7 +23,6 @@
 from gi.repository import Gtk
 
 from pyanaconda.i18n import _, P_
-from pyanaconda.ui.lib.disks import size_str
 from pyanaconda.ui.gui import GUIObject
 from pyanaconda.ui.gui.utils import escape_markup
 from blivet.size import Size
@@ -60,8 +59,8 @@ class SelectedDisksDialog(GUIObject):
         for disk in disks:
             self._store.append([False,
                                 "%s (%s)" % (disk.description, disk.serial),
-                                size_str(disk.size),
-                                size_str(free[disk.name][0]),
+                                str(disk.size),
+                                str(free[disk.name][0]),
                                 disk.name,
                                 disk.id])
         self.disks = disks[:]
diff --git a/pyanaconda/ui/gui/spokes/lib/resize.py b/pyanaconda/ui/gui/spokes/lib/resize.py
index 832e776..da722cb 100644
--- a/pyanaconda/ui/gui/spokes/lib/resize.py
+++ b/pyanaconda/ui/gui/spokes/lib/resize.py
@@ -26,7 +26,6 @@ from math import ceil
 from gi.repository import Gdk, Gtk
 
 from pyanaconda.i18n import _, N_, P_
-from pyanaconda.ui.lib.disks import size_str
 from pyanaconda.ui.gui import GUIObject
 from pyanaconda.ui.gui.utils import escape_markup
 from blivet.size import Size
@@ -67,8 +66,8 @@ class ResizeDialog(GUIObject):
         self.storage = storage
         self.payload = payload
 
-        self._initialFreeSpace = Size(0)
-        self._selectedReclaimableSpace = 0
+        self._initialFreeSpace = Size(bytes=0)
+        self._selectedReclaimableSpace = Size(bytes=0)
 
         self._actionStore = self.builder.get_object("actionStore")
         self._diskStore = self.builder.get_object("diskStore")
@@ -82,7 +81,7 @@ class ResizeDialog(GUIObject):
 
         self._required_label = self.builder.get_object("requiredSpaceLabel")
         markup = self._required_label.get_label()
-        self._required_label.set_markup(markup % size_str(self.payload.spaceRequired))
+        self._required_label.set_markup(markup % escape_markup(str(self.payload.spaceRequired)))
 
         self._reclaimDescLabel = self.builder.get_object("reclaimDescLabel")
 
@@ -119,10 +118,10 @@ class ResizeDialog(GUIObject):
 
     def populate(self, disks):
         totalDisks = 0
-        totalReclaimableSpace = 0
+        totalReclaimableSpace = Size(bytes=0)
 
-        self._initialFreeSpace = Size(0)
-        self._selectedReclaimableSpace = 0
+        self._initialFreeSpace = Size(bytes=0)
+        self._selectedReclaimableSpace = Size(bytes=0)
 
         canShrinkSomething = False
 
@@ -134,13 +133,13 @@ class ResizeDialog(GUIObject):
 
             if disk.partitioned:
                 fstype = ""
-                diskReclaimableSpace = 0
+                diskReclaimableSpace = Size(bytes=0)
             else:
                 fstype = disk.format.type
                 diskReclaimableSpace = disk.size
 
             itr = self._diskStore.append(None, [disk.id,
-                                                "%s %s" % (size_str(int(disk.size)), disk.description),
+                                                "%s %s" % (disk.size.humanReadable(max_places=1), disk.description),
                                                 fstype,
                                                 "<span foreground='grey' style='italic'>%s total</span>",
                                                 _(PRESERVE),
@@ -160,7 +159,7 @@ class ResizeDialog(GUIObject):
                     if dev.resizable:
                         freeSize = dev.size - dev.minSize
                         resizeString = _("%(freeSize)s of %(devSize)s") \
-                                       % {"freeSize": size_str(int(freeSize)), "devSize": size_str(int(dev.size))}
+                                       % {"freeSize": freeSize.humanReadable(max_places=1), "devSize": dev.size.humanReadable(max_places=1)}
                         if not dev.protected:
                             canShrinkSomething = True
                     else:
@@ -187,23 +186,22 @@ class ResizeDialog(GUIObject):
             # And then add another uneditable line that lists how much space is
             # already free in the disk.
             diskFree = free_space[disk.name][0]
-            converted = diskFree.convertTo(spec="mb")
-            if int(converted):
+            if diskFree >= Size(en_spec="1MiB"):
                 self._diskStore.append(itr, [disk.id,
                                              _("""<span foreground='grey' style='italic'>Free space</span>"""),
                                              "",
-                                             "<span foreground='grey' style='italic'>%s</span>" % escape_markup(size_str(diskFree)),
+                                             "<span foreground='grey' style='italic'>%s</span>" % escape_markup(diskFree.humanReadable(max_places=1)),
                                              NOTHING,
                                              False,
                                              TY_FREE_SPACE,
                                              self._get_tooltip(disk),
-                                             int(converted),
+                                             diskFree,
                                              ""])
                 self._initialFreeSpace += diskFree
 
             # And then go back and fill in the total reclaimable space for the
             # disk, now that we know what each partition has reclaimable.
-            self._diskStore[itr][RECLAIMABLE_COL] = self._diskStore[itr][RECLAIMABLE_COL] % size_str(int(diskReclaimableSpace))
+            self._diskStore[itr][RECLAIMABLE_COL] = self._diskStore[itr][RECLAIMABLE_COL] % diskReclaimableSpace
 
             totalDisks += 1
             totalReclaimableSpace += diskReclaimableSpace
@@ -229,11 +227,11 @@ class ResizeDialog(GUIObject):
             text = P_("<b>%(count)s disk; %(size)s reclaimable space</b> (in file systems)",
                       "<b>%(count)s disks; %(size)s reclaimable space</b> (in file systems)",
                       escape_markup(nDisks)) % {"count": escape_markup(nDisks),
-                                                "size": escape_markup(size_str(totalReclaimable))}
+                                                "size": escape_markup(totalReclaimable)}
             self._reclaimable_label.set_markup(text)
 
         if selectedReclaimable is not None:
-            text = _("Total selected space to reclaim: <b>%s</b>") % escape_markup(size_str(selectedReclaimable))
+            text = _("Total selected space to reclaim: <b>%s</b>") % escape_markup(selectedReclaimable)
             self._selected_label.set_markup(text)
 
     def _setup_slider(self, device, value):
@@ -248,10 +246,10 @@ class ResizeDialog(GUIObject):
 
         # The slider needs to be keyboard-accessible.  We'll make small movements change in
         # 1% increments, and large movements in 5% increments.
-        distance = int(device.size) - int(ceil(device.minSize))
-        onePercent = distance*0.01
-        fivePercent = distance*0.05
-        twentyPercent = distance*0.2
+        distance = device.size - device.minSize
+        onePercent = Size(bytes=distance / 100)
+        fivePercent = Size(bytes=distance / 20)
+        twentyPercent = Size(bytes=distance / 5)
 
         adjustment = self.builder.get_object("resizeAdjustment")
         adjustment.configure(value, int(ceil(device.minSize)), int(device.size), onePercent, fivePercent, 0)
@@ -262,8 +260,8 @@ class ResizeDialog(GUIObject):
             self._resizeSlider.add_mark(int(ceil(device.minSize)) + i*twentyPercent, Gtk.PositionType.BOTTOM, None)
 
         # Finally, add tick marks for the ends.
-        self._resizeSlider.add_mark(int(ceil(device.minSize)), Gtk.PositionType.BOTTOM, size_str(int(ceil(device.minSize))))
-        self._resizeSlider.add_mark(int(device.size), Gtk.PositionType.BOTTOM, size_str(int(device.size)))
+        self._resizeSlider.add_mark(device.minSize, Gtk.PositionType.BOTTOM, str(device.minSize))
+        self._resizeSlider.add_mark(device.size, Gtk.PositionType.BOTTOM, str(device.size))
 
     def _update_action_buttons(self, row):
         obj = PartStoreRow(*row)
@@ -284,7 +282,7 @@ class ResizeDialog(GUIObject):
         self._shrinkButton.set_sensitive(device.resizable)
 
         if device.resizable:
-            self._setup_slider(device, obj.target)
+            self._setup_slider(device, Size(bytes=obj.target))
 
         # Then, disable the button for whatever action is currently selected.
         # It doesn't make a lot of sense to allow clicking that.
@@ -334,7 +332,7 @@ class ResizeDialog(GUIObject):
         if obj.action == _(PRESERVE):
             return False
         if obj.action == _(SHRINK):
-            self._selectedReclaimableSpace += int(device.size) - obj.target
+            self._selectedReclaimableSpace += device.size - Size(bytes=obj.target)
         elif obj.action == _(DELETE):
             self._selectedReclaimableSpace += int(device.size)
 
@@ -388,11 +386,11 @@ class ResizeDialog(GUIObject):
 
         # And then we're keeping a running tally of how much space the user
         # has selected to reclaim, so reflect that in the UI.
-        self._selectedReclaimableSpace = 0
+        self._selectedReclaimableSpace = Size(bytes=0)
         self._diskStore.foreach(self._sumReclaimableSpace, None)
         self._update_labels(selectedReclaimable=self._selectedReclaimableSpace)
 
-        self._update_reclaim_button(Size(spec="%f MB" % self._selectedReclaimableSpace))
+        self._update_reclaim_button(self._selectedReclaimableSpace)
         self._update_action_buttons(selectedRow)
 
     def _recursiveRemove(self, device):
@@ -422,7 +420,8 @@ class ResizeDialog(GUIObject):
             return False
         elif obj.action == _(SHRINK):
             if device.resizable:
-                self.storage.resizeDevice(device, obj.target)
+                # round this up to nearest MB? MiB? Maybe in targetSize setter.
+                self.storage.resizeDevice(device, Size(bytes=obj.target))
             else:
                 self._recursiveRemove(device)
         elif obj.action == _(DELETE):
@@ -478,21 +477,21 @@ class ResizeDialog(GUIObject):
     def on_resize_value_changed(self, rng):
         (model, itr) = self._selection.get_selected()
 
-        old_delta = int(rng.get_adjustment().get_upper())- model[itr][RESIZE_TARGET_COL]
+        old_delta = Size(bytes=rng.get_adjustment().get_upper()) - int(model[itr][RESIZE_TARGET_COL])
         self._selectedReclaimableSpace -= old_delta
 
         # Update the target size in the store.
-        model[itr][RESIZE_TARGET_COL] = int(rng.get_value())
+        model[itr][RESIZE_TARGET_COL] = Size(bytes=rng.get_value())
 
         # Update the "Total selected space" label.
-        delta = int(rng.get_adjustment().get_upper()) - int(rng.get_value())
+        delta = Size(bytes=rng.get_adjustment().get_upper()) - int(rng.get_value())
         self._selectedReclaimableSpace += delta
         self._update_labels(selectedReclaimable=self._selectedReclaimableSpace)
 
         # And then the reclaim button, in case they've made enough space.
-        self._update_reclaim_button(Size(spec="%f MB" % self._selectedReclaimableSpace))
+        self._update_reclaim_button(self._selectedReclaimableSpace)
 
     def resize_slider_format(self, scale, value):
         # This makes the value displayed under the slider prettier than just a
         # single number.
-        return size_str(value)
+        return str(Size(bytes=value))
diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py
index 40bdf26..07be194 100644
--- a/pyanaconda/ui/gui/spokes/storage.py
+++ b/pyanaconda/ui/gui/spokes/storage.py
@@ -42,7 +42,7 @@
 from gi.repository import Gdk, GLib, AnacondaWidgets
 
 from pyanaconda.ui.communication import hubQ
-from pyanaconda.ui.lib.disks import getDisks, isLocalDisk, size_str, applyDiskSelection
+from pyanaconda.ui.lib.disks import getDisks, isLocalDisk, applyDiskSelection
 from pyanaconda.ui.gui import GUIObject
 from pyanaconda.ui.gui.spokes import NormalSpoke
 from pyanaconda.ui.gui.spokes.lib.cart import SelectedDisksDialog
@@ -590,7 +590,6 @@ class StorageSpoke(NormalSpoke, StorageChecker):
         else:
             kind = "drive-harddisk"
 
-        size = size_str(disk.size)
         if disk.serial:
             popup_info = "%s" % disk.serial
         else:
@@ -608,8 +607,8 @@ class StorageSpoke(NormalSpoke, StorageChecker):
 
         overview = AnacondaWidgets.DiskOverview(description,
                                                 kind,
-                                                size,
-                                                _("%s free") % size_str(free),
+                                                str(disk.size),
+                                                _("%s free") % free,
                                                 disk.name,
                                                 popup=popup_info)
         box.pack_start(overview, False, False, 0)
@@ -642,7 +641,7 @@ class StorageSpoke(NormalSpoke, StorageChecker):
     def _update_summary(self):
         """ Update the summary based on the UI. """
         count = 0
-        capacity = 0
+        capacity = Size(bytes=0)
         free = Size(bytes=0)
 
         # pass in our disk list so hidden disks' free space is available
@@ -657,7 +656,7 @@ class StorageSpoke(NormalSpoke, StorageChecker):
         summary = (P_("%(count)d disk selected; %(capacity)s capacity; %(free)s free",
                       "%(count)d disks selected; %(capacity)s capacity; %(free)s free",
                       count) % {"count": count,
-                                "capacity": str(Size(spec="%f MB" % capacity)),
+                                "capacity": capacity,
                                 "free": free})
         summary_label = self.builder.get_object("summary_label")
         summary_label.set_text(summary)
@@ -798,7 +797,7 @@ class StorageSpoke(NormalSpoke, StorageChecker):
 
         # show the installation options dialog
         disks = [d for d in self.disks if d.name in self.selected_disks]
-        disks_size = sum(Size(spec="%f MB" % d.size) for d in disks)
+        disks_size = sum(d.size for d in disks, Size(bytes=0))
 
         # No disks selected?  The user wants to back out of the storage spoke.
         if not disks:
@@ -848,10 +847,8 @@ class StorageSpoke(NormalSpoke, StorageChecker):
             fs_free = sum(f[1] for f in free_space.itervalues())
 
         required_space = self.payload.spaceRequired
-        auto_swap = Size(bytes=0)
-        for autoreq in self.storage.autoPartitionRequests:
-            if autoreq.fstype == "swap":
-                auto_swap += Size(spec="%d MB" % autoreq.size)
+        auto_swap = sum(r.size for r in self.storage.autoPartitionRequests
+                                if r.fstype == "swap", Size(bytes=0))
 
         log.debug("disk free: %s  fs free: %s  sw needs: %s  auto swap: %s",
                   disk_free, fs_free, required_space, auto_swap)
diff --git a/pyanaconda/ui/lib/disks.py b/pyanaconda/ui/lib/disks.py
index d5d7e12..2e86d70 100644
--- a/pyanaconda/ui/lib/disks.py
+++ b/pyanaconda/ui/lib/disks.py
@@ -25,7 +25,7 @@ from blivet.size import Size
 
 from pyanaconda.flags import flags
 
-__all__ = ["FakeDiskLabel", "FakeDisk", "getDisks", "isLocalDisk", "size_str"]
+__all__ = ["FakeDiskLabel", "FakeDisk", "getDisks", "isLocalDisk"]
 
 class FakeDiskLabel(object):
     def __init__(self, free=0):
@@ -85,14 +85,6 @@ def isLocalDisk(disk):
             and not isinstance(disk, iScsiDiskDevice)
             and not isinstance(disk, FcoeDiskDevice))
 
-def size_str(mb):
-    if isinstance(mb, Size):
-        spec = str(mb)
-    else:
-        spec = "%f mb" % mb
-
-    return str(Size(spec=spec)).upper()
-
 def applyDiskSelection(storage, data, use_names):
     onlyuse = use_names[:]
     for disk in (d for d in storage.disks if d.name in onlyuse):
diff --git a/pyanaconda/ui/tui/spokes/storage.py b/pyanaconda/ui/tui/spokes/storage.py
index 0e3b9fa..3835821 100644
--- a/pyanaconda/ui/tui/spokes/storage.py
+++ b/pyanaconda/ui/tui/spokes/storage.py
@@ -22,7 +22,7 @@
 # which has the same license and authored by David Lehman <dlehman@redhat.com>
 #
 
-from pyanaconda.ui.lib.disks import getDisks, size_str, applyDiskSelection
+from pyanaconda.ui.lib.disks import getDisks, applyDiskSelection
 from pyanaconda.ui.tui.spokes import NormalTUISpoke
 from pyanaconda.ui.tui.simpleline import TextWidget, CheckboxWidget
 from pyanaconda.ui.tui.tuiobject import YesNoDialog
@@ -215,7 +215,7 @@ class StorageSpoke(NormalTUISpoke):
             CheckboxWidget, pre-format the display string right here.
         """
         # show this info for all disks
-        format_str = "%s: %s (%s)" % (disk.model, size_str(disk.size), disk.name)
+        format_str = "%s: %s (%s)" % (disk.model, disk.size, disk.name)
 
         disk_attrs = []
         # now check for/add info about special disks
-- 
1.9.3

