From e888d8785cec82b654a5ccb1dfe8471ef37a9039 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Thu, 19 Sep 2013 15:36:30 -0500
Subject: [PATCH 5/7] Convert everything to use Size.

---
 blivet/devicefactory.py     | 27 ++++++-------
 blivet/devicelibs/lvm.py    | 79 ++++++++++++++++++++++---------------
 blivet/devicelibs/swap.py   | 24 +++++++-----
 blivet/devices.py           | 95 ++++++++++++++++++++++-----------------------
 blivet/formats/disklabel.py |  9 +++--
 blivet/formats/fs.py        | 66 +++++++++++++++----------------
 blivet/formats/swap.py      |  3 +-
 blivet/partitioning.py      | 86 ++++++++++++++++++++--------------------
 blivet/platform.py          | 26 +++++++------
 blivet/size.py              | 21 +++++++---
 blivet/udev.py              | 17 +++-----
 blivet/util.py              | 20 +++++-----
 examples/factory.py         | 13 ++++---
 examples/partitioning.py    | 22 ++++++-----
 14 files changed, 271 insertions(+), 237 deletions(-)

diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
index 627819f..5e43525 100644
--- a/blivet/devicefactory.py
+++ b/blivet/devicefactory.py
@@ -34,6 +34,7 @@ from devicelibs.lvm import LVM_PE_SIZE
 from .partitioning import SameSizeSet
 from .partitioning import TotalSizeSet
 from .partitioning import doPartitioning
+from .size import Size
 
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
@@ -790,7 +791,7 @@ class PartitionFactory(DeviceFactory):
             self.__fmt = getattr(self, "__fmt", getFormat(self.fstype))
             min_format_size = self.__fmt.minSize
 
-        return max(1, min_format_size)
+        return max(Size(spec="1MB"), min_format_size)
 
     def _get_device_size(self):
         """ Return the factory device size including container limitations. """
@@ -799,7 +800,7 @@ class PartitionFactory(DeviceFactory):
     def _set_device_size(self):
         """ Set the size of a defined factory device. """
         if self.device and self.size != self.raw_device.size:
-            log.info("adjusting device size from %.2f to %.2f"
+            log.info("adjusting device size from %s to %s"
                             % (self.raw_device.size, self.size))
 
             base_size = self._get_base_size()
@@ -928,7 +929,7 @@ class PartitionSetFactory(PartitionFactory):
             add_disks = self.disks
 
         # drop any new disks that don't have free space
-        min_free = min(500, self.parent_factory.size)
+        min_free = min(Size(spec="500MB"), self.parent_factory.size)
         add_disks = [d for d in add_disks if d.partitioned and
                                              d.format.free >= min_free]
 
@@ -1038,7 +1039,7 @@ class PartitionSetFactory(PartitionFactory):
         ##
         ## Set up SizeSet to manage growth of member partitions.
         ##
-        log.debug("adding a %s with size %d"
+        log.debug("adding a %s with size %s"
                   % (self.parent_factory.size_set_class.__name__, total_space))
         size_set = self.parent_factory.size_set_class(members, total_space)
         self.storage.size_sets.append(size_set)
@@ -1106,7 +1107,7 @@ class LVMFactory(DeviceFactory):
             free += self.raw_device.size
 
         if free < size:
-            log.info("adjusting size from %.2f to %.2f so it fits "
+            log.info("adjusting size from %s to %s so it fits "
                      "in container %s" % (size, free, self.container.name))
             size = free
 
@@ -1115,7 +1116,7 @@ class LVMFactory(DeviceFactory):
     def _set_device_size(self):
         size = self._get_device_size()
         if self.device and size != self.raw_device.size:
-            log.info("adjusting device size from %.2f to %.2f"
+            log.info("adjusting device size from %s to %s"
                             % (self.raw_device.size, size))
             self.raw_device.size = size
             self.raw_device.req_grow = False
@@ -1135,10 +1136,10 @@ class LVMFactory(DeviceFactory):
             # grow the container as large as possible
             if self.container:
                 size += sum(p.size for p in self.container.parents)
-                log.debug("size bumped to %d to include container parents" % size)
+                log.debug("size bumped to %s to include container parents" % size)
 
             size += self._get_free_disk_space()
-            log.debug("size bumped to %d to include free disk space" % size)
+            log.debug("size bumped to %s to include free disk space" % size)
         else:
             # container_size is a request for a fixed size for the container
             size += get_pv_space(self.container_size, len(self.disks))
@@ -1146,7 +1147,7 @@ class LVMFactory(DeviceFactory):
         # this does not apply if a specific container size was requested
         if self.container_size in [SIZE_POLICY_AUTO, SIZE_POLICY_MAX]:
             size += self._get_device_space()
-            log.debug("size bumped to %d to include new device space" % size)
+            log.debug("size bumped to %s to include new device space" % size)
             if self.device and self.container_size == SIZE_POLICY_AUTO:
                 # The member count here uses the container's current member set
                 # since that's the basis for the current device's disk space
@@ -1154,7 +1155,7 @@ class LVMFactory(DeviceFactory):
                 size -= get_pv_space(self.device.size,
                                      len(self.container.parents),
                                      **self.size_func_kwargs)
-                log.debug("size cut to %d to omit old device space" % size)
+                log.debug("size cut to %s to omit old device space" % size)
 
         if self.container_raid_level:
             # add five extents per disk to account for md metadata
@@ -1314,7 +1315,7 @@ class LVMThinPFactory(LVMFactory):
 
         size = self.size
         if free < size:
-            log.info("adjusting size from %.2f to %.2f so it fits "
+            log.info("adjusting size from %s to %s so it fits "
                      "in pool %s" % (size, free, self.pool.name))
             size = free
 
@@ -1366,7 +1367,7 @@ class LVMThinPFactory(LVMFactory):
                                        pesize=self._pesize)
                 log.debug("old device size: %s ; old pad: %s" % (self.device.size, pad))
                 size -= pad
-                log.debug("size cut to %d to omit old device padding" % size)
+                log.debug("size cut to %s to omit old device padding" % size)
 
         return size
 
@@ -1439,7 +1440,7 @@ class LVMThinPFactory(LVMFactory):
         if free < (size + pad):
             pad = int(get_pool_padding(free, pesize=self._pesize, reverse=True))
             free = self.container.align(free - pad) # round down
-            log.info("adjusting pool size from %.2f to %.2f so it fits "
+            log.info("adjusting pool size from %s to %s so it fits "
                      "in container %s" % (size, free, self.container.name))
             size = free
 
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index 5bc189c..668b9ff 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -27,6 +27,7 @@ import re
 import logging
 log = logging.getLogger("blivet")
 
+from ..size import Size
 from .. import util
 from .. import arch
 from ..errors import *
@@ -37,14 +38,14 @@ _ = lambda x: gettext.ldgettext("blivet", x)
 MAX_LV_SLOTS = 256
 
 # some of lvm's defaults that we have no way to ask it for
-LVM_PE_START = 1.0      # MB
-LVM_PE_SIZE = 4.0       # MB
+LVM_PE_START = Size(spec="1 MiB")
+LVM_PE_SIZE = Size(spec="4 MiB")
 
 # thinp constants
-LVM_THINP_MIN_METADATA_SIZE = 2             # 2 MiB
-LVM_THINP_MAX_METADATA_SIZE = 16384         # 16 GiB
-LVM_THINP_MIN_CHUNK_SIZE = 0.0625           # 64 KiB
-LVM_THINP_MAX_CHUNK_SIZE = 1024             # 1 GiB
+LVM_THINP_MIN_METADATA_SIZE = Size(spec="2 MiB")
+LVM_THINP_MAX_METADATA_SIZE = Size(spec="16 GiB")
+LVM_THINP_MIN_CHUNK_SIZE = Size(spec="64 KiB")
+LVM_THINP_MAX_CHUNK_SIZE = Size(spec="1 GiB")
 
 def has_lvm():
     if util.find_program_in_path("lvm"):
@@ -118,16 +119,16 @@ def blacklistVG(name):
     global lvm_vg_blacklist
     lvm_vg_blacklist.append(name)
 
-def getPossiblePhysicalExtents(floor=0):
-    """Returns a list of integers representing the possible values for
-       the physical extent of a volume group.  Value is in KB.
+def getPossiblePhysicalExtents():
+    """ Returns a list of possible values for physical extent of a volume group.
 
-       floor - size (in KB) of smallest PE we care about.
+        :returns: list of possible extent sizes (:class:`~.size.Size`)
+        :rtype: list
     """
 
     possiblePE = []
-    curpe = 8
-    while curpe <= 16384*1024:
+    curpe = Size(spec="1 KiB")
+    while curpe <= Size(spec="16 GiB"):
 	if curpe >= floor:
 	    possiblePE.append(curpe)
 	curpe = curpe * 2
@@ -137,17 +138,21 @@ def getPossiblePhysicalExtents(floor=0):
 def getMaxLVSize():
     """ Return the maximum size (in MB) of a logical volume. """
     if arch.getArch() in ("x86_64", "ppc64", "alpha", "ia64", "s390"): #64bit architectures
-        return (8*1024*1024*1024*1024) #Max is 8EiB (very large number..)
+        return Size(spec="8 EiB")
     else:
-        return (16*1024*1024) #Max is 16TiB
+        return Size(spec="16 TiB")
 
 def clampSize(size, pesize, roundup=None):
+    delta = size % pesize
+    if not delta:
+        return size
+
     if roundup:
-        round = math.ceil
+        clamped = size + (pesize - delta)
     else:
-        round = math.floor
+        clamped = size - delta
 
-    return long(round(float(size)/float(pesize)) * pesize)
+    return clamped
 
 def get_pv_space(size, disks, pesize=LVM_PE_SIZE,
                  striped=False, mirrored=False):
@@ -158,8 +163,7 @@ def get_pv_space(size, disks, pesize=LVM_PE_SIZE,
     if size == 0:
         return size
 
-    space = clampSize(size, pesize, roundup=True) + \
-            pesize
+    space = clampSize(size, pesize, roundup=True) + pesize
     return space
 
 def get_pool_padding(size, pesize=LVM_PE_SIZE, reverse=False):
@@ -179,15 +183,26 @@ def get_pool_padding(size, pesize=LVM_PE_SIZE, reverse=False):
     return pad
 
 def is_valid_thin_pool_metadata_size(size):
-    """ Return True if size (in MiB) is a valid thin pool metadata vol size. """
+    """ Return True if size is a valid thin pool metadata vol size.
+
+        :param size: metadata vol size to validate
+        :type size: :class:`~.size.Size`
+        :returns: whether the size is valid
+        :rtype: bool
+    """
     return (LVM_THINP_MIN_METADATA_SIZE <= size <= LVM_THINP_MAX_METADATA_SIZE)
 
 # To support discard, chunk size must be a power of two. Otherwise it must be a
 # multiple of 64 KiB.
 def is_valid_thin_pool_chunk_size(size, discard=False):
-    """ Return True if size (in MiB) is a valid thin pool chunk size.
-
-        discard (boolean) indicates whether discard support is required
+    """ Return True if size is a valid thin pool chunk size.
+
+        :param size: chunk size to validate
+        :type size: :class:`~.size.Size`
+        :keyword discard: whether discard support is required (default: False)
+        :type discard: bool
+        :returns: whether the size is valid
+        :rtype: bool
     """
     if not LVM_THINP_MIN_CHUNK_SIZE <= size <= LVM_THINP_MAX_CHUNK_SIZE:
         return False
@@ -217,7 +232,7 @@ def pvcreate(device):
 
 def pvresize(device, size):
     args = ["pvresize"] + \
-            ["--setphysicalvolumesize", ("%dm" % size)] + \
+            ["--setphysicalvolumesize", ("%dm" % size.convertTo("mb"))] + \
             _getConfigArgs() + \
             [device]
 
@@ -282,7 +297,7 @@ def pvinfo(device):
 def vgcreate(vg_name, pv_list, pe_size):
     argv = ["vgcreate"]
     if pe_size:
-        argv.extend(["-s", "%dm" % pe_size])
+        argv.extend(["-s", "%dm" % pe_size.convertTo("mb")])
     argv.extend(_getConfigArgs())
     argv.append(vg_name)
     argv.extend(pv_list)
@@ -396,7 +411,7 @@ def lvorigin(vg_name, lv_name):
 
 def lvcreate(vg_name, lv_name, size, pvs=[]):
     args = ["lvcreate"] + \
-            ["-L", "%dm" % size] + \
+            ["-L", "%dm" % size.convertTo("mb")] + \
             ["-n", lv_name] + \
             _getConfigArgs() + \
             [vg_name] + pvs
@@ -418,7 +433,7 @@ def lvremove(vg_name, lv_name):
 
 def lvresize(vg_name, lv_name, size):
     args = ["lvresize"] + \
-            ["--force", "-L", "%dm" % size] + \
+            ["--force", "-L", "%dm" % size.convertTo("mb")] + \
             _getConfigArgs() + \
             ["%s/%s" % (vg_name, lv_name)]
 
@@ -450,15 +465,15 @@ def lvdeactivate(vg_name, lv_name):
 
 def thinpoolcreate(vg_name, lv_name, size, metadatasize=None, chunksize=None):
     args = ["lvcreate", "--thinpool", "%s/%s" % (vg_name, lv_name),
-            "--size", "%dm" % size]
+            "--size", "%dm" % size.convertTo("mb")]
 
     if metadatasize:
-        # default unit is MiB
-        args += ["--poolmetadatasize", "%d" % metadatasize]
+        # default unit is MB
+        args += ["--poolmetadatasize", "%d" % metadatasize.convertTo("mb")]
 
     if chunksize:
-        # default unit is KiB
-        args += ["--chunksize", "%d" % (chunksize * 1024,)]
+        # default unit is KB
+        args += ["--chunksize", "%d" % chunksize.convertTo("kb")]
 
     args += _getConfigArgs()
 
diff --git a/blivet/devicelibs/swap.py b/blivet/devicelibs/swap.py
index fc3f940..1794081 100644
--- a/blivet/devicelibs/swap.py
+++ b/blivet/devicelibs/swap.py
@@ -26,6 +26,7 @@ import os
 from ..errors import *
 from .. import util
 from . import dm
+from ..size import Size
 
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
@@ -126,32 +127,37 @@ def swapSuggestion(quiet=False, hibernation=False):
 
     """
 
-    mem = util.total_memory()/1024
+    mem = util.total_memory()
     mem = ((mem/16)+1)*16
     if not quiet:
-        log.info("Detected %sM of memory", mem)
+        log.info("Detected %s of memory", mem)
+
+    two_GiB = Size(spec="2GiB")
+    four_GiB = Size(spec="4GiB")
+    eight_GiB = Size(spec="8GiB")
+    sixtyfour_GiB = Size(spec="64 GiB")
 
     #chart suggested in the discussion with other teams
-    if mem < 2048:
+    if mem < two_GiB:
         swap = 2 * mem
 
-    elif 2048 <= mem < 8192:
+    elif two_GiB <= mem < eight_GiB:
         swap = mem
 
-    elif 8192 <= mem < 65536:
+    elif eight_GiB <= mem < sixtyfour_GiB:
         swap = mem / 2
 
     else:
-        swap = 4096
+        swap = four_GiB
 
     if hibernation:
-        if mem <= 65536:
+        if mem <= sixtyfour_GiB:
             swap = mem + swap
         else:
-            log.info("Ignoring --hibernation option on systems with 64 GB of RAM or more")
+            log.info("Ignoring --hibernation option on systems with %s of RAM or more", sixtyfour_GiB)
 
     if not quiet:
-        log.info("Swap attempt of %sM", swap)
+        log.info("Swap attempt of %s", swap)
 
     return swap
 
diff --git a/blivet/devices.py b/blivet/devices.py
index 6650086..58a3cb1 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -117,6 +117,7 @@ from flags import flags
 from storage_log import log_method_call
 from udev import *
 from formats import get_device_format_class, getFormat, DeviceFormat
+from size import Size
 
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
@@ -505,13 +506,13 @@ class StorageDevice(Device):
                 sector_size = read_int_from_sys("%s/queue/logical_block_size"
                                                 % device_root)
                 size = read_int_from_sys("%s/size" % device_root)
-                self._size = (size * sector_size) / (1024.0 * 1024.0)
+                self._size = Size(bytes=(size * sector_size))
 
     def __str__(self):
         exist = "existing"
         if not self.exists:
             exist = "non-existent"
-        s = "%s %dMB %s" % (exist, self.size, super(StorageDevice, self).__str__())
+        s = "%s %s %s" % (exist, self.size, super(StorageDevice, self).__str__())
         if self.format.type:
             s += " with %s" % self.format
 
@@ -857,20 +858,20 @@ class StorageDevice(Device):
     def _setSize(self, newsize):
         """ Set the device's size to a new value. """
         if newsize > self.maxSize:
-            raise DeviceError("device cannot be larger than %s MB" %
+            raise DeviceError("device cannot be larger than %s" %
                               (self.maxSize,), self.name)
         self._size = newsize
 
     size = property(lambda x: x._getSize(),
                     lambda x, y: x._setSize(y),
-                    doc="The device's size in MB, accounting for pending changes")
+                    doc="The device's size, accounting for pending changes")
 
     @property
     def currentSize(self):
         """ The device's actual size. """
         size = 0
         if self.exists and self.partedDevice:
-            size = self.partedDevice.getSize()
+            size = Size(bytes=self.partedDevice.getSize(unit="B"))
         elif self.exists:
             size = self._size
         return size
@@ -1065,7 +1066,7 @@ class DiskDevice(StorageDevice):
 
     @property
     def size(self):
-        """ The disk's size in MB """
+        """ The disk's size """
         return super(DiskDevice, self).size
     #size = property(StorageDevice._getSize)
 
@@ -1090,7 +1091,7 @@ class PartitionDevice(StorageDevice):
     """
     _type = "partition"
     _resizable = True
-    defaultSize = 500
+    defaultSize = Size(spec="500MB")
 
     def __init__(self, name, format=None,
                  size=None, grow=False, maxsize=None, start=None, end=None,
@@ -1119,7 +1120,7 @@ class PartitionDevice(StorageDevice):
 
                     partType -- primary,extended,&c (as parted constant)
                     grow -- whether or not to grow the partition
-                    maxsize -- max size for growable partitions (in MB)
+                    maxsize -- max size for growable partitions
                     size -- the device's size (in MB)
                     start -- start sector (see note, below)
                     end -- end sector (see note, below)
@@ -1483,8 +1484,7 @@ class PartitionDevice(StorageDevice):
         if not self.exists:
             return
 
-        # this is in MB
-        self._size = self.partedPartition.getSize()
+        self._size = Size(bytes=self.partedPartition.getSize(unit="B"))
         self._currentSize = self._size
         self.targetSize = self._size
 
@@ -1502,7 +1502,7 @@ class PartitionDevice(StorageDevice):
         device = self.partedPartition.geometry.device.path
 
         # Erase 1MiB or to end of partition
-        count = 1 * 1024 * 1024 / bs
+        count = Size(spec="1 MiB") / bs
         count = min(count, part_len)
 
         cmd = ["dd", "if=/dev/zero", "of=%s" % device, "bs=%s" % bs,
@@ -1567,7 +1567,7 @@ class PartitionDevice(StorageDevice):
         # compute new size for partition
         currentGeom = partition.geometry
         currentDev = currentGeom.device
-        newLen = long(self.targetSize * 1024 * 1024) / currentDev.sectorSize
+        newLen = self.targetSize / currentDev.sectorSize
         newGeometry = parted.Geometry(device=currentDev,
                                       start=currentGeom.start,
                                       length=newLen)
@@ -1648,8 +1648,7 @@ class PartitionDevice(StorageDevice):
         """ Get the device's size. """
         size = self._size
         if self.partedPartition:
-            # this defaults to MB
-            size = self.partedPartition.getSize()
+            size = Size(bytes=self.partedPartition.getSize(unit="B"))
         return size
 
     def _setSize(self, newsize):
@@ -1668,8 +1667,7 @@ class PartitionDevice(StorageDevice):
         if newsize > self.disk.size:
             raise ValueError("partition size would exceed disk size")
 
-        # this defaults to MB
-        maxAvailableSize = self.partedPartition.getMaxAvailableSize()
+        maxAvailableSize = Size(bytes=self.partedPartition.getMaxAvailableSize(unit="B"))
 
         if newsize > maxAvailableSize:
             raise ValueError("new size is greater than available space")
@@ -1678,7 +1676,7 @@ class PartitionDevice(StorageDevice):
         geometry = self.partedPartition.geometry
         physicalSectorSize = geometry.device.physicalSectorSize
 
-        new_length = (newsize * (1024 * 1024)) / physicalSectorSize
+        new_length = newsize / physicalSectorSize
         geometry.length = new_length
 
     def _getDisk(self):
@@ -1768,7 +1766,7 @@ class PartitionDevice(StorageDevice):
             data.size = int(self.req_base_size)
             data.grow = self.req_grow
             if self.req_grow:
-                data.maxSizeMB = self.req_max_size
+                data.maxSizeMB = int(self.req_max_size.convertTo(spec="mb"))
 
             ##data.disk = self.disk.name                      # by-id
             if self.req_disks and len(self.req_disks) == 1:
@@ -2109,7 +2107,7 @@ class LVMVolumeGroupDevice(DMDevice):
 
         # TODO: validate peSize if given
         if not self.peSize:
-            self.peSize = lvm.LVM_PE_SIZE  # MB
+            self.peSize = lvm.LVM_PE_SIZE
 
         if not self.exists:
             self.pvCount = len(self.parents)
@@ -2309,7 +2307,7 @@ class LVMVolumeGroupDevice(DMDevice):
            lv.size > self.freeSpace:
             raise DeviceError("new lv is too large to fit in free space", self.name)
 
-        log.debug("Adding %s/%dMB to %s" % (lv.name, lv.size, self.name))
+        log.debug("Adding %s/%s to %s" % (lv.name, lv.size, self.name))
         self._lvs.append(lv)
 
     def _removeLogVol(self, lv):
@@ -2389,7 +2387,7 @@ class LVMVolumeGroupDevice(DMDevice):
         if self.reserved_percent > 0:
             reserved = self.reserved_percent * 0.01 * self.size
         elif self.reserved_space > 0:
-            reserved = self.reserved_space
+            reserved = self.reserved_space  # XXX FIXME
 
         return self.align(reserved, roundup=True)
 
@@ -2410,7 +2408,7 @@ class LVMVolumeGroupDevice(DMDevice):
         """ Number of extents in this VG """
         # TODO: just ask lvm if isModified returns False
 
-        return self.size / self.peSize
+        return int(self.size / self.peSize)
 
     @property
     def freeSpace(self):
@@ -2418,32 +2416,24 @@ class LVMVolumeGroupDevice(DMDevice):
         # TODO: just ask lvm if isModified returns False
 
         # total the sizes of any LVs
-        log.debug("%s size is %dMB" % (self.name, self.size))
+        log.debug("%s size is %s" % (self.name, self.size))
         used = sum(lv.vgSpaceUsed for lv in self.lvs) + self.snapshotSpace
         used += self.reservedSpace
         free = self.size - used
-        log.debug("vg %s has %dMB free" % (self.name, free))
+        log.debug("vg %s has %s free" % (self.name, free))
         return free
 
     @property
     def freeExtents(self):
         """ The number of free extents in this VG. """
         # TODO: just ask lvm if isModified returns False
-        return self.freeSpace / self.peSize
+        return int(self.freeSpace / self.peSize)
 
     def align(self, size, roundup=None):
         """ Align a size to a multiple of physical extent size. """
         size = util.numeric_type(size)
 
-        if roundup:
-            round = math.ceil
-        else:
-            round = math.floor
-
-        # we want Kbytes as a float for our math
-        size *= 1024.0
-        pesize = self.peSize * 1024.0
-        return long((round(size / pesize) * pesize) / 1024)
+        return lvm.clampSize(size, self.peSize, roundup=roundup)
 
     @property
     def pvs(self):
@@ -2480,7 +2470,7 @@ class LVMVolumeGroupDevice(DMDevice):
         data.physvols = ["pv.%d" % p.id for p in self.parents]
         data.preexist = self.exists
         if not self.exists:
-            data.pesize = self.peSize * 1024
+            data.pesize = self.peSize.convertTo(spec="kb")
 
         # reserved percent/space
 
@@ -2538,7 +2528,7 @@ class LVMLogicalVolumeDevice(DMDevice):
         self.singlePVerr = ("%(mountpoint)s is restricted to a single "
                             "physical volume on this platform.  No physical "
                             "volumes available in volume group %(vgname)s "
-                            "with %(size)d MB of available space." %
+                            "with %(size)s of available space." %
                            {'mountpoint': getattr(self.format, "mountpoint",
                                                   "A proposed logical volume"),
                             'vgname': self.vg.name,
@@ -2581,8 +2571,8 @@ class LVMLogicalVolumeDevice(DMDevice):
         s += ("  VG device = %(vgdev)r\n"
               "  segment type = %(type)s percent = %(percent)s\n"
               "  mirror copies = %(copies)d"
-              "  snapshot total =  %(snapshots)dMB\n"
-              "  VG space used = %(vgspace)dMB" %
+              "  snapshot total =  %(snapshots)s\n"
+              "  VG space used = %(vgspace)s" %
               {"vgdev": self.vg, "percent": self.req_percent,
                "copies": self.copies, "type": self.segType,
                "snapshots": self.snapshotSpace, "vgspace": self.vgSpaceUsed })
@@ -2606,12 +2596,12 @@ class LVMLogicalVolumeDevice(DMDevice):
 
     def _setSize(self, size):
         size = self.vg.align(util.numeric_type(size))
-        log.debug("trying to set lv %s size to %dMB" % (self.name, size))
+        log.debug("trying to set lv %s size to %s" % (self.name, size))
         if size <= self.vg.freeSpace + self.vgSpaceUsed:
             self._size = size
             self.targetSize = size
         else:
-            log.debug("failed to set size: %dMB short" % (size - (self.vg.freeSpace + self.vgSpaceUsed),))
+            log.debug("failed to set size: %s short" % (size - (self.vg.freeSpace + self.vgSpaceUsed),))
             raise ValueError("not enough free space in volume group")
 
     size = property(StorageDevice._getSize, _setSize)
@@ -2772,7 +2762,7 @@ class LVMLogicalVolumeDevice(DMDevice):
             data.grow = self.req_grow
             if self.req_grow:
                 data.size = int(self.req_size)
-                data.maxSizeMB = self.req_max_size
+                data.maxSizeMB = self.req_max_size.convertTo(spec="mb")
             else:
                 data.size = int(self.size)
 
@@ -2839,7 +2829,7 @@ class LVMThinPoolDevice(LVMLogicalVolumeDevice):
 
         # TODO: add some checking to prevent overcommit for preexisting
         self.vg._addLogVol(lv)
-        log.debug("Adding %s/%dMB to %s" % (lv.name, lv.size, self.name))
+        log.debug("Adding %s/%s to %s" % (lv.name, lv.size, self.name))
         self._lvs.append(lv)
 
     def _removeLogVol(self, lv):
@@ -2983,7 +2973,7 @@ class MDRaidArrayDevice(StorageDevice):
         self._totalDevices = util.numeric_type(totalDevices)
         self._memberDevices = util.numeric_type(memberDevices)
 
-        self.chunkSize = 512.0 / 1024.0         # chunk size in MB
+        self.chunkSize = Size(spec="512 KiB")
 
         if not self.exists and not isinstance(metadataVersion, str):
             self.metadataVersion = "default"
@@ -3106,7 +3096,7 @@ class MDRaidArrayDevice(StorageDevice):
                 size -= size % self.chunkSize
             log.debug("non-existent RAID %s size == %s" % (self.level, size))
         else:
-            size = self.partedDevice.getSize()
+            size = Size(bytes=self.partedDevice.getSize(unit="B"))
             log.debug("existing RAID %s size == %s" % (self.level, size))
 
         return size
@@ -3832,9 +3822,18 @@ class FileDevice(StorageDevice):
         """ Create the device. """
         log_method_call(self, self.name, status=self.status)
         fd = os.open(self.path, os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
-        buf = "\0" * 1024 * 1024
-        for n in range(self.size):
-            os.write(fd, buf)
+        # all this fuss is so we write the zeros 1MiB at a time
+        zero = "\0"
+        MiB = Size(spec="1 MiB")
+        count = int(self.size.convertTo(spec="MiB"))
+        rem = self.size % MiB
+        for n in range(count):
+            os.write(fd, buf * MiB)
+
+        if rem:
+            # write out however many more zeros it takes to hit our size target
+            os.write(fd, buf * rem)
+
         os.close(fd)
 
     def _destroy(self):
@@ -3851,7 +3850,7 @@ class SparseFileDevice(FileDevice):
         """Create a sparse file."""
         log_method_call(self, self.name, status=self.status)
         fd = os.open(self.path, os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
-        os.ftruncate(fd, 1024*1024*self.size)
+        os.ftruncate(fd, self.size)
         os.close(fd)
 
 
diff --git a/blivet/formats/disklabel.py b/blivet/formats/disklabel.py
index 90b73f3..0650162 100644
--- a/blivet/formats/disklabel.py
+++ b/blivet/formats/disklabel.py
@@ -31,6 +31,7 @@ from .. import arch
 from ..flags import flags
 from ..udev import udev_settle
 from . import DeviceFormat, register_device_format
+from ..size import Size
 
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
@@ -222,7 +223,7 @@ class DiskLabel(DeviceFormat):
         size = self._size
         if not size:
             try:
-                size = self.partedDevice.getSize(unit="MB")
+                size = Size(bytes=self.partedDevice.getSize(unit="B"))
             except Exception:
                 size = 0
 
@@ -413,8 +414,8 @@ class DiskLabel(DeviceFormat):
             return int(open(path).readline().strip())
 
         try:
-            free = sum([f.getSize()
-                        for f in self.partedDisk.getFreeSpacePartitions()])
+            free = sum(Size(bytes=f.getSize(unit="B"))
+                        for f in self.partedDisk.getFreeSpacePartitions())
         except Exception:
             sys_block_root = "/sys/class/block/"
 
@@ -431,7 +432,7 @@ class DiskLabel(DeviceFormat):
                 partition_length = read_int_from_sys("%s/size" % partition_root)
                 used_sectors += partition_length
 
-            free = ((disk_length - used_sectors) * sector_size) / (1024.0 * 1024.0)
+            free = Size(bytes=((disk_length - used_sectors) * sector_size))
 
         return free
 
diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py
index 13896c9..e24791b 100644
--- a/blivet/formats/fs.py
+++ b/blivet/formats/fs.py
@@ -39,6 +39,7 @@ from .. import platform
 from ..flags import flags
 from parted import fileSystemType
 from ..storage_log import log_method_call
+from ..size import Size
 
 import logging
 log = logging.getLogger("blivet")
@@ -51,10 +52,6 @@ try:
 except OSError:
     lost_and_found_context = None
 
-# these are for converting to/from SI for ntfsresize
-mb = 1000 * 1000.0
-mib = 1024 * 1024.0
-
 fs_configs = {}
 
 kernel_filesystems = []
@@ -155,7 +152,7 @@ class FS(DeviceFormat):
         self.fsprofile = kwargs.get("fsprofile")
 
         # filesystem size does not necessarily equal device size
-        self._size = kwargs.get("size", 0)
+        self._size = kwargs.get("size", Size(bytes=0))
         self._minInstanceSize = None    # min size of this FS instance
         self._mountpoint = None     # the current mountpoint when mounted
 
@@ -320,8 +317,7 @@ class FS(DeviceFormat):
                 for value in values:
                     size *= value
 
-                # report current size as megabytes
-                size = math.floor(size / 1024.0 / 1024.0)
+                size = Size(bytes=size)
             except Exception as e:
                 log.error("failed to obtain size of filesystem on %s: %s"
                           % (self.device, e))
@@ -331,10 +327,10 @@ class FS(DeviceFormat):
     @property
     def currentSize(self):
         """ The filesystem's current actual size. """
-        size = 0
+        size = Size(bytes=0)
         if self.exists:
             size = self._size
-        return float(size)
+        return size
 
     @property
     def free(self):
@@ -406,7 +402,7 @@ class FS(DeviceFormat):
 
     @property
     def resizeArgs(self):
-        argv = [self.device, "%d" % (self.targetSize,)]
+        argv = [self.device, "%d" % (self.targetSize.convertTo("mb"),)]
         return argv
 
     def doResize(self, *args, **kwargs):
@@ -832,8 +828,8 @@ class Ext2FS(FS):
     _supported = True
     _resizable = True
     _linuxNative = True
-    _maxSize = 8 * 1024 * 1024
-    _minSize = 0
+    _maxSize = Size(spec="8 GiB")
+    _minSize = Size(bytes=0)
     _defaultFormatOptions = []
     _defaultMountOptions = ["defaults"]
     _defaultCheckOptions = ["-f", "-p", "-C", "0"]
@@ -917,7 +913,7 @@ class Ext2FS(FS):
                 # to bytes, then megabytes, and finally round up.
                 (text, sep, minSize) = line.partition(": ")
                 size = long(minSize) * blockSize
-                size = math.ceil(size / 1024.0 / 1024.0)
+                size = Size(bytes=size)
                 break
 
             if size is None:
@@ -943,7 +939,7 @@ class Ext2FS(FS):
 
     @property
     def resizeArgs(self):
-        argv = ["-p", self.device, "%dM" % (self.targetSize,)]
+        argv = ["-p", self.device, "%dM" % (self.targetSize.convertTo("MiB"))]
         return argv
 
 register_device_format(Ext2FS)
@@ -960,7 +956,7 @@ class Ext3FS(Ext2FS):
     # smaller than the default of 4096 bytes and therefore to make liars of us
     # with regard to this maximum filesystem size, but if they're doing such
     # things they should know the implications of their chosen block size.
-    _maxSize = 16 * 1024 * 1024
+    _maxSize = Size(spec="16 TiB")
 
     @property
     def needsFSCheck(self):
@@ -991,7 +987,7 @@ class FATFS(FS):
                    2: _("Usage error.")}
     _supported = True
     _formattable = True
-    _maxSize = 1024 * 1024
+    _maxSize = Size(spec="1 TiB")
     _packages = [ "dosfstools" ]
     _defaultMountOptions = ["umask=0077", "shortname=winnt"]
     # FIXME this should be fat32 in some cases
@@ -1013,7 +1009,7 @@ class EFIFS(FATFS):
     _mountType = "vfat"
     _modules = ["vfat"]
     _name = "EFI System Partition"
-    _minSize = 50
+    _minSize = Size(spec="50 MB")
 
     @property
     def supported(self):
@@ -1036,8 +1032,8 @@ class BTRFS(FS):
     _dump = True
     _check = True
     _packages = ["btrfs-progs"]
-    _minSize = 256
-    _maxSize = 16 * 1024 * 1024
+    _minSize = Size(spec="256 MB")
+    _maxSize = Size(spec="16 TiB")
     # FIXME parted needs to be taught about btrfs so that we can set the
     # partition table type correctly for btrfs partitions
     # partedSystem = fileSystemType["btrfs"]
@@ -1119,7 +1115,7 @@ class JFS(FS):
     _defaultFormatOptions = ["-q"]
     _defaultLabelOptions = ["-L"]
     _maxLabelChars = 16
-    _maxSize = 8 * 1024 * 1024
+    _maxSize = Size(spec="8 TiB")
     _formattable = True
     _linuxNative = True
     _supported = False
@@ -1152,7 +1148,7 @@ class ReiserFS(FS):
     _defaultFormatOptions = ["-f", "-f"]
     _defaultLabelOptions = ["-l"]
     _maxLabelChars = 16
-    _maxSize = 16 * 1024 * 1024
+    _maxSize = Size(spec="16 TiB")
     _formattable = True
     _linuxNative = True
     _supported = False
@@ -1190,7 +1186,7 @@ class XFS(FS):
     _defaultFormatOptions = ["-f"]
     _defaultLabelOptions = ["-L"]
     _maxLabelChars = 16
-    _maxSize = 100 * 1024 * 1024
+    _maxSize = Size(spec="100 TiB")
     _formattable = True
     _linuxNative = True
     _supported = True
@@ -1245,8 +1241,8 @@ class AppleBootstrapFS(HFS):
     _type = "appleboot"
     _mountType = "hfs"
     _name = "Apple Bootstrap"
-    _minSize = 800.00 / 1024.00
-    _maxSize = 1
+    _minSize = Size(spec="768 KiB")
+    _maxSize = Size(spec="1MiB")
 
     @property
     def supported(self):
@@ -1265,8 +1261,8 @@ class HFSPlus(FS):
     _packages = ["hfsplus-tools"]
     _formattable = True
     _mountType = "hfsplus"
-    _minSize = 1
-    _maxSize = 2 * 1024 * 1024
+    _minSize = Size(spec="1 MiB")
+    _maxSize = Size(spec="2 TiB")
     _check = True
     partedSystem = fileSystemType["hfs+"]
 
@@ -1279,8 +1275,8 @@ class NTFS(FS):
     _resizefs = "ntfsresize"
     _fsck = "ntfsresize"
     _resizable = True
-    _minSize = 1
-    _maxSize = 16 * 1024 * 1024
+    _minSize = Size(spec="1 MiB")
+    _maxSize = Size(spec="16 TiB")
     _defaultMountOptions = ["defaults", "ro"]
     _defaultCheckOptions = ["-c"]
     _packages = ["ntfsprogs"]
@@ -1299,7 +1295,12 @@ class NTFS(FS):
         return False
 
     def _getMinSize(self, info=None):
-        # try to determine the minimum size.
+        """ Set the minimum size for this filesystem.
+
+            :keyword info: filesystem info buffer
+            :type info: str (output of :attr:`infofsProg`)
+            :rtype: None.
+        """
         size = self._minSize
         if self.exists and os.path.exists(self.device) and \
            util.find_program_in_path(self.resizefsProg):
@@ -1309,8 +1310,7 @@ class NTFS(FS):
                 if not l.startswith("Minsize"):
                     continue
                 try:
-                    minSize = int(l.split(":")[1].strip())  # MB
-                    minSize *= (mb / mib)                   # MiB
+                    minSize = Size(spec="%d mb" % l.split(":")[1].strip())
                 except (IndexError, ValueError) as e:
                     minSize = None
                     log.warning("Unable to parse output for minimum size on %s: %s" %(self.device, e))
@@ -1341,8 +1341,8 @@ class NTFS(FS):
         # converting back and forth between MiB and MB means the filesystem is
         # getting resized to be slightly larger than the partition holding it.
         # This hack just makes the filesystem fit.
-        targetSize = (mib / mb) * (self.targetSize-1) # convert MiB to MB
-        argv = ["-ff", "-s", "%dM" % (targetSize,), self.device]
+        argv = ["-ff", "-s", "%dM" % self.targetSize.convertTo(spec="mb"),
+                self.device]
         return argv
 
 
diff --git a/blivet/formats/swap.py b/blivet/formats/swap.py
index da8f4df..3f903be 100644
--- a/blivet/formats/swap.py
+++ b/blivet/formats/swap.py
@@ -26,6 +26,7 @@ from ..errors import *
 from ..util import numeric_type
 from ..devicelibs import swap
 from . import DeviceFormat, register_device_format
+from ..size import Size
 
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
@@ -46,7 +47,7 @@ class SwapSpace(DeviceFormat):
     _linuxNative = True                # for clearpart
 
     #see rhbz#744129 for details
-    _maxSize = 128 * 1024
+    _maxSize = Size(spec="128 GiB")
 
     def __init__(self, *args, **kwargs):
         """ Create a SwapSpace instance.
diff --git a/blivet/partitioning.py b/blivet/partitioning.py
index abaf1b3..891049b 100644
--- a/blivet/partitioning.py
+++ b/blivet/partitioning.py
@@ -33,6 +33,7 @@ from flags import flags
 from devices import PartitionDevice, LUKSDevice, devicePathToName
 from formats import getFormat
 from devicelibs.lvm import get_pool_padding
+from size import Size
 
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
@@ -54,7 +55,7 @@ def _getCandidateDisks(storage):
                 part = part.nextPartition()
                 continue
 
-            if part.getSize(unit="MB") > PartitionDevice.defaultSize:
+            if part.getSize(unit="B") > PartitionDevice.defaultSize:
                 disks.append(disk)
                 break
 
@@ -105,9 +106,9 @@ def _schedulePartitions(storage, disks):
         log.error("no free space on disks %s" % ([d.name for d in disks],))
         return
 
-    free = all_free[0].getSize()
+    free = Size(bytes=all_free[0].getSize(unit="B"))
     if len(all_free) > 1:
-        free += all_free[1].getSize()
+        free += all_free[1].getSize(unit="B")
 
     # The boot disk must be set at this point. See if any platform-specific
     # stage1 device we might allocate already exists on the boot disk.
@@ -488,7 +489,7 @@ def getBestFreeSpaceRegion(disk, part_type, req_size, start=None,
             start -- requested start sector for the partition
 
     """
-    log.debug("getBestFreeSpaceRegion: disk=%s part_type=%d req_size=%dMB "
+    log.debug("getBestFreeSpaceRegion: disk=%s part_type=%d req_size=%s "
               "boot=%s best=%s grow=%s start=%s" %
               (disk.device.path, part_type, req_size, boot, best_free, grow,
                start))
@@ -513,17 +514,18 @@ def getBestFreeSpaceRegion(disk, part_type, req_size, start=None,
             continue
 
         if boot:
-            free_start_mb = sectorsToSize(free_geom.start,
-                                          disk.device.sectorSize)
-            req_end_mb = free_start_mb + req_size
-            if req_end_mb > 2*1024*1024:
-                log.debug("free range position would place boot req above 2TB")
+            max_boot = Size(spec="2 TiB")
+            free_start = Size(bytes=(free_geom.start * disk.device.sectorSize))
+            req_end = free_start + req_size
+            if req_end > max_boot:
+                log.debug("free range position would place boot req above %s",
+                            max_boot)
                 continue
 
         log.debug("current free range is %d-%d (%dMB)" % (free_geom.start,
                                                           free_geom.end,
                                                           free_geom.getSize()))
-        free_size = free_geom.getSize()
+        free_size = Size(bytes=free_geom.getSize(unit="B"))
 
         # For boot partitions, we want the first suitable region we find.
         # For growable or extended partitions, we want the largest possible
@@ -546,24 +548,24 @@ def getBestFreeSpaceRegion(disk, part_type, req_size, start=None,
     return best_free
 
 def sectorsToSize(sectors, sectorSize):
-    """ Convert length in sectors to size in MB.
+    """ Convert length in sectors to size.
 
         Arguments:
 
             sectors     -   sector count
             sectorSize  -   sector size for the device, in bytes
     """
-    return (sectors * sectorSize) / (1024.0 * 1024.0)
+    return Size(bytes=(sectors * sectorSize))
 
 def sizeToSectors(size, sectorSize):
-    """ Convert size in MB to length in sectors.
+    """ Convert size to length in sectors.
 
         Arguments:
 
-            size        -   size in MB
+            size        -   size
             sectorSize  -   sector size for the device, in bytes
     """
-    return (size * 1024.0 * 1024.0) / sectorSize
+    return int(size) / sectorSize
 
 def removeNewPartitions(disks, partitions):
     """ Remove newly added input partitions from input disks.
@@ -641,7 +643,6 @@ def addPartition(disklabel, free, part_type, size, start=None, end=None):
             end = free.end
             length = end - start + 1
         else:
-            # size is in MB
             length = sizeToSectors(size, disklabel.partedDevice.sectorSize)
             end = start + length - 1
 
@@ -874,7 +875,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
         boot = _part.req_base_weight > 1000
 
         log.debug("allocating partition: %s ; id: %d ; disks: %s ;\n"
-                  "boot: %s ; primary: %s ; size: %dMB ; grow: %s ; "
+                  "boot: %s ; primary: %s ; size: %s ; grow: %s ; "
                   "max_size: %s ; start: %s ; end: %s" % (_part.name, _part.id,
                                     [d.name for d in req_disks],
                                     boot, _part.req_primary,
@@ -884,7 +885,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
         free = None
         use_disk = None
         part_type = None
-        growth = 0
+        growth = 0  # in sectors
         # loop through disks
         for _disk in req_disks:
             disklabel = disklabels[_disk.path]
@@ -1001,7 +1002,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
                                                temp_parts, freespace)
 
                         # grow all growable requests
-                        disk_growth = 0
+                        disk_growth = 0 # in sectors
                         disk_sector_size = disklabels[disk_path].partedDevice.sectorSize
                         for chunk in chunks:
                             chunk.growRequests()
@@ -1009,8 +1010,8 @@ def allocatePartitions(storage, disks, partitions, freespace):
                             new_growth += chunk.growth
                             disk_growth += chunk.growth
                             for req in chunk.requests:
-                                log.debug("request %d (%s) growth: %d (%dMB) "
-                                          "size: %dMB" %
+                                log.debug("request %d (%s) growth: %d (%s) "
+                                          "size: %s" %
                                           (req.device.id,
                                            req.device.name,
                                            req.growth,
@@ -1018,7 +1019,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
                                                          disk_sector_size),
                                            sectorsToSize(req.growth + req.base,
                                                          disk_sector_size)))
-                        log.debug("disk %s growth: %d (%dMB)" %
+                        log.debug("disk %s growth: %d (%s)" %
                                         (disk_path, disk_growth,
                                          sectorsToSize(disk_growth,
                                                        disk_sector_size)))
@@ -1049,7 +1050,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
                                 % (_disk.name, new_part_type))
                     part_type = new_part_type
                     use_disk = _disk
-                    log.debug("new free: %d-%d / %dMB" % (best.start,
+                    log.debug("new free: %d-%d / %sMB" % (best.start,
                                                           best.end,
                                                           best.getSize()))
                     log.debug("new free allows for %d sectors of growth" %
@@ -1187,10 +1188,10 @@ class LVRequest(Request):
 
         # Round up to nearest pe. For growable requests this will mean that
         # first growth is to fill the remainder of any unused extent.
-        self.base = lv.vg.align(lv.req_size, roundup=True) / lv.vg.peSize # pe
+        self.base = int(lv.vg.align(lv.req_size, roundup=True) / lv.vg.peSize)
 
         if lv.req_grow:
-            limits = [l / lv.vg.peSize for l in
+            limits = [int(l / lv.vg.peSize) for l in
                         [lv.vg.align(lv.req_max_size),
                          lv.vg.align(lv.format.maxSize)] if l > 0]
 
@@ -1232,7 +1233,7 @@ class Chunk(object):
 
     def __repr__(self):
         s = ("%(type)s instance --\n"
-             "device = %(device)s  length = %(length)d  size = %(size)d\n"
+             "device = %(device)s  length = %(length)d  size = %(size)s\n"
              "remaining = %(rem)d  pool = %(pool)d" %
              {"type": self.__class__.__name__, "device": self.path,
               "length": self.length, "size": self.lengthToSize(self.length),
@@ -1256,7 +1257,7 @@ class Chunk(object):
 
     def reclaim(self, request, amount):
         """ Reclaim units from a request and return them to the pool. """
-        log.debug("reclaim: %s %d (%d MB)" % (request, amount, self.lengthToSize(amount)))
+        log.debug("reclaim: %s %d (%s)" % (request, amount, self.lengthToSize(amount)))
         if request.growth < amount:
             log.error("tried to reclaim %d from request with %d of growth"
                         % (amount, request.growth))
@@ -1309,7 +1310,7 @@ class Chunk(object):
             if req.growth > max_growth:
                 # we've grown beyond the maximum. put some back.
                 extra = req.growth - max_growth
-                log.debug("taking back %d (%dMB) from %d (%s)" %
+                log.debug("taking back %d (%s) from %d (%s)" %
                             (extra, self.lengthToSize(extra),
                              req.device.id, req.device.name))
                 self.pool += extra
@@ -1346,7 +1347,7 @@ class Chunk(object):
             if uniform:
                 growth = last_pool / self.remaining
 
-            log.debug("%d requests and %d (%dMB) left in chunk" %
+            log.debug("%d requests and %s (%s) left in chunk" %
                         (self.remaining, self.pool, self.lengthToSize(self.pool)))
             for p in self.requests:
                 if p.done or p in self.skip_list:
@@ -1361,13 +1362,13 @@ class Chunk(object):
 
                 p.growth += growth
                 self.pool -= growth
-                log.debug("adding %d (%dMB) to %d (%s)" %
+                log.debug("adding %s (%s) to %d (%s)" %
                             (growth, self.lengthToSize(growth),
                              p.device.id, p.device.name))
 
                 new_base = self.trimOverGrownRequest(p, base=new_base)
-                log.debug("new grow amount for request %d (%s) is %d "
-                          "units, or %dMB" %
+                log.debug("new grow amount for request %d (%s) is %s "
+                          "units, or %s" %
                             (p.device.id, p.device.name, p.growth,
                              self.lengthToSize(p.growth)))
 
@@ -1381,13 +1382,13 @@ class Chunk(object):
                 growth = self.pool
                 p.growth += growth
                 self.pool = 0
-                log.debug("adding %d (%dMB) to %d (%s)" %
+                log.debug("adding %s (%s) to %d (%s)" %
                             (growth, self.lengthToSize(growth),
                              p.device.id, p.device.name))
 
                 self.trimOverGrownRequest(p)
-                log.debug("new grow amount for request %d (%s) is %d "
-                          "units, or %dMB" %
+                log.debug("new grow amount for request %d (%s) is %s "
+                          "units, or %s" %
                             (p.device.id, p.device.name, p.growth,
                              self.lengthToSize(p.growth)))
 
@@ -1490,7 +1491,8 @@ class DiskChunk(Chunk):
 
         # 2TB limit on bootable partitions, regardless of disklabel
         if req.device.req_bootable:
-            limits.append(sizeToSectors(2*1024*1024, self.sectorSize) - req_end)
+            max_boot = sizeToSectors(Size(spec="2 TiB"), self.sectorSize)
+            limits.append(max_boot - req_end)
 
         # request-specific maximum (see Request.__init__, above, for details)
         if req.max_growth:
@@ -1527,7 +1529,7 @@ class VGChunk(Chunk):
         """
         self.vg = vg
         self.path = vg.path
-        usable_extents = vg.extents - (vg.reservedSpace / vg.peSize)
+        usable_extents = vg.extents - int(vg.reservedSpace / vg.peSize)
         super(VGChunk, self).__init__(usable_extents, requests=requests)
 
     def addRequest(self, req):
@@ -1539,10 +1541,10 @@ class VGChunk(Chunk):
         super(VGChunk, self).addRequest(req)
 
     def lengthToSize(self, length):
-        return length * self.vg.peSize
+        return self.vg.peSize * length
 
     def sizeToLength(self, size):
-        return size / self.vg.peSize
+        return int(size / self.vg.peSize)
 
     def sortRequests(self):
         # sort the partitions by start sector
@@ -1560,13 +1562,13 @@ class VGChunk(Chunk):
             growth = int(req.device.req_percent * 0.01 * self.length)# truncate
             req.growth += growth
             self.pool -= growth
-            log.debug("adding %d (%dMB) to %d (%s)" %
+            log.debug("adding %d (%s) to %d (%s)" %
                         (growth, self.lengthToSize(growth),
                          req.device.id, req.device.name))
 
             new_base = self.trimOverGrownRequest(req)
             log.debug("new grow amount for request %d (%s) is %d "
-                      "units, or %dMB" %
+                      "units, or %s" %
                         (req.device.id, req.device.name, req.growth,
                          self.lengthToSize(req.growth)))
 
@@ -1997,7 +1999,7 @@ def growLVM(storage):
             log.debug("vg %s has no free space" % vg.name)
             continue
 
-        log.debug("vg %s: %dMB free ; lvs: %s" % (vg.name, total_free,
+        log.debug("vg %s: %s free ; lvs: %s" % (vg.name, total_free,
                                                   [l.lvname for l in vg.lvs]))
 
         # don't include thin lvs in the vg's growth calculation
diff --git a/blivet/platform.py b/blivet/platform.py
index 185d82e..442bdde 100644
--- a/blivet/platform.py
+++ b/blivet/platform.py
@@ -28,6 +28,7 @@ import parted
 from . import arch
 from .flags import flags
 from .partspec import PartSpec
+from .size import Size
 
 import gettext
 _ = lambda x: gettext.ldgettext("blivet", x)
@@ -137,8 +138,8 @@ class Platform(object):
 
     def setDefaultPartitioning(self):
         """Return the default platform-specific partitioning information."""
-        return [PartSpec(mountpoint="/boot",
-                         size=500, weight=self.weight(mountpoint="/boot"))]
+        return [PartSpec(mountpoint="/boot", size=Size(spec="500MB"),
+                         weight=self.weight(mountpoint="/boot"))]
 
     def weight(self, fstype=None, mountpoint=None):
         """ Given an fstype (as a string) or a mountpoint, return an integer
@@ -168,7 +169,7 @@ class X86(Platform):
     def setDefaultPartitioning(self):
         """Return the default platform-specific partitioning information."""
         ret = Platform.setDefaultPartitioning(self)
-        ret.append(PartSpec(fstype="biosboot", size=1,
+        ret.append(PartSpec(fstype="biosboot", size=Size(spec="1MB"),
                             weight=self.weight(fstype="biosboot")))
         return ret
 
@@ -196,8 +197,8 @@ class EFI(Platform):
 
     def setDefaultPartitioning(self):
         ret = Platform.setDefaultPartitioning(self)
-        ret.append(PartSpec(mountpoint="/boot/efi", fstype="efi", size=20,
-                            maxSize=200,
+        ret.append(PartSpec(mountpoint="/boot/efi", fstype="efi",
+                            size=Size(spec="20MB"), maxSize=Size(spec="200MB"),
                             grow=True, weight=self.weight(fstype="efi")))
         return ret
 
@@ -218,8 +219,8 @@ class MacEFI(EFI):
 
     def setDefaultPartitioning(self):
         ret = Platform.setDefaultPartitioning(self)
-        ret.append(PartSpec(mountpoint="/boot/efi", fstype="hfs+", size=20,
-                            maxSize=200,
+        ret.append(PartSpec(mountpoint="/boot/efi", fstype="hfs+",
+                            size=Size(spec="20MB"), maxSize=Size(spec="200MB"),
                             grow=True, weight=self.weight(mountpoint="/boot/efi")))
         return ret
 
@@ -240,7 +241,7 @@ class IPSeriesPPC(PPC):
 
     def setDefaultPartitioning(self):
         ret = PPC.setDefaultPartitioning(self)
-        ret.append(PartSpec(fstype="prepboot", size=4,
+        ret.append(PartSpec(fstype="prepboot", size=Size(spec="4MB"),
                             weight=self.weight(fstype="prepboot")))
         return ret
 
@@ -262,7 +263,7 @@ class NewWorldPPC(PPC):
 
     def setDefaultPartitioning(self):
         ret = Platform.setDefaultPartitioning(self)
-        ret.append(PartSpec(fstype="appleboot", size=1, maxSize=1,
+        ret.append(PartSpec(fstype="appleboot", size=Size(spec="1MB"),
                             weight=self.weight(fstype="appleboot")))
         return ret
 
@@ -293,7 +294,7 @@ class S390(Platform):
 
     def setDefaultPartitioning(self):
         """Return the default platform-specific partitioning information."""
-        return [PartSpec(mountpoint="/boot", size=500,
+        return [PartSpec(mountpoint="/boot", size=Size(spec="500MB"),
                          weight=self.weight(mountpoint="/boot"), lv=True,
                          singlePV=True)]
 
@@ -338,10 +339,11 @@ class omapARM(ARM):
     def setDefaultPartitioning(self):
         """Return the ARM-OMAP platform-specific partitioning information."""
         ret = [PartSpec(mountpoint="/boot/uboot", fstype="vfat",
-                        size=20, maxSize=200, grow=True,
+                        size=Size(spec="20MB"), maxSize=Size(spec="200MB"),
+                        grow=True,
                         weight=self.weight(fstype="vfat", mountpoint="/boot/uboot"))]
         ret.append(PartSpec(mountpoint="/", fstype="ext4",
-                            size=2000, maxSize=3000,
+                            size=Size(spec="2GB"), maxSize=Size(spec="3GB"),
                             weight=self.weight(mountpoint="/")))
         return ret
 
diff --git a/blivet/size.py b/blivet/size.py
index 73c3e5a..448a578 100644
--- a/blivet/size.py
+++ b/blivet/size.py
@@ -47,11 +47,11 @@ _decimalPrefix = [(1000, _("kilo"), _("k")),
 _binaryPrefix = [(1024, _("kibi"), _("Ki")),
                  (1024**2, _("mebi"), _("Mi")),
                  (1024**3, _("gibi"), _("Gi")),
-                 (1024**4, _("tebi"), None),
-                 (1024**5, _("pebi"), None),
-                 (1024**6, _("ebi"), None),
-                 (1024**7, _("zebi"), None),
-                 (1024**8, _("yobi"), None)]
+                 (1024**4, _("tebi"), _("Ti")),
+                 (1024**5, _("pebi"), _("Pi")),
+                 (1024**6, _("ebi"), _("Ei")),
+                 (1024**7, _("zebi"), _("Zi")),
+                 (1024**8, _("yobi"), _("Yi"))]
 
 _bytes = [_('b'), _('byte'), _('bytes')]
 _prefixes = _decimalPrefix + _binaryPrefix
@@ -144,6 +144,9 @@ class Size(Decimal):
     def __repr__(self):
         return "Size('%s')" % self
 
+    def __deepcopy__(self, memo):
+        return Size(bytes=self.convertTo(spec="b"))
+
     def __add__(self, other, context=None):
         return Size(bytes=Decimal.__add__(self, other, context=context))
 
@@ -162,6 +165,12 @@ class Size(Decimal):
     def __div__(self, other, context=None):
         return Size(bytes=Decimal.__div__(self, other, context=context))
 
+    def __mod__(self, other, context=None):
+        return Size(bytes=Decimal.__mod__(self, other, context=context))
+
+    def __float__(self, context=None):
+        return float(self.convertTo(spec="b"))
+
     def _trimEnd(self, val):
         """ Internal method to trim trailing zeros. """
         val = re.sub(r'(\.\d*?)0+$', '\\1', val)
@@ -180,7 +189,7 @@ class Size(Decimal):
         spec = spec.lower()
 
         if spec in _bytes:
-            return self
+            return Decimal(self)
 
         for factor, prefix, abbr in _prefixes:
             check = _makeSpecs(prefix, abbr)
diff --git a/blivet/udev.py b/blivet/udev.py
index ebcf92e..10f0c35 100644
--- a/blivet/udev.py
+++ b/blivet/udev.py
@@ -26,6 +26,7 @@ import re
 
 import util
 from errors import *
+from size import Size
 
 import pyudev
 global_udev = pyudev.Udev()
@@ -398,17 +399,15 @@ def udev_device_get_vg_uuid(info):
 def udev_device_get_vg_size(info):
     # lvm's decmial precision is not configurable, so we tell it to use
     # KB and convert to MB here
-    return float(info['LVM2_VG_SIZE']) / 1024
+    return Size(spec="%s kb" % info['LVM2_VG_SIZE'])
 
 def udev_device_get_vg_free(info):
     # lvm's decmial precision is not configurable, so we tell it to use
     # KB and convert to MB here
-    return float(info['LVM2_VG_FREE']) / 1024
+    return Size(spec="%s kb" % info['LVM2_VG_FREE'])
 
 def udev_device_get_vg_extent_size(info):
-    # lvm's decmial precision is not configurable, so we tell it to use
-    # KB and convert to MB here
-    return float(info['LVM2_VG_EXTENT_SIZE']) / 1024
+    return Size(spec="%s kb" % info['LVM2_VG_EXTENT_SIZE'])
 
 def udev_device_get_vg_extent_count(info):
     return int(info['LVM2_VG_EXTENT_COUNT'])
@@ -420,9 +419,7 @@ def udev_device_get_vg_pv_count(info):
     return int(info['LVM2_PV_COUNT'])
 
 def udev_device_get_pv_pe_start(info):
-    # lvm's decmial precision is not configurable, so we tell it to use
-    # KB and convert to MB here
-    return float(info['LVM2_PE_START']) / 1024
+    return Size(spec="%s kb" % info['LVM2_PE_START'])
 
 def udev_device_get_lv_names(info):
     names = info['LVM2_LV_NAME']
@@ -441,15 +438,13 @@ def udev_device_get_lv_uuids(info):
     return uuids
 
 def udev_device_get_lv_sizes(info):
-    # lvm's decmial precision is not configurable, so we tell it to use
-    # KB and convert to MB here
     sizes = info['LVM2_LV_SIZE']
     if not sizes:
         sizes = []
     elif not isinstance(sizes, list):
         sizes = [sizes]
 
-    return [float(s) / 1024 for s in sizes]
+    return [Size(spec="%s kb" % s) for s in sizes]
 
 def udev_device_get_lv_attr(info):
     attr = info['LVM2_LV_ATTR']
diff --git a/blivet/util.py b/blivet/util.py
index 9d038cd..f2bad56 100644
--- a/blivet/util.py
+++ b/blivet/util.py
@@ -5,6 +5,7 @@ import subprocess
 import re
 
 from udev import udev_settle
+from size import Size
 
 import logging
 log = logging.getLogger("blivet")
@@ -126,19 +127,20 @@ def get_mount_device(mountpoint):
     return mount_device
 
 def total_memory():
-    """ Return the amount of system RAM in kilobytes. """
+    """ Return the amount of system RAM.
+
+        :rtype: :class:`~.size.Size`
+    """
     lines = open("/proc/meminfo").readlines()
     for line in lines:
         if line.startswith("MemTotal:"):
-            mem = long(line.split()[1])
+            mem = Size(spec="%d KiB" % line.split()[1])
 
     # Because /proc/meminfo only gives us the MemTotal (total physical RAM
     # minus the kernel binary code), we need to round this up. Assuming
-    # every machine has the total RAM MB number divisible by 128. */
-    mem /= 1024
-    mem = (mem / 128 + 1) * 128
-    mem *= 1024
-
+    # every machine has the total RAM MiB number divisible by 128. */
+    bs = Size(spec="128MiB")
+    mem = (mem / bs + 1) * bs
     return mem
 
 ##
@@ -311,9 +313,7 @@ def numeric_type(num):
     """
     if num is None:
         num = 0
-    elif not (isinstance(num, int) or \
-              isinstance(num, long) or \
-              isinstance(num, float)):
+    elif not isinstance(num, (int, long, float, Size)):
         raise ValueError("value (%s) must be either a number or None" % num)
 
     return num
diff --git a/examples/factory.py b/examples/factory.py
index 7e6391c..c8f9546 100644
--- a/examples/factory.py
+++ b/examples/factory.py
@@ -14,13 +14,14 @@ blivet_log = logging.getLogger("blivet")
 blivet_log.info(sys.argv[0])
 
 import blivet
+from blivet.size import Size
 
 b = blivet.Blivet()   # create an instance of Blivet (don't add system devices)
 
 # create two disk image files on which to create new devices
-disk1_file = create_sparse_file(b, "disk1", 100000)
+disk1_file = create_sparse_file(b, "disk1", Size(spec="100GB"))
 b.config.diskImages["disk1"] = disk1_file
-disk2_file = create_sparse_file(b, "disk2", 100000)
+disk2_file = create_sparse_file(b, "disk2", Size(spec="100GB"))
 b.config.diskImages["disk2"] = disk2_file
 
 b.reset()
@@ -32,14 +33,14 @@ try:
     disk2.format = blivet.formats.getFormat("disklabel", device=disk2.path)
 
     # create an lv named data in a vg named testvg
-    device = b.factoryDevice(blivet.devicefactory.DEVICE_TYPE_LVM, 50000,
-                             disks=[disk1, disk2],
+    device = b.factoryDevice(blivet.devicefactory.DEVICE_TYPE_LVM,
+                             Size(spec="50GB"), disks=[disk1, disk2],
                              fstype="xfs", mountpoint="/data")
     print_devices(b)
 
     # change testvg to have an md RAID1 pv instead of partition pvs
-    device = b.factoryDevice(blivet.devicefactory.DEVICE_TYPE_LVM, 50000,
-                             disks=[disk1, disk2],
+    device = b.factoryDevice(blivet.devicefactory.DEVICE_TYPE_LVM,
+                             Size(spec="50GB"), disks=[disk1, disk2],
                              fstype="xfs", mountpoint="/data",
                              container_raid_level="raid1",
                              device=device)
diff --git a/examples/partitioning.py b/examples/partitioning.py
index 07c6a73..6ee5eee 100644
--- a/examples/partitioning.py
+++ b/examples/partitioning.py
@@ -14,13 +14,14 @@ blivet_log = logging.getLogger("blivet")
 blivet_log.info(sys.argv[0])
 
 import blivet
+from blivet.size import Size
 
 b = blivet.Blivet()   # create an instance of Blivet (don't add system devices)
 
 # create two disk image files on which to create new devices
-disk1_file = create_sparse_file(b, "disk1", 100000)
+disk1_file = create_sparse_file(b, "disk1", Size(spec="100GB"))
 b.config.diskImages["disk1"] = disk1_file
-disk2_file = create_sparse_file(b, "disk2", 100000)
+disk2_file = create_sparse_file(b, "disk2", Size(spec="100GB"))
 b.config.diskImages["disk2"] = disk2_file
 
 b.reset()
@@ -32,20 +33,21 @@ try:
     b.initializeDisk(disk1)
     b.initializeDisk(disk2)
 
-    # new partition on either disk1 or disk2 with base size 10000 MiB and growth
-    # up to a maximum size of 50000 MiB
-    dev = b.newPartition(size=10000, grow=True, maxsize=50000,
-                         parents=[disk1, disk2])
+    # new partition on either disk1 or disk2 with base size 10GiB and growth
+    # up to a maximum size of 50GiB
+    dev = b.newPartition(size=Size(spec="10MiB"), maxsize=Size(spec="50GiB"),
+                         grow=True, parents=[disk1, disk2])
     b.createDevice(dev)
 
-    # new partition on disk1 with base size 5000 MiB and unbounded growth and an
+    # new partition on disk1 with base size 5GiB and unbounded growth and an
     # ext4 filesystem
-    dev = b.newPartition(fmt_type="ext4", size=5000, grow=True, parents=[disk1])
+    dev = b.newPartition(fmt_type="ext4", size=Size(spec="5GiB"), grow=True,
+                         parents=[disk1])
     b.createDevice(dev)
 
-    # new partition on any suitable disk with a fixed size of 2000 MiB formatted
+    # new partition on any suitable disk with a fixed size of 2GiB formatted
     # as swap space
-    dev = b.newPartition(fmt_type="swap", size=2000)
+    dev = b.newPartition(fmt_type="swap", size=Size(spec="2GiB"))
     b.createDevice(dev)
 
     # allocate the partitions (decide where and on which disks they'll reside)
-- 
1.8.1.4

