Implement rollback of failed partition resize/move steps (#791875)

Even after implementing a fix for bug 790418 "Unable to inform the
kernel of the change message may lead to corrupted partition table"
GParted/libparted can still encounter errors informing the kernel of the
new partition layout.  This has been seen with GParted on CentOS 7 with
libparted 3.1.

In such a case the partition has been successfully written to the disk
but just informing the kernel failed.  This is a problem because when a
partition is being moved in advance of a file system move step, failure
to inform the kernel leaves the partition boundaries not matching the on
disk limits of the file system.  For a move to the left this leaves the
partition reported as unknown, apparently losing the user's data.

For example start with a 512 MiB partition containing an XFS file
system.  This is recognised by blkid and parted, hence also by GParted.

    # blkid /dev/sdb1
    /dev/sdb1: UUID=... TYPE="xfs" PARTUUID="37965980-01"
    # parted /dev/sdb unit s print
    Model: ATA VBOX HARDDISK (scsi)
    Disk /dev/sdb: 16777216s
    Sector size (logical/physical): 512B/512B
    Partition Table: msdos
    Disk Flags:

    Number  Start     End       Size      Type     File system  Flags
     1      1048576s  2097151s  1048576s  primary  xfs

Now move the partition 100 MiB to the left and have it fail to inform
the kernel after the first partition change step.  Operation details:

    Move /dev/sdb1 to the left                                 (ERROR)
    * calibrate /dev/sdb1                                      (SUCCESS)
    * check file system on /dev/sdb1 for errors and (if poss...(SUCCESS)
    * grow partition from 512.00 MiB to 612.00 MiB             (ERROR)
        old start: 1048576
        old end: 2097151
        old size: 1048576 (512.00 MiB)
        requested start: 843776
        requested end: 2097151
        requested size: 1253376 (612.00 MiB)
      * libparted messages                                     (ERROR)
          Error informing the kernel about modifications to partition
          /dev/sdb1 -- Device or resource busy.  This means Linux won't
          know about any changes you made to /dev/sdb1 until you reboot
          -- so you shouldn't mount it or use it in any way before
          rebooting.  Failed to add partition 1 (resource temporarily
          unavailable)

Now because the start of the partition is 100 MiB before the start of
the file system, the file system is no longer recognised, and apparently
the user's data has been lost.

    # blkid /dev/sdb1
    /dev/sdb1: PARTUUID="37965980-01"
    # parted /dev/sdb unit s print
    ...
    Number  Start    End       Size      Type     File system  Flags
     1      843776s  2097151s  1253376s  primary

It doesn't matter why updating the partition failed, even if it was
because of an error writing to the disk.  Rollback of the change to the
partition should be attempted.  The worst case scenario is that rollback
of the change fails, which is the equivalent to how the code worked
before this patch set.

However in other cases where the partition boundaries are being updated
after a file system move or shrink step then the partition should be
updated to match the new location of the file system itself.  And no
rollback is wanted.  If the failure was only informing the kernel then
in fact the partition has actually been updated on disk after all.

So each partition resize/move step needs examining on a case by case
basis to decide if rolling back the change to the partition is wanted or
not.

This patch only adds partition change rollback into
resize_move_partition().  Rollback remains disabled until all cases are
examined in the following patch.

Bug 791875 - Rollback specific failed partition change steps
This commit is contained in:
Mike Fleetwood 2017-12-08 16:46:31 +00:00 committed by Curtis Gedak
parent 93ccc79e3a
commit 0f16703bbb
2 changed files with 37 additions and 5 deletions

View File

@ -144,8 +144,9 @@ private:
const Partition & partition_new,
OperationDetail & operationdetail );
bool resize_move_partition( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail ) ;
const Partition & partition_new,
OperationDetail & operationdetail,
bool rollback_on_fail = false );
bool resize_move_partition_implement( const Partition & partition_old,
const Partition & partition_new,
Sector & new_start,

View File

@ -2640,8 +2640,9 @@ bool GParted_Core::resize_plain( const Partition & partition_old,
}
bool GParted_Core::resize_move_partition( const Partition & partition_old,
const Partition & partition_new,
OperationDetail & operationdetail )
const Partition & partition_new,
OperationDetail & operationdetail,
bool rollback_on_fail )
{
if ( partition_new.type == TYPE_UNPARTITIONED )
// Trying to resize/move a non-partitioned whole disk device is a
@ -2770,8 +2771,38 @@ bool GParted_Core::resize_move_partition( const Partition & partition_old,
FONT_ITALIC )
) ;
}
operationdetail.get_last_child().set_success_and_capture_errors( success );
if ( ! success && rollback_on_fail )
{
operationdetail.add_child(
OperationDetail( _("attempt to rollback failed change to the partition") ) );
Partition *partition_restore = partition_old.clone();
// Ensure that old partition boundaries are not modified
partition_restore->alignment = ALIGN_STRICT;
bool rollback_success = resize_move_partition_implement( partition_new, *partition_restore,
new_start, new_end );
operationdetail.get_last_child().add_child(
OperationDetail(
String::ucompose( _("original start: %1"), partition_restore->sector_start ) + "\n" +
String::ucompose( _("original end: %1"), partition_restore->sector_end ) + "\n" +
String::ucompose( _("original size: %1 (%2)"),
partition_restore->get_sector_length(),
Utils::format_size( partition_restore->get_sector_length(), partition_restore->sector_size ) ),
STATUS_NONE, FONT_ITALIC ) );
// Update dev mapper entry if partition is dmraid.
rollback_success = rollback_success && update_dmraid_entry( *partition_restore, operationdetail );
delete partition_restore;
partition_restore = NULL;
operationdetail.get_last_child().set_success_and_capture_errors( rollback_success );
}
return success;
}