Proof of concept, early work in progress code for partition/filesystem resizing

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I've been looking some at what we need to have to implement resizing of
partitions and filesystems within anaconda.  At this point, the code is
starting to work, although there are still some large-ish holes.  Also,
there are some functionality pieces that are entirely missing.  So to
start with the caveats:
1) The UI is just enough to make it possible to test the code and not
the UI that we're going to actually want to use.  
2) Only implemented for ext[23].  Relatively easy to add additional
implementations for other filesystems, though
3) Only implemented for partitions.  Not RAID.  Not LVM.  I'm not sure
if we can do RAID.  LVM LVs are pretty easy.  LVM PVs look like they
have a lot more caveats based on looking at the pvresize man page
4) Real minimum and maximum calculations still need to be written.  But
stubs are there
5) Doesn't really have any error handling to speak of

All that said, I have done installs to resized partitions and
filesystems which is pretty freaking cool :-)  

I'm going to keep working on filling in the holes but anyone else that
wants to take a look, feel free.  I just wanted to get this out there
for any comments, thoughts, etc that people have

Jeremy
diff --git a/pydisk.c b/pydisk.c
index 51c2e27..20bb714 100644
--- a/pydisk.c
+++ b/pydisk.c
@@ -589,6 +589,28 @@ py_ped_partition_set_system (PyPedPartition *p, PyObject * args)
 }
 
 static PyObject *
+py_ped_partition_set_geometry (PyPedPartition *p, PyObject * args)
+{
+        PyPedConstraint *cs = NULL;
+        PedSector start, end;
+
+        if (!PyArg_ParseTuple(args, "O!LL", &PyPedConstraintType, &cs, 
+                              &start, &end))
+                return NULL;
+        
+        py_ped_exception_string_clear ();
+
+        if (!ped_disk_set_partition_geom (p->disk->disk, p->part, cs->constraint, 
+                                          start, end)) {
+                py_ped_set_error_from_ped_exception ();
+                return NULL;
+        }
+        
+        Py_INCREF(Py_None);
+        return Py_None;
+}
+
+static PyObject *
 py_ped_partition_set_name (PyPedPartition *p, PyObject * args)
 {
         char *name;
@@ -685,6 +707,8 @@ static struct PyMethodDef PyPedPartitionMethods[] = {
           METH_VARARGS, NULL },
         { "set_name", (PyCFunction) py_ped_partition_set_name,
           METH_VARARGS, NULL },
+        { "set_geometry", (PyCFunction) py_ped_partition_set_geometry,
+          METH_VARARGS, NULL },
         { "get_name", (PyCFunction) py_ped_partition_get_name,
           METH_VARARGS, NULL },
         { "is_busy", (PyCFunction) py_ped_partition_is_busy,
diff --git a/pygeometry.c b/pygeometry.c
index fdd7614..8522161 100644
--- a/pygeometry.c
+++ b/pygeometry.c
@@ -24,6 +24,7 @@
 #include "partedmodule.h"
 #include "pygeometry.h"
 #include "pyfilesystem.h"
+#include "pyconstraint.h"
 
 /* geometry implementation */
 
@@ -157,6 +158,24 @@ py_ped_geometry_duplicate (PyPedGeometry * self, PyObject * args)
         return (PyObject *) py_ped_geometry_obj_new (geom, self->dev, 0);
 }
 
+static PyObject *
+py_ped_constraint_exact (PyPedGeometry * self, PyObject * args)
+{
+        PedConstraint *constraint;
+        PyPedConstraint *pyconstraint;
+        
+        py_ped_exception_string_clear ();
+        constraint = ped_constraint_exact (self->geom);
+        if (constraint == NULL) {
+                py_ped_set_error_from_ped_exception ();
+                return NULL;
+        }
+        
+        pyconstraint = py_ped_constraint_obj_new (constraint, self->dev, 0);
+
+        return (PyObject *) pyconstraint;
+}
+
 static struct PyMethodDef PyPedGeometryMethods[] = {
 	{ "file_system_open",
           (PyCFunction) py_ped_file_system_open, METH_VARARGS, NULL },
@@ -172,7 +191,8 @@ static struct PyMethodDef PyPedGeometryMethods[] = {
           (PyCFunction) py_ped_geometry_set_end, METH_VARARGS, NULL },
         { "duplicate",
           (PyCFunction) py_ped_geometry_duplicate, METH_VARARGS, NULL },
-
+        { "constraint_exact", 
+          (PyCFunction) py_ped_constraint_exact, METH_VARARGS, NULL },
 	{ NULL, NULL, 0, NULL }	
 };
 
diff --git a/autopart.py b/autopart.py
index 8886241..f660114 100644
--- a/autopart.py
+++ b/autopart.py
@@ -865,6 +865,23 @@ def setPreexistParts(diskset, requests):
         part = disk.next_partition()
         while part:
             if part.geom.start == request.start and part.geom.end == request.end:
+                # if the partition is being resized, we do that now
+                if request.targetSize is not None:
+                    startSec = part.geom.start
+                    endSec = part.geom.start + long(((request.targetSize * 1024L * 1024L) / disk.dev.sector_size)) - 1
+
+                    # FIXME: could do with some error handling
+                    g = part.geom.duplicate()
+                    g.set_end(endSec)
+
+                    constraint = g.constraint_exact()
+                    part.set_geometry(constraint, startSec, endSec)
+
+                    if startSec != part.geom.start:
+                        isys.vtActivate(1)
+                        import pdb; pdb.set_trace()
+                        raise RuntimeError, "start of partition moved"
+
                 request.device = partedUtils.get_partition_name(part)
                 if request.fstype:
                     if request.fstype.getName() != request.origfstype.getName():
diff --git a/dispatch.py b/dispatch.py
index 5775108..8d36483 100644
--- a/dispatch.py
+++ b/dispatch.py
@@ -78,8 +78,10 @@ installSteps = [
     ("upgradeswapsuggestion", upgradeSwapSuggestion, ),
     ("addswap", ),
     ("partitiondone", partitioningComplete, ),
+    ("enablefilesystems", turnOnFilesystems, ),
     ("upgrademigfind", upgradeMigrateFind, ),
     ("upgrademigratefs", ),
+    ("migratefilesystems", doMigrateFilesystems, ),
     ("upgbootloader", ),
     ("bootloadersetup", bootloaderSetupChoices, ),
     ("bootloader", ),
@@ -96,8 +98,6 @@ installSteps = [
     ("confirminstall", ),
     ("confirmupgrade", ),
     ("install", ),
-    ("enablefilesystems", turnOnFilesystems, ),
-    ("migratefilesystems", doMigrateFilesystems, ),
     ("setuptime", setupTimezone, ),
     ("preinstallconfig", doPreInstall, ),
     ("installpackages", doInstall, ),
diff --git a/fsset.py b/fsset.py
index 61ba6a0..84b8440 100644
--- a/fsset.py
+++ b/fsset.py
@@ -177,10 +177,18 @@ class FileSystemType:
         self.extraFormatArgs = []
         self.maxLabelChars = 16
         self.packages = []
+        self.resizable = False
         self.supportsFsProfiles = False
         self.fsProfileSpecifier = None
         self.fsprofile = None
 
+    def isResizable(self):
+        return self.resizable
+    def resize(self, entry, size, chroot='/'):
+        pass
+    def getMinimumSize(self):
+        pass
+
     def isKernelFS(self):
         """Returns True if this is an in-kernel pseudo-filesystem."""
         return False
@@ -505,6 +513,26 @@ class extFileSystem(FileSystemType):
         self.packages = [ "e2fsprogs" ]
         self.supportsFsProfiles = True
         self.fsProfileSpecifier = "-T"
+        self.resizable = True
+
+    def resize(self, entry, size, chroot='/'):
+        devicePath = entry.device.setupDevice(chroot)
+        log.info("checking %s prior to resize" %(devicePath,))
+        rc = iutil.execWithRedirect("e2fsck", ["-f", devicePath],
+                                    stdout="/dev/tty5", stderr="/dev/tty5",
+                                    searchPath = 1)
+        log.info("resizing %s" %(devicePath,))
+        rc = iutil.execWithRedirect("resize2fs", [devicePath, "%sM" %(size,)],
+                                    stdout="/dev/tty5", stderr="/dev/tty5",
+                                    searchPath = 1)
+        if rc:
+            raise RuntimeError, "resize failed"
+
+    def getMinimumSize(self):
+        """Return the minimum filesystem size in megabytes"""
+        # FIXME: there isn't a good way to get this information with
+        # an ext filesystem.  
+        return 100
 
     def labelDevice(self, entry, chroot):
         devicePath = entry.device.setupDevice(chroot)
@@ -1376,6 +1404,23 @@ MAILADDR root
             if bootPart:
                 del bootPart
 
+    def resizeFilesystems (self, shrinkgrow):
+        for entry in self.entries:
+            if not entry.fsystem or not entry.fsystem.isResizable():
+                continue
+            if entry.resizetarget is None:
+                continue
+            if entry.resizedirection < 0 and shrinkgrow != -1:
+                continue
+            if entry.resizedirection > 0 and shrinkgrow != 1:
+                continue
+            entry.fsystem.resize(entry, entry.resizetarget)
+
+    def shrinkFilesystems (self):
+        self.resizeFilesystems(-1)
+    def growFilesystems (self):
+        self.resizeFilesystems(1)
+
     def formatSwap (self, chroot, forceFormat=False):
         formatted = []
         notformatted = []
@@ -1826,6 +1871,8 @@ class FileSystemSetEntry:
         self.fsystem = fsystem
         self.origfsystem = origfsystem
         self.migrate = migrate
+        self.resizetarget = None
+        self.resizedirection = 0
         self.options = options
         self.mountcount = 0
         self.label = None
@@ -1907,6 +1954,18 @@ class FileSystemSetEntry:
     def getMigrate (self):
         return self.migrate
 
+    def setResizeTarget (self, targetsize, size):
+        if not self.fsystem.isResizable() and targetsize is not None:
+            raise ValueError, "Can't set a resize target for a non-resizable filesystem"
+        self.resizetarget = targetsize
+        # FIXME: this is pretty hacky.  we should determine shrink/grow
+        # more automatically
+        # a grow or shrink by looking at the sizes
+        self.resizedirection = targetsize - size
+
+    def getResizeTarget (self):
+        return self.targetsize
+
     def isMounted (self):
         return self.mountcount > 0
 
diff --git a/iw/partition_dialog_gui.py b/iw/partition_dialog_gui.py
index 042be17..2644cfd 100644
--- a/iw/partition_dialog_gui.py
+++ b/iw/partition_dialog_gui.py
@@ -214,6 +214,17 @@ class PartitionEditor:
                 else:
                     request.migrate = 0
 
+                if self.fsoptionsDict.has_key("resizecb"):
+                    resizecb = self.fsoptionsDict["resizecb"]
+                else:
+                    resizecb = None
+
+                if resizecb and resizecb.get_active():
+                    request.targetSize = self.fsoptionsDict["resizesb"].get_value_as_int()
+                else:
+                    request.targetSize = None
+                print "the target size for %s is %s" %(request.mountpoint, request.targetSize)
+
                 # set back if we are not formatting or migrating
 		origfstype = self.origrequest.origfstype
                 if not request.format and not request.migrate:
diff --git a/iw/partition_ui_helpers_gui.py b/iw/partition_ui_helpers_gui.py
index be66780..09a3a96 100644
--- a/iw/partition_ui_helpers_gui.py
+++ b/iw/partition_ui_helpers_gui.py
@@ -206,6 +206,9 @@ def mountptchangeCB(widget, fstypecombo):
     if rhpl.getArch() == "ia64" and widget.get_children()[0].get_text() == "/boot/efi":
         fstypecombo.set_active_text("vfat")
 
+def resizeOptionCB(widget, resizesb):
+    resizesb.set_sensitive(widget.get_active())
+
 def formatOptionCB(widget, data):
     (combowidget, mntptcombo, ofstype) = data
     combowidget.set_sensitive(widget.get_active())
@@ -305,11 +308,50 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo,
 	migraterb = None
 	migfstypeCombo = None
 
+    if origrequest.origfstype.isResizable():
+        maintable.attach(gtk.HSeparator(), 0, 2, row, row + 1)
+        row = row + 1
+
+        label = gtk.Label(_("How large would you like this partition to be?"))
+        label.set_line_wrap(1)
+        label.set_alignment(0.0, 0.0)
+
+        maintable.attach(label, 0, 2, row, row + 1)
+        row = row + 1
+
+        resizecb = gtk.CheckButton(label=_("_Resize partition:"))
+        resizecb.set_active(0)
+        if origrequest.targetSize is not None:
+            resizecb.set_active(1)
+
+        maintable.attach(resizecb, 0, 1, row, row + 1)
+        
+        origsize = origrequest.size
+        if origrequest.targetSize is not None:
+            val = origrequest.targetSize
+        else:
+            val = origsize 
+        adj = gtk.Adjustment(value = val, 
+                             lower = origrequest.origfstype.getMinimumSize(),
+                             upper = origrequest.getMaximumSize(),
+                             step_incr = 1)
+        resizesb = gtk.SpinButton(adj, digits = 0)
+        resizesb.set_property('numeric', True)
+        maintable.attach(resizesb, 1, 2, row, row + 1)
+
+        resizecb.connect('toggled', resizeOptionCB, resizesb)
+        resizeOptionCB(resizecb, resizesb)
+
+        row = row + 1
+    else:
+        resizecb = resizesb = None
+        
+
     row = row + 1
 
     rc = {}
     for var in ['noformatrb', 'formatrb', 'fstypeCombo',
-                'migraterb', 'migfstypeCombo']:
+                'migraterb', 'migfstypeCombo', 'resizecb', 'resizesb']:
         if eval("%s" % (var,)) is not None:
             rc[var] = eval("%s" % (var,))
 
diff --git a/packages.py b/packages.py
index aeefde2..f9b8169 100644
--- a/packages.py
+++ b/packages.py
@@ -148,8 +148,10 @@ def turnOnFilesystems(anaconda):
                                        searchPath = 1)
             anaconda.id.partitions.doMetaDeletes(anaconda.id.diskset)
             anaconda.id.fsset.setActive(anaconda.id.diskset)
+            anaconda.id.fsset.shrinkFilesystems()
             if not anaconda.id.fsset.isActive():
                 anaconda.id.diskset.savePartitions ()
+            anaconda.id.fsset.growFilesystems()
             if not anaconda.id.fsset.volumesCreated:
                 anaconda.id.fsset.createLogicalVolumes(anaconda.rootPath)
             anaconda.id.fsset.formatSwap(anaconda.rootPath)
diff --git a/partRequests.py b/partRequests.py
index 3fbb812..7437cdf 100644
--- a/partRequests.py
+++ b/partRequests.py
@@ -152,6 +152,9 @@ class RequestSpec:
         self.dev = None
         """A Device() as defined in fsset.py to correspond to this request."""
 
+        self.targetSize = None
+        """Size to resize to"""
+
     def __str__(self):
         if self.fstype:
             fsname = self.fstype.getName()
@@ -209,6 +212,9 @@ class RequestSpec:
         if self.fslabel:
             entry.setLabel(self.fslabel)
 
+        if self.targetSize and self.fstype.isResizable():
+            entry.setResizeTarget(self.targetSize, self.size)
+
         return entry
 
     def setProtected(self, val):
@@ -558,6 +564,10 @@ class PreexistingPartitionSpec(PartitionSpec):
                                mountpoint = mountpoint, preexist = 1)
         self.type = REQUEST_PREEXIST
 
+    def getMaximumSize(self):
+        # FIXME: obviously this needs to be figured out for real
+        return MAX_PART_SIZE
+
 class RaidRequestSpec(RequestSpec):
     """Request to represent RAID devices."""
     
_______________________________________________
Anaconda-devel-list mailing list
Anaconda-devel-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/anaconda-devel-list

[Index of Archives]     [Kickstart]     [Fedora Users]     [Fedora Legacy List]     [Fedora Maintainers]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [Yosemite Photos]     [KDE Users]     [Fedora Tools]
  Powered by Linux