2008-03-14 14:07:01

by Josef Bacik

[permalink] [raw]
Subject: [PATCH] e2fsprogs: report minimal resize size when given size is too small

Hello,

Munged the subject on the last resend and didn't add the Signed-off-by, so
here's the 3rd try. This patch will spit out the minimum number of blocks the
fs can be resized if the resize size it is given is too small. This is usefull
for those creating live boot images who create large images to install what they
need onto, and then resize down to the smalles size that they can. Thanks much,

Signed-off-by: Josef Back <[email protected]>


diff --git a/resize/main.c b/resize/main.c
index 7c1d0c1..a9653ee 100644
--- a/resize/main.c
+++ b/resize/main.c
@@ -342,11 +342,6 @@ int main (int argc, char ** argv)
if (new_size_str) {
new_size = parse_num_blocks(new_size_str,
fs->super->s_log_block_size);
- if (!new_size) {
- com_err(program_name, 0, _("bad filesystem size - %s"),
- new_size_str);
- exit(1);
- }
} else {
new_size = max_size;
/* Round down to an even multiple of a pagesize */
diff --git a/resize/resize2fs.c b/resize/resize2fs.c
index 9959671..e2a8fcc 100644
--- a/resize/resize2fs.c
+++ b/resize/resize2fs.c
@@ -48,6 +48,7 @@ static errcode_t inode_ref_fix(ext2_resize_t rfs);
static errcode_t move_itables(ext2_resize_t rfs);
static errcode_t fix_resize_inode(ext2_filsys fs);
static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs);
+static blk_t ext2fs_calculate_minimum_resize_size(ext2_filsys fs);

/*
* Some helper CPP macros
@@ -63,7 +64,9 @@ static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs);
((blk) < (FS_INODE_TB((fs), (i)) + \
(fs)->inode_blocks_per_group)))

-
+#define META_OVERHEAD(fs) (2 + (fs)->inode_blocks_per_group)
+#define SUPER_OVERHEAD(fs) (1 + (fs)->desc_blocks +\
+ (fs)->super->s_reserved_gdt_blocks)

/*
* This is the top-level routine which does the dirty deed....
@@ -75,11 +78,19 @@ errcode_t resize_fs(ext2_filsys fs, blk_t *new_size, int flags,
{
ext2_resize_t rfs;
errcode_t retval;
+ blk_t min_size = 0;

retval = ext2fs_read_bitmaps(fs);
if (retval)
return retval;

+ min_size = ext2fs_calculate_minimum_resize_size(fs);
+ if (min_size > *new_size) {
+ printf("%u blocks is too small, minimum size is %u blocks\n",
+ *new_size, min_size);
+ return EXT2_ET_TOOSMALL;
+ }
+
/*
* Create the data structure
*/
@@ -1628,3 +1639,139 @@ static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs)
ext2fs_mark_super_dirty(fs);
return 0;
}
+
+/*
+ * calcluate the minimum number of blocks the given fs can be resized to
+ */
+static blk_t ext2fs_calculate_minimum_resize_size(ext2_filsys fs)
+{
+ blk_t inode_count, blks_needed, groups, blk, data_blocks;
+ blk_t grp, data_needed, last_start;
+ int overhead = 0, old_group = -1, num_of_superblocks = 0;
+
+ /*
+ * first figure out how many group descriptors we need to
+ * handle the number of inodes we have
+ */
+ inode_count = fs->super->s_inodes_count -
+ fs->super->s_free_inodes_count;
+ blks_needed = ext2fs_div_ceil(inode_count,
+ fs->super->s_inodes_per_group) *
+ EXT2_BLOCKS_PER_GROUP(fs->super);
+ groups = ext2fs_div_ceil(blks_needed,
+ EXT2_BLOCKS_PER_GROUP(fs->super));
+
+ /*
+ * we need to figure out how many backup superblocks we have so we can
+ * account for that in the metadata
+ */
+ for (grp = 0; grp < fs->group_desc_count; grp++) {
+ if (ext2fs_bg_has_super(fs, grp))
+ num_of_superblocks++;
+ }
+
+ /* calculate how many blocks are needed for data */
+ data_needed = fs->super->s_blocks_count -
+ fs->super->s_free_blocks_count;
+ data_needed -= SUPER_OVERHEAD(fs) * num_of_superblocks;
+ data_needed -= META_OVERHEAD(fs) * fs->group_desc_count;
+
+ /*
+ * figure out how many data blocks we have given the number of groups
+ * we need for our inodes
+ */
+ data_blocks = groups * EXT2_BLOCKS_PER_GROUP(fs->super);
+ last_start = 0;
+ for (grp = 0; grp < groups; grp++) {
+ overhead = META_OVERHEAD(fs);
+
+ if (ext2fs_bg_has_super(fs, grp))
+ overhead += SUPER_OVERHEAD(fs);
+
+ /*
+ * we want to keep track of how much data we can store in
+ * the groups leading up to the last group so we can determine
+ * how big the last group needs to be
+ */
+ if (grp != (groups - 1))
+ last_start += EXT2_BLOCKS_PER_GROUP(fs->super) -
+ overhead;
+
+ data_blocks -= overhead;
+ }
+
+ /*
+ * if we need more group descriptors in order to accomodate our data
+ * then we need to add them here
+ */
+ while (data_needed > data_blocks) {
+ blk_t remainder = data_needed - data_blocks;
+ blk_t extra_grps;
+
+ /* figure out how many more groups we need for the data */
+ extra_grps = ext2fs_div_ceil(remainder,
+ EXT2_BLOCKS_PER_GROUP(fs->super));
+
+ data_blocks += extra_grps * EXT2_BLOCKS_PER_GROUP(fs->super);
+
+ /* ok we have to account for the last group */
+ overhead = META_OVERHEAD(fs);
+ if (ext2fs_bg_has_super(fs, groups-1))
+ overhead += SUPER_OVERHEAD(fs);
+ last_start += EXT2_BLOCKS_PER_GROUP(fs->super) - overhead;
+
+ for (grp = groups; grp < groups+extra_grps; grp++) {
+ overhead = META_OVERHEAD(fs);
+ if (ext2fs_bg_has_super(fs, grp))
+ overhead += SUPER_OVERHEAD(fs);
+
+ /*
+ * again, we need to see how much data we cram into
+ * all of the groups leading up to the last group
+ */
+ if (grp != (groups + extra_grps - 1))
+ last_start += EXT2_BLOCKS_PER_GROUP(fs->super)
+ - overhead;
+
+ data_blocks -= overhead;
+ }
+
+ groups += extra_grps;
+ }
+
+ /* now for the fun voodoo */
+ overhead = META_OVERHEAD(fs);
+
+ /*
+ * if this is the case then the last group is going to have data in it
+ * so we need to adjust the size of the last group accordingly
+ */
+ if (last_start < data_needed) {
+ blk_t remainder = data_needed - last_start;
+
+ /*
+ * 50 is a magic number that mkfs/resize uses to see if its
+ * even worth making/resizing the fs. basically you need to
+ * have at least 50 blocks in addition to the blocks needed
+ * for the metadata in the last group
+ */
+ if (remainder > 50)
+ overhead += remainder;
+ else
+ overhead += 50;
+ } else
+ overhead += 50;
+
+ if (ext2fs_bg_has_super(fs, groups-1))
+ overhead += SUPER_OVERHEAD(fs);
+
+ /*
+ * since our last group doesn't have to be BLOCKS_PER_GROUP large, we
+ * only do groups-1, and then add the number of blocks needed to
+ * handle the group descriptor metadata+data that we need
+ */
+ blks_needed = (groups-1) * EXT2_BLOCKS_PER_GROUP(fs->super);
+ blks_needed += overhead;
+
+ return blks_needed;
+}


2008-03-15 03:08:50

by Theodore Ts'o

[permalink] [raw]
Subject: Re: [PATCH] e2fsprogs: report minimal resize size when given size is too small

On Fri, Mar 14, 2008 at 10:05:06AM -0400, Josef Bacik wrote:
> Hello,
>
> Munged the subject on the last resend and didn't add the
> Signed-off-by, so here's the 3rd try. This patch will spit out the
> minimum number of blocks the fs can be resized if the resize size it
> is given is too small. This is usefull for those creating live boot
> images who create large images to install what they need onto, and
> then resize down to the smalles size that they can. Thanks much,
>
> Signed-off-by: Josef Back <[email protected]>

Hmm.... so I tried creating a filesystem using a 1k blocksize, to make
sure that your code does the right thing when the number of block
group descriptors goes down. And it doesn't work correctly.

So first, I create a logical volume for doing testing, with a 1024
blocksize, and populate it with e2fsprogs sources. The we run e2fsck
so that reszie2fs will be able to run with it:

# lvcreate --size 1G --name testresize /dev/closure
Logical volume "testresize" created
# mke2fs -b 1024 -jq /dev/closure/testresize
Warning: 256-byte inodes not usable on older systems
# mount /dev/closure/testresize /mnt
# tar -C /mnt -xzf /usr/projects/e2fsprogs/e2fsprogs-1.40.8.tar.gz
# umount /mnt
# e2fsck -fp /dev/closure/testresize
e2fsck 1.40.8 (13-Mar-2008)
/dev/closure/testresize: 1177/65536 files (0.9% non-contiguous), 72811/1048576 blocks

OK, let's create s snapshot, and then try resizing it using your
patched resize2fs:

# lvcreate --snapshot --size 1G --name resizesnap /dev/closure/testresize
Logical volume "resizesnap" created
# ./resize/resize2fs /dev/closure/resizesnap 0
resize2fs 1.40.7 (28-Feb-2008)
Resizing the filesystem on /dev/closure/resizesnap to 0 (1k) blocks.
0 blocks is too small, minimum size is 55254 blocks
./resize/resize2fs: Not enough space to build proposed filesystem while trying to resize /dev/closure/resizesnap
# ./resize/resize2fs /dev/closure/resizesnap 55254
resize2fs 1.40.7 (28-Feb-2008)
Resizing the filesystem on /dev/closure/resizesnap to 55254 (1k) blocks.
The filesystem on /dev/closure/resizesnap is now 55254 blocks long.

OK, so we resized it down to 55,254. Are we done?

# ./resize/resize2fs /dev/closure/resizesnap 0
resize2fs 1.40.7 (28-Feb-2008)
Resizing the filesystem on /dev/closure/resizesnap to 0 (1k) blocks.
0 blocks is too small, minimum size is 55242 blocks
# ./resize/resize2fs /dev/closure/resizesnap 55242
resize2fs 1.40.7 (28-Feb-2008)
Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
The filesystem on /dev/closure/resizesnap is now 55242 blocks long.
# lvremove -f /dev/closure/resizesnap
Logical volume "resizesnap" successfully removed

No wait! It turns out we can resize it down further, to 55,242
blocks.

Now let's try to resize it down to 55,242 blocks directly using an
unpatched resize2fs:

# lvcreate --snapshot --size 1G --name resizesnap /dev/closure/testresize
Logical volume "resizesnap" created
# /sbin/resize2fs /dev/closure/resizesnap 55242
resize2fs 1.40.8 (13-Mar-2008)
Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
The filesystem on /dev/closure/resizesnap is now 55242 blocks long.
# lvremove -f /dev/closure/resizesnap
Logical volume "resizesnap" successfully removed

Gee, look! It works.

Can we do it with your patched resize2fs?

# lvcreate --snapshot --size 1G --name resizesnap /dev/closure/testresize
Logical volume "resizesnap" created
# ./resize/resize2fs /dev/closure/resizesnap 55242
resize2fs 1.40.7 (28-Feb-2008)
Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
55242 blocks is too small, minimum size is 55254 blocks
./resize/resize2fs: Not enough space to build proposed filesystem while trying to resize /dev/closure/resizesnap

Nope. It won't let you resize down to 55,254 blocks in one go,
although you can do it in two steps. This is a regression, since with
the unpatched resize2fs, it works with one step. The problem is that
your code assumes that ext2fs_calculate_minimum_resize_size() reliably
returns the minimum size, and if the user specifies a size smaller
than returned size, it should bomb out with an error. Unfortunately,
it *isn't* the minimum possible size.

# ./resize/resize2fs /dev/closure/resizesnap 55254
resize2fs 1.40.7 (28-Feb-2008)
Resizing the filesystem on /dev/closure/resizesnap to 55254 (1k) blocks.
The filesystem on /dev/closure/resizesnap is now 55254 blocks long.
# ./resize/resize2fs /dev/closure/resizesnap 55242
resize2fs 1.40.7 (28-Feb-2008)
Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
The filesystem on /dev/closure/resizesnap is now 55242 blocks long.
# lvremove -f /dev/closure/resizesnap
Logical volume "resizesnap" successfully removed


What bothers me with your patch, though is that even though it
apparently isn't doing the right thing when the filesystem changes the
number of group descriptor blocks after doing the resize:

# dumpe2fs /dev/closure/testresize | grep "Group descriptors" | head -5
dumpe2fs 1.40.8 (13-Mar-2008)
Primary superblock at 1, Group descriptors at 2-5
Backup superblock at 8193, Group descriptors at 8194-8197
Backup superblock at 24577, Group descriptors at 24578-24581
Backup superblock at 40961, Group descriptors at 40962-40965
Backup superblock at 57345, Group descriptors at 57346-57349

# dumpe2fs /dev/closure/resizesnap | grep "Group descriptors" | head -5
dumpe2fs 1.40.8 (13-Mar-2008)
Primary superblock at 1, Group descriptors at 2-2
Backup superblock at 8193, Group descriptors at 8194-8194
Backup superblock at 24577, Group descriptors at 24578-24578
Backup superblock at 40961, Group descriptors at 40962-40962

.... yet when I tried doing the test with 4k blocksizes, where I
started with a filesystem which is 17 gigabytes (with a 4k blocksize,
every 16gigs there are 128 block groups, which will occupy a single 4k
block's worth of block group descriptors), and then shrunk it down,
expecting it to break --- and it didn't. The fact it should have
broken, since the number of group descriptors did go down, and it the
code didn't take that into account --- and yet it didn't, disturbed
me. Not knowing why it works when an inspection seems to indicate
that it should fails always scares me.

So I can't apply this the way it is. What I *can* do is set it up so
that it will only call the calculate minimum size if the specified
size is 0 blocks. I'll mention in the known bugs that it can
sometimes be wrong with its estimates with 1k and 2k block
filesystems.

- Ted

2008-03-15 05:15:40

by Theodore Ts'o

[permalink] [raw]
Subject: [PATCH] resize2fs: Add options to print (and resizing to) the minimum filesystem size

From: Josef Bacik <[email protected]>

Add the -P option to print the minimum filesystem size and exit.

Add the -M option to force resizing the filesystem to the minimum
filesystem size.

Signed-off-by: Josef Back <[email protected]>
Signed-off-by: "Theodore Ts'o" <[email protected]>
---
resize/main.c | 29 +++++++---
resize/resize2fs.8.in | 47 +++++++++--------
resize/resize2fs.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++-
resize/resize2fs.h | 1 +
4 files changed, 185 insertions(+), 32 deletions(-)

diff --git a/resize/main.c b/resize/main.c
index f283e41..0cdda32 100644
--- a/resize/main.c
+++ b/resize/main.c
@@ -36,8 +36,8 @@ char *program_name, *device_name, *io_options;

static void usage (char *prog)
{
- fprintf (stderr, _("Usage: %s [-d debug_flags] [-f] [-F] [-p] "
- "device [new_size]\n\n"), prog);
+ fprintf (stderr, _("Usage: %s [-d debug_flags] [-f] [-F] [-M] [-P] "
+ "[-p] device [new_size]\n\n"), prog);

exit (1);
}
@@ -152,6 +152,8 @@ int main (int argc, char ** argv)
int flush = 0;
int force = 0;
int io_flags = 0;
+ int force_min_size = 0;
+ int print_min_size = 0;
int fd, ret;
blk_t new_size = 0;
blk_t max_size = 0;
@@ -183,7 +185,7 @@ int main (int argc, char ** argv)
if (argc && *argv)
program_name = *argv;

- while ((c = getopt (argc, argv, "d:fFhpS:")) != EOF) {
+ while ((c = getopt (argc, argv, "d:fFhMPpS:")) != EOF) {
switch (c) {
case 'h':
usage(program_name);
@@ -194,6 +196,12 @@ int main (int argc, char ** argv)
case 'F':
flush = 1;
break;
+ case 'M':
+ force_min_size = 1;
+ break;
+ case 'P':
+ print_min_size = 1;
+ break;
case 'd':
flags |= atoi(optarg);
break;
@@ -308,6 +316,12 @@ int main (int argc, char ** argv)
exit(1);
}

+ if (print_min_size) {
+ printf("Estimated minimum size of the filesystem: %lu\n",
+ calculate_minimum_resize_size(fs));
+ exit(0);
+ }
+
/* Determine the system page size if possible */
#ifdef HAVE_SYSCONF
#if (!defined(_SC_PAGESIZE) && defined(_SC_PAGE_SIZE))
@@ -332,14 +346,11 @@ int main (int argc, char ** argv)
_("while trying to determine filesystem size"));
exit(1);
}
- if (new_size_str) {
+ if (force_min_size)
+ new_size = calculate_minimum_resize_size(fs);
+ else if (new_size_str) {
new_size = parse_num_blocks(new_size_str,
fs->super->s_log_block_size);
- if (!new_size) {
- com_err(program_name, 0, _("bad filesystem size - %s"),
- new_size_str);
- exit(1);
- }
} else {
new_size = max_size;
/* Round down to an even multiple of a pagesize */
diff --git a/resize/resize2fs.8.in b/resize/resize2fs.8.in
index d43adad..7be4f04 100644
--- a/resize/resize2fs.8.in
+++ b/resize/resize2fs.8.in
@@ -8,6 +8,9 @@ resize2fs \- ext2/ext3 file system resizer
.SH SYNOPSIS
.B resize2fs
[
+.B \-fFpPM
+]
+[
.B \-d
.I debug-flags
]
@@ -15,15 +18,6 @@ resize2fs \- ext2/ext3 file system resizer
.B \-S
.I RAID-stride
]
-[
-.B \-f
-]
-[
-.B \-F
-]
-[
-.B \-p
-]
.I device
[
.I size
@@ -99,19 +93,6 @@ from the following list:
\ 8\ \-\ Debug inode relocations
.br
\ 16\ \-\ Debug moving the inode table
-.TP
-.B \-S \fIRAID-stride
-The
-.B resize2fs
-program will heuristically determine the RAID stride that was specified
-when the filesystem was created. This option allows the user to
-explicitly specify a RAID stride setting to be used by resize2fs instead.
-.TP
-.B \-p
-Prints out a percentage completion bars for each
-.B resize2fs
-operation, so that the user can keep track of what
-the program is doing.
.TP
.B \-f
Forces resize2fs to proceed with the filesystem resize operation, overriding
@@ -122,6 +103,28 @@ Flush the filesystem device's buffer caches before beginning. Only
really useful for doing
.B resize2fs
time trials.
+.TP
+.B \-M
+Shrink the filesystem to the minimum size.
+.TP
+.B \-p
+Prints out a percentage completion bars for each
+.B resize2fs
+operation, so that the user can keep track of what
+the program is doing.
+.TP
+.B \-P
+Print the minimum size of the filesystem and exit.
+.TP
+.B \-S \fIRAID-stride
+The
+.B resize2fs
+program will heuristically determine the RAID stride that was specified
+when the filesystem was created. This option allows the user to
+explicitly specify a RAID stride setting to be used by resize2fs instead.
+.SH KNOWN BUGS
+The minimum size of the filesystem as estimated by resize2fs may be
+incorrect, especially for filesystems with 1k and 2k blocksizes.
.SH AUTHOR
.B resize2fs
was written by Theodore Ts'o <[email protected]>.
diff --git a/resize/resize2fs.c b/resize/resize2fs.c
index da86ece..08dd413 100644
--- a/resize/resize2fs.c
+++ b/resize/resize2fs.c
@@ -63,7 +63,9 @@ static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs);
((blk) < (FS_INODE_TB((fs), (i)) + \
(fs)->inode_blocks_per_group)))

-
+#define META_OVERHEAD(fs) (2 + (fs)->inode_blocks_per_group)
+#define SUPER_OVERHEAD(fs) (1 + (fs)->desc_blocks +\
+ (fs)->super->s_reserved_gdt_blocks)

/*
* This is the top-level routine which does the dirty deed....
@@ -1620,3 +1622,139 @@ static errcode_t ext2fs_calculate_summary_stats(ext2_filsys fs)
ext2fs_mark_super_dirty(fs);
return 0;
}
+
+/*
+ * calcluate the minimum number of blocks the given fs can be resized to
+ */
+blk_t calculate_minimum_resize_size(ext2_filsys fs)
+{
+ blk_t inode_count, blks_needed, groups, blk, data_blocks;
+ blk_t grp, data_needed, last_start;
+ int overhead = 0, old_group = -1, num_of_superblocks = 0;
+
+ /*
+ * first figure out how many group descriptors we need to
+ * handle the number of inodes we have
+ */
+ inode_count = fs->super->s_inodes_count -
+ fs->super->s_free_inodes_count;
+ blks_needed = ext2fs_div_ceil(inode_count,
+ fs->super->s_inodes_per_group) *
+ EXT2_BLOCKS_PER_GROUP(fs->super);
+ groups = ext2fs_div_ceil(blks_needed,
+ EXT2_BLOCKS_PER_GROUP(fs->super));
+
+ /*
+ * we need to figure out how many backup superblocks we have so we can
+ * account for that in the metadata
+ */
+ for (grp = 0; grp < fs->group_desc_count; grp++) {
+ if (ext2fs_bg_has_super(fs, grp))
+ num_of_superblocks++;
+ }
+
+ /* calculate how many blocks are needed for data */
+ data_needed = fs->super->s_blocks_count -
+ fs->super->s_free_blocks_count;
+ data_needed -= SUPER_OVERHEAD(fs) * num_of_superblocks;
+ data_needed -= META_OVERHEAD(fs) * fs->group_desc_count;
+
+ /*
+ * figure out how many data blocks we have given the number of groups
+ * we need for our inodes
+ */
+ data_blocks = groups * EXT2_BLOCKS_PER_GROUP(fs->super);
+ last_start = 0;
+ for (grp = 0; grp < groups; grp++) {
+ overhead = META_OVERHEAD(fs);
+
+ if (ext2fs_bg_has_super(fs, grp))
+ overhead += SUPER_OVERHEAD(fs);
+
+ /*
+ * we want to keep track of how much data we can store in
+ * the groups leading up to the last group so we can determine
+ * how big the last group needs to be
+ */
+ if (grp != (groups - 1))
+ last_start += EXT2_BLOCKS_PER_GROUP(fs->super) -
+ overhead;
+
+ data_blocks -= overhead;
+ }
+
+ /*
+ * if we need more group descriptors in order to accomodate our data
+ * then we need to add them here
+ */
+ while (data_needed > data_blocks) {
+ blk_t remainder = data_needed - data_blocks;
+ blk_t extra_grps;
+
+ /* figure out how many more groups we need for the data */
+ extra_grps = ext2fs_div_ceil(remainder,
+ EXT2_BLOCKS_PER_GROUP(fs->super));
+
+ data_blocks += extra_grps * EXT2_BLOCKS_PER_GROUP(fs->super);
+
+ /* ok we have to account for the last group */
+ overhead = META_OVERHEAD(fs);
+ if (ext2fs_bg_has_super(fs, groups-1))
+ overhead += SUPER_OVERHEAD(fs);
+ last_start += EXT2_BLOCKS_PER_GROUP(fs->super) - overhead;
+
+ for (grp = groups; grp < groups+extra_grps; grp++) {
+ overhead = META_OVERHEAD(fs);
+ if (ext2fs_bg_has_super(fs, grp))
+ overhead += SUPER_OVERHEAD(fs);
+
+ /*
+ * again, we need to see how much data we cram into
+ * all of the groups leading up to the last group
+ */
+ if (grp != (groups + extra_grps - 1))
+ last_start += EXT2_BLOCKS_PER_GROUP(fs->super)
+ - overhead;
+
+ data_blocks -= overhead;
+ }
+
+ groups += extra_grps;
+ }
+
+ /* now for the fun voodoo */
+ overhead = META_OVERHEAD(fs);
+
+ /*
+ * if this is the case then the last group is going to have data in it
+ * so we need to adjust the size of the last group accordingly
+ */
+ if (last_start < data_needed) {
+ blk_t remainder = data_needed - last_start;
+
+ /*
+ * 50 is a magic number that mkfs/resize uses to see if its
+ * even worth making/resizing the fs. basically you need to
+ * have at least 50 blocks in addition to the blocks needed
+ * for the metadata in the last group
+ */
+ if (remainder > 50)
+ overhead += remainder;
+ else
+ overhead += 50;
+ } else
+ overhead += 50;
+
+ if (ext2fs_bg_has_super(fs, groups-1))
+ overhead += SUPER_OVERHEAD(fs);
+
+ /*
+ * since our last group doesn't have to be BLOCKS_PER_GROUP large, we
+ * only do groups-1, and then add the number of blocks needed to
+ * handle the group descriptor metadata+data that we need
+ */
+ blks_needed = (groups-1) * EXT2_BLOCKS_PER_GROUP(fs->super);
+ blks_needed += overhead;
+
+ return blks_needed;
+}
diff --git a/resize/resize2fs.h b/resize/resize2fs.h
index f87d04e..49b77d8 100644
--- a/resize/resize2fs.h
+++ b/resize/resize2fs.h
@@ -129,6 +129,7 @@ extern errcode_t resize_fs(ext2_filsys fs, blk_t *new_size, int flags,

extern errcode_t adjust_fs_info(ext2_filsys fs, ext2_filsys old_fs,
blk_t new_size);
+extern blk_t calculate_minimum_resize_size(ext2_filsys fs);


/* extent.c */
--
1.5.4.1.144.gdfee-dirty


2008-03-17 16:07:14

by Josef Bacik

[permalink] [raw]
Subject: Re: [PATCH] e2fsprogs: report minimal resize size when given size is too small

On Fri, Mar 14, 2008 at 11:08:40PM -0400, Theodore Tso wrote:
> On Fri, Mar 14, 2008 at 10:05:06AM -0400, Josef Bacik wrote:
> > Hello,
> >
> > Munged the subject on the last resend and didn't add the
> > Signed-off-by, so here's the 3rd try. This patch will spit out the
> > minimum number of blocks the fs can be resized if the resize size it
> > is given is too small. This is usefull for those creating live boot
> > images who create large images to install what they need onto, and
> > then resize down to the smalles size that they can. Thanks much,
> >
> > Signed-off-by: Josef Back <[email protected]>
>
> Hmm.... so I tried creating a filesystem using a 1k blocksize, to make
> sure that your code does the right thing when the number of block
> group descriptors goes down. And it doesn't work correctly.
>
> So first, I create a logical volume for doing testing, with a 1024
> blocksize, and populate it with e2fsprogs sources. The we run e2fsck
> so that reszie2fs will be able to run with it:
>
> # lvcreate --size 1G --name testresize /dev/closure
> Logical volume "testresize" created
> # mke2fs -b 1024 -jq /dev/closure/testresize
> Warning: 256-byte inodes not usable on older systems
> # mount /dev/closure/testresize /mnt
> # tar -C /mnt -xzf /usr/projects/e2fsprogs/e2fsprogs-1.40.8.tar.gz
> # umount /mnt
> # e2fsck -fp /dev/closure/testresize
> e2fsck 1.40.8 (13-Mar-2008)
> /dev/closure/testresize: 1177/65536 files (0.9% non-contiguous), 72811/1048576 blocks
>
> OK, let's create s snapshot, and then try resizing it using your
> patched resize2fs:
>
> # lvcreate --snapshot --size 1G --name resizesnap /dev/closure/testresize
> Logical volume "resizesnap" created
> # ./resize/resize2fs /dev/closure/resizesnap 0
> resize2fs 1.40.7 (28-Feb-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 0 (1k) blocks.
> 0 blocks is too small, minimum size is 55254 blocks
> ./resize/resize2fs: Not enough space to build proposed filesystem while trying to resize /dev/closure/resizesnap
> # ./resize/resize2fs /dev/closure/resizesnap 55254
> resize2fs 1.40.7 (28-Feb-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 55254 (1k) blocks.
> The filesystem on /dev/closure/resizesnap is now 55254 blocks long.
>
> OK, so we resized it down to 55,254. Are we done?
>
> # ./resize/resize2fs /dev/closure/resizesnap 0
> resize2fs 1.40.7 (28-Feb-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 0 (1k) blocks.
> 0 blocks is too small, minimum size is 55242 blocks
> # ./resize/resize2fs /dev/closure/resizesnap 55242
> resize2fs 1.40.7 (28-Feb-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
> The filesystem on /dev/closure/resizesnap is now 55242 blocks long.
> # lvremove -f /dev/closure/resizesnap
> Logical volume "resizesnap" successfully removed
>
> No wait! It turns out we can resize it down further, to 55,242
> blocks.
>
> Now let's try to resize it down to 55,242 blocks directly using an
> unpatched resize2fs:
>
> # lvcreate --snapshot --size 1G --name resizesnap /dev/closure/testresize
> Logical volume "resizesnap" created
> # /sbin/resize2fs /dev/closure/resizesnap 55242
> resize2fs 1.40.8 (13-Mar-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
> The filesystem on /dev/closure/resizesnap is now 55242 blocks long.
> # lvremove -f /dev/closure/resizesnap
> Logical volume "resizesnap" successfully removed
>
> Gee, look! It works.
>
> Can we do it with your patched resize2fs?
>
> # lvcreate --snapshot --size 1G --name resizesnap /dev/closure/testresize
> Logical volume "resizesnap" created
> # ./resize/resize2fs /dev/closure/resizesnap 55242
> resize2fs 1.40.7 (28-Feb-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
> 55242 blocks is too small, minimum size is 55254 blocks
> ./resize/resize2fs: Not enough space to build proposed filesystem while trying to resize /dev/closure/resizesnap
>
> Nope. It won't let you resize down to 55,254 blocks in one go,
> although you can do it in two steps. This is a regression, since with
> the unpatched resize2fs, it works with one step. The problem is that
> your code assumes that ext2fs_calculate_minimum_resize_size() reliably
> returns the minimum size, and if the user specifies a size smaller
> than returned size, it should bomb out with an error. Unfortunately,
> it *isn't* the minimum possible size.
>
> # ./resize/resize2fs /dev/closure/resizesnap 55254
> resize2fs 1.40.7 (28-Feb-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 55254 (1k) blocks.
> The filesystem on /dev/closure/resizesnap is now 55254 blocks long.
> # ./resize/resize2fs /dev/closure/resizesnap 55242
> resize2fs 1.40.7 (28-Feb-2008)
> Resizing the filesystem on /dev/closure/resizesnap to 55242 (1k) blocks.
> The filesystem on /dev/closure/resizesnap is now 55242 blocks long.
> # lvremove -f /dev/closure/resizesnap
> Logical volume "resizesnap" successfully removed
>
>
> What bothers me with your patch, though is that even though it
> apparently isn't doing the right thing when the filesystem changes the
> number of group descriptor blocks after doing the resize:
>
> # dumpe2fs /dev/closure/testresize | grep "Group descriptors" | head -5
> dumpe2fs 1.40.8 (13-Mar-2008)
> Primary superblock at 1, Group descriptors at 2-5
> Backup superblock at 8193, Group descriptors at 8194-8197
> Backup superblock at 24577, Group descriptors at 24578-24581
> Backup superblock at 40961, Group descriptors at 40962-40965
> Backup superblock at 57345, Group descriptors at 57346-57349
>
> # dumpe2fs /dev/closure/resizesnap | grep "Group descriptors" | head -5
> dumpe2fs 1.40.8 (13-Mar-2008)
> Primary superblock at 1, Group descriptors at 2-2
> Backup superblock at 8193, Group descriptors at 8194-8194
> Backup superblock at 24577, Group descriptors at 24578-24578
> Backup superblock at 40961, Group descriptors at 40962-40962
>
> .... yet when I tried doing the test with 4k blocksizes, where I
> started with a filesystem which is 17 gigabytes (with a 4k blocksize,
> every 16gigs there are 128 block groups, which will occupy a single 4k
> block's worth of block group descriptors), and then shrunk it down,
> expecting it to break --- and it didn't. The fact it should have
> broken, since the number of group descriptors did go down, and it the
> code didn't take that into account --- and yet it didn't, disturbed
> me. Not knowing why it works when an inspection seems to indicate
> that it should fails always scares me.
>
> So I can't apply this the way it is. What I *can* do is set it up so
> that it will only call the calculate minimum size if the specified
> size is 0 blocks. I'll mention in the known bugs that it can
> sometimes be wrong with its estimates with 1k and 2k block
> filesystems.
>

So this is all very strange. I fixed my patch to let resize2fs do what it
always does whether or not my calculation thinks it will work so I could test
it. With 1k and 2k blocks you are right, when reducing the number of group
descriptors i can always go lower. However with 4k blocks (which is what I
always tested with) it works completely fine. For example, a completely cleanly
mkfs.ext3'd 15gb lv I get this

[root@unused resize]# ./resize2fs -P /dev/testvg/testext3
resize2fs 1.40.8 (13-Mar-2008)
Estimated minimum size of the filesystem: 35758

I try to resize to something smaller

[root@unused resize]# ./resize2fs /dev/testvg/testext3 35757
resize2fs 1.40.8 (13-Mar-2008)
Resizing the filesystem on /dev/testvg/testext3 to 35757 (4k) blocks.
./resize2fs: No space left on device while trying to resize /dev/testvg/testext3

No dice, it won't work, but using my calcluation it works fine

[root@unused resize]# ./resize2fs /dev/testvg/testext3 35758
resize2fs 1.40.8 (13-Mar-2008)
Resizing the filesystem on /dev/testvg/testext3 to 35758 (4k) blocks.
The filesystem on /dev/testvg/testext3 is now 35758 blocks long.

I'll try to figure out whats wrong in the 1k/2k block case, but for 4k it seems
to work fine. Thanks,

Josef