Re-add getting EXT2/3/4 free space from dumpe2fs as a fallback (#8)

If an EXT2/3/4 file system needs checking, then resize2fs will report an
error, rather than report the minimum file system size.

    # mkfs.ext4 /dev/sdb11
    # resize2fs -P /dev/sdb11
    resize2fs 1.42.9 (28-Dec-2013)
    Estimated minimum size of the filesystem: 17012
    # debugfs -w -R "ssv state 0" /dev/sdb11
    # resize2fs -P /dev/sdb11
    resize2fs 1.42.9 (28-Dec-2013)
    Please run 'e2fsck -f /dev/sdb11' first.

    # echo $?
    1

This will prevent GParted reading the file system usage and in turn
GParted won't allow the file system to be shrunk.  Re-add the previous
method of reading the free space from dumpe2fs output as a fallback.

With this change, the worst case scenario is that GParted allows the
user to attempt to shrink an unclean EXT4 file system, smaller that that
which resize2fs allows and gets an error telling them so.  As part of
the failed shrink operation GParted will have checked the file system so
on refresh GParted will get the correct minimum size next time.

This scenario only seems to apply to unclean EXT4 file systems because
resize2fs has a larger minimum size that the free blocks would suggest
because of extra space requirements when resizing EXT4 file systems [1].

[1] e2fsprogs 1.44.3, resize/resize2fs.c:calculate_minimum_resize_size()
    https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git/tree/resize/resize2fs.c?h=v1.44.3#n2946
    /*
     * For ext4 we need to allow for up to a flex_bg worth of
     * inode tables of slack space so the resize operation can be
     * guaranteed to finish.
     */

    /*
     * We need to reserve a few extra blocks if extents are
     * enabled, in case we need to grow the extent tree.  The more
     * we shrink the file system, the more space we need.
     *
     * The absolute worst case is every single data block is in
     * the part of the file system that needs to be evacuated,
     * with each data block needs to be in its own extent, and
     * with each inode needing at least one extent block.
     */

Closes #8 - Shrinking an EXT4 partition does not respect resize2fs
            limits
This commit is contained in:
Mike Fleetwood 2018-07-30 21:04:14 +01:00
parent fe83f6290f
commit ed17982eb3
1 changed files with 19 additions and 4 deletions

View File

@ -77,6 +77,10 @@ FS ext2::get_filesystem_support()
if ( ! Glib::find_program_in_path( "dumpe2fs").empty() )
{
// Resize2fs is preferred, but not required, to determine the minimum FS
// size. Can fall back to using dumpe2fs instead. Dumpe2fs is required
// for reading FS size and FS block size. See ext2::set_used_sectors()
// implementation for details.
fs.read = FS::EXTERNAL;
fs.online_read = FS::EXTERNAL;
}
@ -146,7 +150,7 @@ void ext2::set_used_sectors( Partition & partition )
// the file system size from the on disk superblock using dumpe2fs to
// avoid overhead subtraction. When mounted read the free space from
// the kernel via the statvfs() system call. When unmounted read the
// free space using resize2fs itself.
// free space using resize2fs itself falling back to using dumpe2fs.
if ( ! Utils::execute_command( "dumpe2fs -h " + Glib::shell_quote( partition.get_path() ),
output, error, true ) )
{
@ -180,14 +184,25 @@ void ext2::set_used_sectors( Partition & partition )
// Resize2fs won't shrink a file system smaller than it's own
// estimated minimum size, so use that to derive the free space.
N = -1;
Glib::ustring output2;
Glib::ustring error2;
if ( ! Utils::execute_command( "resize2fs -P " + Glib::shell_quote( partition.get_path() ),
output, error, true ) )
output2, error2, true ) )
{
if ( sscanf( output.c_str(), "Estimated minimum size of the filesystem: %lld", &N ) == 1 )
if ( sscanf( output2.c_str(), "Estimated minimum size of the filesystem: %lld", &N ) == 1 )
N = T - N;
}
if ( N == -1 && ! error.empty() )
// Resize2fs can fail reporting please run fsck first. Fall back
// to reading dumpe2fs output for free space.
if ( N == -1 )
{
index = output.find( "Free blocks:" );
if ( index < output.length() )
sscanf( output.substr( index ).c_str(), "Free blocks: %lld", &N );
}
if ( N == -1 && ! error2.empty() )
partition.push_back_message( error );
}