[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20260129221637.459315-1-daniel.mazon@proton.me>
Date: Thu, 29 Jan 2026 22:18:27 +0000
From: Daniel Mazon <daniel.mazon@...ton.me>
To: linux-ext4@...r.kernel.org
Cc: Daniel Mazon <daniel.mazon@...ton.me>
Subject: [PATCH] resize2fs: implement option to modify the inode count
Hello,
This patch creates a new operation in "resize2fs" to modify the inode count of
an existing ext4 filesystem independently of the block count.
This is a continuation of this email:
https://lore.kernel.org/linux-ext4/20251223044519.GF42033@macsyma.lan/
Until now, the bytes-per-inode ratio was chosen at filesystem creation time,
and could not be modified afterwards. With this patch, it will be possible to
increase or decrease the inode count using resize2fs. This operation must be
done offline.
Some points:
- the functionality is integrated with no duplication of source code, and in a
single binary
- the existing tests "make fullcheck" are all ok
- also added two new tests specific to inode count modification
- I also integrated the suggestion about a signed argument: NNN inodes, +NNN
inodes or -NNN inodes
Technically, the operations are designed as follows:
- To reduce the inode count, we first calculate the new total count, and then
we translate all inodes above that number to lower numbers. This is done using
the existing functions that do the same when shrinking a filesystem. After
that, we move the inodes "in-place" to the new positions in the existing
itables. Finally, we free the remaining space of itables, place them
contiguosly in flex_bg systems, and update group stats. This approach should
work even if no free space is available in the fs.
- To increase the inode count, we allocate new larger inode tables, and move
the existing inodes to those tables. The inode tables must be X blocks
contiguous, so we may have a badly fragmented fs where no enough contiguous
free space exists for the new inode tables. In that case, we move some data
blocks to make room for the new itables. This is done using the existing
functions that move blocks when shrinking a filesystem. We may repeat these
operations several times if the free space is low, freeing the old inode tables
and allocating the new ones, until all inodes are migrated. Also, the group
stats are updated.
I have tried to minimize modifications to the existing source code. For most
functions it sufficed to add some specific 'if's when we are running an inode
change operation. Only the below two functions have larger changes:
- migrate_ea_block(): Before, this function was only called for the old_fs.
Now, we may need to call it also for the new_fs, so change the rfs parameter to
a pointer to the desired fs. Also, there was a check on the metadata_csum of
the new_fs in the previous version. AFAICT, the value of metadata_csum shall be
the same for both old and new, no resize2fs operation will change it, so now we
just check its value on the fs given as parameter.
- inode_scan_and_fix(): Before, this function used a ext2_inode_scan structure
to scan all inodes in the filesystem and update inode numbers and blocks. This
structure was associated with the old_fs only. We may now need to do the same
with the new_fs, so I replaced the ext2_inode_scan with a simple 'for' loop so
we can read each inode from a different fs. I replicated the calls to
progress_callback inside the loop, with the same conditions as in the scan.
This allows to get the same results for the existing tests, which remain
unmodified. When running the tests in my pc, replacing the inode_scan with a
simple loop had no performance penalty, and the tests results are still ok.
I tested the patch sending it to myself first. When applying it, there is a
warning message about trailing white space, but I think it is related only to
the "expected" stdout in the new tests. This is my first contribution to such a
big project, so I guess there will be many things to improve :)
Regards,
Daniel
Signed-off-by: Daniel Mazon <daniel.mazon@...ton.me>
---
resize/main.c | 507 +++++++++-
resize/resize2fs.c | 951 +++++++++++++++++-
resize/resize2fs.h | 22 +-
.../expect | 41 +
.../name | 1 +
.../script | 76 ++
.../r_inode_count_increase_almost_full/expect | 35 +
tests/r_inode_count_increase_almost_full/name | 1 +
.../r_inode_count_increase_almost_full/script | 67 ++
9 files changed, 1644 insertions(+), 57 deletions(-)
create mode 100644 tests/r_inode_count_decrease_translate_number/expect
create mode 100644 tests/r_inode_count_decrease_translate_number/name
create mode 100644 tests/r_inode_count_decrease_translate_number/script
create mode 100644 tests/r_inode_count_increase_almost_full/expect
create mode 100644 tests/r_inode_count_increase_almost_full/name
create mode 100644 tests/r_inode_count_increase_almost_full/script
diff --git a/resize/main.c b/resize/main.c
index 08a4bbaf..c5dad457 100644
--- a/resize/main.c
+++ b/resize/main.c
@@ -41,14 +41,18 @@ extern int optind;
#include "../version.h"
+#ifdef __linux__ /* Kludge for debugging */
+#define RESIZE2FS_DEBUG
+#endif
+
char *program_name;
static char *device_name, *io_options;
static void usage (char *prog)
{
fprintf (stderr, _("Usage: %s [-d debug_flags] [-f] [-F] [-M] [-P] "
- "[-p] device [-b|-s|new_size] [-S RAID-stride] "
- "[-z undo_file]\n\n"),
+ "[-p] device [-b|-s|-i [+-]inode_count|new_size] "
+ "[-S RAID-stride] [-z undo_file]\n\n"),
prog ? prog : "resize2fs");
exit (1);
@@ -244,6 +248,403 @@ err:
return retval;
}
+/*
+ * Check corner case: we want to increase the inode table in
+ * a filesystem with no flex_bg and a very small last group
+ */
+static errcode_t check_space_last_group(ext2_filsys fs,
+ unsigned int inode_blocks_per_group)
+{
+
+ ext2fs_block_bitmap meta_bmap = NULL;
+ blk64_t b;
+ errcode_t retval = 0;
+ unsigned int movable_blocks = 0;
+ ext2_badblocks_list badblock_list = 0;
+
+ retval =
+ ext2fs_allocate_block_bitmap(fs, _("meta-data blocks"), &meta_bmap);
+ if (retval)
+ goto errout;
+
+ retval = mark_table_blocks(fs, meta_bmap);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_read_bb_inode(fs, &badblock_list);
+ if (retval) {
+ fprintf(stderr,
+ "Error while reading badblock list\n");
+ goto errout;
+ }
+
+ for (b = ext2fs_group_first_block2(fs, fs->group_desc_count - 1);
+ b < ext2fs_blocks_count(fs->super); b++) {
+ if (!ext2fs_test_block_bitmap2(meta_bmap, b)
+ && !ext2fs_badblocks_list_test(badblock_list, b))
+ movable_blocks++;
+ }
+
+ if (movable_blocks < inode_blocks_per_group) {
+ fprintf(stderr,"************ STOP ************\n");
+ if (!ext2fs_has_feature_flex_bg(fs->super))
+ fprintf(stderr,
+ "The filesystem flex_bg feature is not set\n");
+ if (!fs->super->s_log_groups_per_flex)
+ fprintf(stderr,"The value of s_log_groups_per_flex is "
+ "%u\n", fs->super->s_log_groups_per_flex);
+ fprintf(stderr,"The last group only has %u movable blocks\n",
+ movable_blocks);
+ fprintf(stderr, "This is not enough to allocate a new inode "
+ "table of %u blocks\n", inode_blocks_per_group);
+ fprintf(stderr, "Under these conditions, it is not possible "
+ "to continue.\n\n");
+ fprintf(stderr,"You may try first one of these options:\n");
+ fprintf(stderr," - Use debugfs to %s%s%s\n",
+ !ext2fs_has_feature_flex_bg(fs->
+ super) ?
+ "set the flex_bg feature " : "",
+ !ext2fs_has_feature_flex_bg(fs->super)
+ && !fs->super->s_log_groups_per_flex ? "and " : "",
+ !fs->super->s_log_groups_per_flex ?
+ "set a value of log_groups_per_flex to 4 in the "
+ "superblock (default mkfs value)"
+ : "");
+ fprintf(stderr, " - Use resize2fs to grow the filesystem by "
+ "at least %u blocks\n",
+ inode_blocks_per_group - movable_blocks);
+ if (fs->group_desc_count > 1)
+ fprintf(stderr,
+ " - Use resize2fs to shrink the filesystem to %llu "
+ "blocks, in order to get rid of the last group\n",
+ (blk64_t) EXT2_BLOCKS_PER_GROUP(fs->super) *
+ (fs->group_desc_count - 1));
+ fprintf(stderr, "After that, you can try again to change "
+ "the inode count\n");
+ retval = 1;
+ }
+
+ errout:
+ if (meta_bmap)
+ ext2fs_free_block_bitmap(meta_bmap);
+ if (badblock_list)
+ ext2fs_badblocks_list_free(badblock_list);
+
+ return retval;
+}
+
+static ext2_ino_t find_last_used_inode(ext2_filsys fs)
+{
+ ext2_ino_t ino_num = fs->super->s_inodes_count;
+ if (ext2fs_read_inode_bitmap(fs)) {
+ fprintf(stderr, "Error while reading inode bitmap\n");
+ exit(1);
+ }
+ while (ino_num && !ext2fs_test_inode_bitmap2(fs->inode_map, ino_num))
+ ino_num--;
+ return ino_num;
+}
+
+static ext2_ino_t parse_count_param(char *p, ext2_ino_t current_count)
+{
+ char type = 0;
+ unsigned long n;
+ ext2_ino_t res = 0;
+ ext2_ino_t MAX_INODE = 0xFFFFFFFF;
+
+ if (p == NULL || p[0] == 0 || p[1] == 0)
+ return 0;
+
+ if (p[0] == '-' && p[1] >= '0' && p[1] <= '9') {
+ p++;
+ type = '-';
+ } else if (p[0] == '+') {
+ type = '+';
+ }
+
+ n = strtoul(p, NULL, 0);
+
+ if (n > MAX_INODE || n == 0) {
+ fprintf(stderr, "invalid param %s\n", type == '-' ? --p : p);
+ return 0;
+ }
+
+ if (type == '-') {
+ if (current_count <= n)
+ fprintf(stderr, "the resulting count will be under 0 "
+ "for param: %s\n", --p);
+ else
+ res = current_count - n;
+ } else if (type == '+') {
+ if (current_count > MAX_INODE - n)
+ fprintf(stderr, "the resulting count will overflow "
+ "for param: %s\n", p);
+ else
+ res = current_count + n;
+ } else {
+ res = n;
+ }
+ return res;
+}
+
+static errcode_t calculate_new_inodes_per_group(ext2_filsys fs,
+ ext2_ino_t requested_count,
+ unsigned int *ipg, int force,
+ int flags)
+{
+
+ errcode_t retval = 0;
+ int inode_ratio,
+ blocksize = EXT2_BLOCK_SIZE(fs->super);
+ unsigned int new_inode_count,
+ new_inodes_per_group,
+ inode_blocks_per_group_rounded,
+ required_inodes,
+ max_inode_blocks_per_group;
+ blk64_t free_space,
+ current_inode_blocks_space,
+ new_inode_blocks_space,
+ safety_margin;
+
+ required_inodes = fs->super->s_inodes_count
+ - fs->super->s_free_inodes_count;
+ max_inode_blocks_per_group = blocksize * 8 * EXT2_INODE_SIZE(fs->super)
+ / blocksize;
+
+ /*in KiB */
+ current_inode_blocks_space = ((blk64_t) fs->inode_blocks_per_group)
+ * fs->group_desc_count * (blocksize / 1024);
+ free_space = ext2fs_free_blocks_count(fs->super) * (blocksize / 1024);
+
+#ifdef RESIZE2FS_DEBUG
+ if (flags & RESIZE_DEBUG_INODECOUNT) {
+ printf("Current inode blocks per group: %u\n",
+ fs->inode_blocks_per_group);
+ printf("Current inode count: %u\n", fs->super->s_inodes_count);
+ printf("Current inode ratio: %llu bytes-per-inode\n",
+ ext2fs_blocks_count(fs->super) * blocksize /
+ fs->super->s_inodes_count);
+ printf("Current inodes per group: %u\n",
+ fs->super->s_inodes_per_group);
+ printf("Current space used by inode tables: ");
+
+ if (current_inode_blocks_space > 1048576) {
+ printf("%.2f GiB\n",
+ (double)current_inode_blocks_space / 1048576);
+ } else if (current_inode_blocks_space > 1024) {
+ printf("%.2f MiB\n",
+ (double)current_inode_blocks_space / 1024);
+ } else {
+ printf("%.2f KiB\n",
+ (double)current_inode_blocks_space);
+ }
+
+ printf("\nInodes currently used by the filesystem: %u\n",
+ required_inodes);
+ printf("Current free space: ");
+ if (free_space > 1048576) {
+ printf("%.2f GiB\n", (double)free_space / 1048576);
+ } else if (free_space > 1024) {
+ printf("%.2f MiB\n", (double)free_space / 1024);
+ } else {
+ printf("%.2f KiB\n", (double)free_space);
+ }
+ printf("\nInode count requested by the user: %u\n\n",
+ requested_count);
+ }
+#endif
+
+ if (requested_count < EXT2_FIRST_INODE(fs->super) + 1) {
+ fprintf(stderr,
+ "The requested inode count is too low. Minimum is %u\n\n",
+ EXT2_FIRST_INODE(fs->super) + 1);
+ return 1;
+ }
+
+ new_inodes_per_group = ext2fs_div64_ceil(requested_count,
+ fs->group_desc_count);
+
+ /*
+ * Finally, make sure the number of inodes per group is a
+ * multiple of 8. This is needed to simplify the bitmap
+ * splicing code.
+ */
+ if (new_inodes_per_group < 8)
+ new_inodes_per_group = 8;
+ else {
+ if (new_inodes_per_group % 8) {
+ new_inodes_per_group &= ~7;
+ new_inodes_per_group += 8;
+ }
+ }
+ inode_blocks_per_group_rounded =
+ (((new_inodes_per_group * EXT2_INODE_SIZE(fs->super))
+ + blocksize - 1) / blocksize);
+
+ if (ext2fs_has_feature_bigalloc(fs->super)
+ && inode_blocks_per_group_rounded > fs->inode_blocks_per_group
+ && inode_blocks_per_group_rounded % EXT2FS_CLUSTER_RATIO(fs)) {
+
+ /*When increasing the inode count on a bigalloc fs, the
+ allocation functions will put each inode table in different
+ clusters, ie.: a given cluster cannot be shared by multiple
+ itables. Therefore, make sure the whole cluster is used,
+ otherwise the remaining blocks would be wasted. */
+
+ inode_blocks_per_group_rounded += (EXT2FS_CLUSTER_RATIO(fs) -
+ (inode_blocks_per_group_rounded % EXT2FS_CLUSTER_RATIO(fs)));
+#ifdef RESIZE2FS_DEBUG
+ if (flags & RESIZE_DEBUG_INODECOUNT)
+ printf(
+ "New inode blocks per group (after rounding to fill last "
+ "itable cluster): %u\n", inode_blocks_per_group_rounded);
+#endif
+
+ } else {
+#ifdef RESIZE2FS_DEBUG
+ if (flags & RESIZE_DEBUG_INODECOUNT)
+ printf(
+ "New inode blocks per group (after rounding to fill last "
+ "itable block): %u\n", inode_blocks_per_group_rounded);
+#endif
+ }
+
+ if (inode_blocks_per_group_rounded < EXT2FS_CLUSTER_RATIO(fs)) {
+ if (ext2fs_has_feature_bigalloc(fs->super)
+ && inode_blocks_per_group_rounded >
+ fs->inode_blocks_per_group) {
+ printf
+ ("inode_blocks_per_group was %u, forced to %i\n",
+ inode_blocks_per_group_rounded,
+ EXT2FS_CLUSTER_RATIO(fs));
+ inode_blocks_per_group_rounded =
+ EXT2FS_CLUSTER_RATIO(fs);
+ } else if (inode_blocks_per_group_rounded < 1) {
+ printf("inode_blocks_per_group was %u, forced to 1\n",
+ inode_blocks_per_group_rounded);
+ inode_blocks_per_group_rounded = 1;
+ }
+ } else {
+ if (inode_blocks_per_group_rounded > max_inode_blocks_per_group)
+ {
+ printf
+ ("inode_blocks_per_group was %u, forced to %u as "
+ "the remaining inodes would not be addressable in "
+ "the inode bitmap\n",inode_blocks_per_group_rounded,
+ max_inode_blocks_per_group);
+ inode_blocks_per_group_rounded =
+ max_inode_blocks_per_group;
+ }
+ }
+
+ if (fs->group_desc_count * ((blk64_t) inode_blocks_per_group_rounded
+ * blocksize / EXT2_INODE_SIZE(fs->super)) > 0xffffffff) {
+ fprintf(stderr,
+ "ERROR: the new inode count (%llu) is above the max "
+ "allowed value (%u)\n", fs->group_desc_count *
+ ((blk64_t) inode_blocks_per_group_rounded * blocksize /
+ EXT2_INODE_SIZE(fs->super)), 0xffffffff);
+ return 1;
+ }
+
+ new_inode_count = fs->group_desc_count *
+ (inode_blocks_per_group_rounded * blocksize
+ / EXT2_INODE_SIZE(fs->super));
+
+ if (new_inode_count < EXT2_FIRST_INODE(fs->super) + 1) {
+ fprintf(stderr, "The inode count is too low!\n");
+ return 1;
+ }
+
+ new_inodes_per_group = inode_blocks_per_group_rounded
+ * blocksize / EXT2_INODE_SIZE(fs->super);
+
+
+ if (new_inodes_per_group > EXT2_MAX_INODES_PER_GROUP(fs->super)) {
+ fprintf(stderr,
+ "ERROR: the new inodes per group is above the max "
+ "allowed value (%u)\n",
+ EXT2_MAX_INODES_PER_GROUP(fs->super));
+ return 1;
+ }
+
+
+ new_inode_blocks_space = ((blk64_t) inode_blocks_per_group_rounded)
+ * fs->group_desc_count * (blocksize / 1024);
+
+#ifdef RESIZE2FS_DEBUG
+ if (flags & RESIZE_DEBUG_INODECOUNT) {
+ printf("New inode count: %u\n", new_inode_count);
+ printf("New inode ratio: %llu bytes-per-inode\n",
+ ext2fs_blocks_count(fs->super) * blocksize
+ / new_inode_count);
+ printf("New inodes per group: %u\n", new_inodes_per_group);
+ printf("New space used by inode tables: ");
+ if (new_inode_blocks_space > 1048576) {
+ printf("%.2f GiB\n", (double)new_inode_blocks_space / 1048576);
+ } else if (new_inode_blocks_space > 1024) {
+ printf("%.2f MiB\n",
+ (double)new_inode_blocks_space / 1024);
+ } else {
+ printf("%.2f KiB\n",
+ (double)new_inode_blocks_space);
+ }
+ printf("\n");
+ }
+#endif
+
+ if (required_inodes > new_inode_count) {
+ fprintf(stderr, "The chosen inode count will not provide "
+ "enough inodes for the existing filesystem, please "
+ "choose a higher inode count\n");
+ return 1;
+ }
+
+ if (new_inode_count == fs->super->s_inodes_count) {
+ printf("The existing filesystem already has %u inodes. "
+ "No change needed.\n", new_inode_count);
+ *ipg = new_inodes_per_group;
+ return 0;
+ }
+
+ if (new_inode_count > fs->super->s_inodes_count) {
+ /* this safety_margin shall be much more than enough */
+ safety_margin = new_inode_blocks_space / 2;
+ if (new_inode_blocks_space + safety_margin > free_space) {
+ if (new_inode_blocks_space - current_inode_blocks_space
+ >= free_space) {
+ fprintf(stderr, "The free space in the "
+ "filesystem is too low to perform the change:"
+ "\nIt will not be possible to allocate large "
+ "enough inode tables for the chosen inode "
+ "count\n");
+ return 1;
+ }
+ printf
+ ("The filesystem doesn't have enough free space "
+ "to perform the change in a safe way.\n");
+ if (force) {
+ printf("As the force flag has been provided,"
+ " we will proceed with the change\n");
+ } else {
+ printf("Re-run with the force flag if you "
+ "want to try anyway.\n");
+ return 1;
+ }
+ }
+
+ if (!ext2fs_has_feature_flex_bg(fs->super)
+ || !fs->super->s_log_groups_per_flex) {
+ retval = check_space_last_group(fs,
+ inode_blocks_per_group_rounded);
+ }
+ }
+
+ *ipg = new_inodes_per_group;
+ return retval;
+
+}
+
int main (int argc, char ** argv)
{
errcode_t retval;
@@ -270,6 +671,9 @@ int main (int argc, char ** argv)
long sysval;
int len, mount_flags;
char *mtpt, *undo_file = NULL;
+ ext2_ino_t last_used_inode, new_inode_count = 0;
+ unsigned int new_inodes_per_group = 0;
+ char *inode_count_param = 0;
#ifdef ENABLE_NLS
setlocale(LC_MESSAGES, "");
@@ -288,7 +692,7 @@ int main (int argc, char ** argv)
else
usage(NULL);
- while ((c = getopt(argc, argv, "d:fFhMPpS:bsz:")) != EOF) {
+ while ((c = getopt(argc, argv, "d:fFhMPpS:bsz:i:")) != EOF) {
switch (c) {
case 'h':
usage(program_name);
@@ -323,6 +727,9 @@ int main (int argc, char ** argv)
case 'z':
undo_file = optarg;
break;
+ case 'i':
+ inode_count_param = optarg;
+ break;
default:
usage(program_name);
}
@@ -528,8 +935,9 @@ int main (int argc, char ** argv)
if (sys_page_size > blocksize)
new_size &= ~((blk64_t)((sys_page_size / blocksize)-1));
}
- /* If changing 64bit, don't change the filesystem size. */
- if (flags & (RESIZE_DISABLE_64BIT | RESIZE_ENABLE_64BIT)) {
+ /* If changing 64bit or the inode count, don't change the filesystem size. */
+ if ((flags & (RESIZE_DISABLE_64BIT | RESIZE_ENABLE_64BIT)) ||
+ inode_count_param != 0) {
new_size = ext2fs_blocks_count(fs->super);
}
if (!ext2fs_has_feature_64bit(fs->super)) {
@@ -628,6 +1036,72 @@ int main (int argc, char ** argv)
"feature.\n"));
goto errout;
}
+ } else if (inode_count_param != 0) {
+ if (mount_flags & EXT2_MF_MOUNTED) {
+ fprintf(stderr, _("Cannot change the inode count "
+ "while the filesystem is mounted.\n"));
+ goto errout;
+ }
+
+ new_inode_count = parse_count_param(inode_count_param,
+ fs->super->s_inodes_count);
+ if (!new_inode_count)
+ goto errout;
+
+ retval = calculate_new_inodes_per_group(fs, new_inode_count,
+ &new_inodes_per_group,
+ force, flags);
+ if (retval)
+ goto errout;
+ if (new_inodes_per_group == fs->super->s_inodes_per_group)
+ goto success_exit;
+
+ if (ext2fs_has_feature_stable_inodes(fs->super)) {
+ if (new_inodes_per_group >
+ fs->super->s_inodes_per_group) {
+ if (force) {
+ printf("Increasing inode count in a "
+ "filesystem with stable_inodes, because"
+ " the force flag is passed\n");
+ } else {
+ fprintf(stderr, "Asked to increase the "
+ "inode count in a filesystem with "
+ "stable_inodes feature flag.\nPlease "
+ "note it might not be possible to "
+ "reduce the inode count later because "
+ "of this flag.\nRestart with force "
+ "flag to proceed\n");
+ goto errout;
+ }
+ } else {
+ last_used_inode = find_last_used_inode(fs);
+ if (new_inodes_per_group *
+ fs->group_desc_count < last_used_inode) {
+ fprintf(stderr, "Cannot reduce inode "
+ "count in this filesystem because it "
+ "has the stable_inodes feature flag "
+ "and the used inode with the highest "
+ "number is %u, while the resulting "
+ "filesystem would have %u inodes.\n",
+ last_used_inode, new_inodes_per_group *
+ fs->group_desc_count);
+ goto errout;
+ }
+ fprintf(stderr, "Reducing inode count in a "
+ "filesystem with stable_inodes feature flag."
+ "\nThe used inode with the highest number is "
+ "%u. The resulting filesystem will have %u "
+ "inodes.\n", last_used_inode,
+ new_inodes_per_group * fs->group_desc_count);
+ }
+ }
+
+ flags |=
+ (new_inodes_per_group >
+ fs->super->s_inodes_per_group) ?
+ RESIZE_INCREASE_INODE_COUNT :
+ RESIZE_DECREASE_INODE_COUNT;
+
} else {
adjust_new_size(fs, &new_size);
if (new_size == ext2fs_blocks_count(fs->super)) {
@@ -639,6 +1113,12 @@ int main (int argc, char ** argv)
goto success_exit;
}
}
+ if (flags & (RESIZE_INCREASE_INODE_COUNT | RESIZE_DECREASE_INODE_COUNT) &&
+ flags & (RESIZE_ENABLE_64BIT | RESIZE_DISABLE_64BIT)) {
+ fprintf(stderr,
+ _("Cannot change 64-bits mode and inode count simultaneously\n"));
+ goto errout;
+ }
if ((flags & RESIZE_ENABLE_64BIT) &&
ext2fs_has_feature_64bit(fs->super)) {
fprintf(stderr, _("The filesystem is already 64-bit.\n"));
@@ -663,12 +1143,15 @@ int main (int argc, char ** argv)
printf(_("Converting the filesystem to 64-bit.\n"));
else if (flags & RESIZE_DISABLE_64BIT)
printf(_("Converting the filesystem to 32-bit.\n"));
+ else if (flags & (RESIZE_INCREASE_INODE_COUNT
+ | RESIZE_DECREASE_INODE_COUNT))
+ printf(_("Changing inode count on the filesystem.\n"));
else
printf(_("Resizing the filesystem on "
"%s to %llu (%dk) blocks.\n"),
device_name, (unsigned long long) new_size,
blocksize / 1024);
- retval = resize_fs(fs, &new_size, flags,
+ retval = resize_fs(fs, &new_size, new_inodes_per_group, flags,
((flags & RESIZE_PERCENT_COMPLETE) ?
resize_progress_func : 0));
}
@@ -682,8 +1165,16 @@ int main (int argc, char ** argv)
device_name);
goto errout;
}
- printf(_("The filesystem on %s is now %llu (%dk) blocks long.\n\n"),
- device_name, (unsigned long long) new_size, blocksize / 1024);
+ if (flags & (RESIZE_INCREASE_INODE_COUNT | RESIZE_DECREASE_INODE_COUNT)) {
+ printf(_("The filesystem on %s now has %u inodes.\n\n"),
+ device_name,
+ new_inodes_per_group * fs->group_desc_count);
+ } else {
+ printf(_
+ ("The filesystem on %s is now %llu (%dk) blocks long.\n\n"),
+ device_name, (unsigned long long)new_size,
+ blocksize / 1024);
+ }
if ((st_buf.st_size > new_file_size) &&
(fd > 0)) {
diff --git a/resize/resize2fs.c b/resize/resize2fs.c
index c8964af5..b15fadd3 100644
--- a/resize/resize2fs.c
+++ b/resize/resize2fs.c
@@ -32,6 +32,33 @@
* 4. Update the directory blocks with the new inode locations.
* 5. Move the inode tables, if necessary.
*/
+ /*
+ * Reducing the inode count consists of the following phases:
+ *
+ * 1. Calculate the new maximum inode number
+ * 2. Move the inodes in use above that number to lower numbers
+ * 2.a. For those inodes, update the references in folders entries
+ * 3. Reset stats for groups in new fs
+ * 4. Migrate all the inodes to the new reduced inodes tables
+ * 5. Free the remaining space. For flex_bg filesystems, try
+ * to place the new inode tables contiguously
+ *
+ */
+ /*
+ * Increasing the inode count consists of the following phases:
+ *
+ * 1. Reset stats for groups in new fs
+ * 2. Allocate new larger inode tables
+ * 3. Migrate inodes to the new itables
+ * 4. Free the space of the old itables
+ * 5. If there were no contiguous free blocks to allocate
+ * some of the new itables:
+ * 5.a Move some data blocks to make room for them
+ * 5.b Update references to those data blocks
+ * in the affected files/folders
+ * 5.c Go back to step 2 for the new itables pending allocation
+ *
+ */
#include "config.h"
#include "resize2fs.h"
@@ -52,14 +79,16 @@ static errcode_t fix_resize_inode(ext2_filsys fs);
static errcode_t fix_orphan_file_inode(ext2_filsys fs);
static errcode_t resize2fs_calculate_summary_stats(ext2_filsys fs);
static errcode_t fix_sb_journal_backup(ext2_filsys fs);
-static errcode_t mark_table_blocks(ext2_filsys fs,
- ext2fs_block_bitmap bmap);
static errcode_t clear_sparse_super2_last_group(ext2_resize_t rfs);
static errcode_t reserve_sparse_super2_last_group(ext2_resize_t rfs,
ext2fs_block_bitmap meta_bmap);
static errcode_t resize_group_descriptors(ext2_resize_t rfs, blk64_t new_size);
static errcode_t move_bg_metadata(ext2_resize_t rfs);
static errcode_t zero_high_bits_in_inodes(ext2_resize_t rfs);
+static errcode_t move_inodes_to_smaller_tables(ext2_resize_t rfs);
+static errcode_t init_increase_inode_count(ext2_resize_t rfs);
+static errcode_t allocate_new_itables(ext2_resize_t rfs);
+static errcode_t make_room_for_new_itables(ext2_resize_t rfs);
/*
* Some helper functions to check if a block is in a metadata area
@@ -95,7 +124,8 @@ static int lazy_itable_init;
/*
* This is the top-level routine which does the dirty deed....
*/
-errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size, int flags,
+errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size,
+ unsigned int new_inodes_per_group, int flags,
errcode_t (*progress)(ext2_resize_t rfs, int pass,
unsigned long cur,
unsigned long max_val))
@@ -116,6 +146,7 @@ errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size, int flags,
rfs->old_fs = fs;
rfs->flags = flags;
rfs->itable_buf = 0;
+ rfs->new_inodes_per_group = new_inodes_per_group;
rfs->progress = progress;
init_resource_track(&overall_track, "overall resize2fs", fs->io);
@@ -169,6 +200,24 @@ errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size, int flags,
*new_size = ext2fs_blocks_count(rfs->new_fs->super);
+ init_resource_track(&rtrack, "init_increase_inode_count", fs->io);
+ init_increase_inode_count(rfs);
+ if (retval)
+ goto errout;
+ print_resource_track(rfs, &rtrack, fs->io);
+retry:
+ init_resource_track(&rtrack, "allocate_new_itables", fs->io);
+ allocate_new_itables(rfs);
+ if (retval)
+ goto errout;
+ print_resource_track(rfs, &rtrack, fs->io);
+
+ init_resource_track(&rtrack, "make_room_for_new_itables", fs->io);
+ make_room_for_new_itables(rfs);
+ if (retval)
+ goto errout;
+ print_resource_track(rfs, &rtrack, fs->io);
+
init_resource_track(&rtrack, "blocks_to_move", fs->io);
retval = blocks_to_move(rfs);
if (retval)
@@ -195,12 +244,22 @@ errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size, int flags,
goto errout;
print_resource_track(rfs, &rtrack, fs->io);
+ if ((rfs->flags & RESIZE_INCREASE_INODE_COUNT) &&
+ rfs->allocated_new_itables < rfs->new_fs->group_desc_count)
+ goto retry;
+
init_resource_track(&rtrack, "inode_ref_fix", fs->io);
retval = inode_ref_fix(rfs);
if (retval)
goto errout;
print_resource_track(rfs, &rtrack, fs->io);
+ init_resource_track(&rtrack, "move_inodes_to_smaller_tables", fs->io);
+ move_inodes_to_smaller_tables(rfs);
+ if (retval)
+ goto errout;
+ print_resource_track(rfs, &rtrack, fs->io);
+
init_resource_track(&rtrack, "move_itables", fs->io);
retval = move_itables(rfs);
if (retval)
@@ -268,6 +327,11 @@ errout:
}
if (rfs->itable_buf)
ext2fs_free_mem(&rfs->itable_buf);
+ if (rfs->evacuated_inodes)
+ ext2fs_free_mem(&rfs->evacuated_inodes);
+ if (rfs->new_itable_status)
+ ext2fs_free_mem(&rfs->new_itable_status);
+
ext2fs_free_mem(&rfs);
return retval;
}
@@ -1130,6 +1194,9 @@ static errcode_t adjust_superblock(ext2_resize_t rfs, blk64_t new_size)
ext2fs_mark_bb_dirty(fs);
ext2fs_mark_ib_dirty(fs);
+ if (rfs->flags & (RESIZE_INCREASE_INODE_COUNT | RESIZE_DECREASE_INODE_COUNT))
+ return 0;
+
retval = ext2fs_allocate_block_bitmap(fs, _("reserved blocks"),
&rfs->reserve_blocks);
if (retval)
@@ -1237,7 +1304,7 @@ errout:
* This helper function creates a block bitmap with all of the
* filesystem meta-data blocks.
*/
-static errcode_t mark_table_blocks(ext2_filsys fs,
+errcode_t mark_table_blocks(ext2_filsys fs,
ext2fs_block_bitmap bmap)
{
dgrp_t i;
@@ -1367,6 +1434,10 @@ static errcode_t blocks_to_move(ext2_resize_t rfs)
ext2fs_block_bitmap meta_bmap, new_meta_bmap = NULL;
int flex_bg;
+ if (rfs->flags
+ & (RESIZE_INCREASE_INODE_COUNT | RESIZE_DECREASE_INODE_COUNT))
+ return 0;
+
fs = rfs->new_fs;
old_fs = rfs->old_fs;
if (ext2fs_blocks_count(old_fs->super) > ext2fs_blocks_count(fs->super))
@@ -1748,18 +1819,40 @@ static errcode_t resize2fs_get_alloc_block(ext2_filsys fs,
(unsigned long long) blk);
#endif
- ext2fs_mark_block_bitmap2(rfs->old_fs->block_map, blk);
- ext2fs_mark_block_bitmap2(rfs->new_fs->block_map, blk);
- group = ext2fs_group_of_blk2(rfs->old_fs, blk);
- ext2fs_clear_block_uninit(rfs->old_fs, group);
- group = ext2fs_group_of_blk2(rfs->new_fs, blk);
- ext2fs_clear_block_uninit(rfs->new_fs, group);
+ if (rfs->flags & RESIZE_INCREASE_INODE_COUNT) {
+ if (fs == rfs->new_fs) {
+ ext2fs_block_alloc_stats2(rfs->old_fs, blk, +1);
+
+ ext2fs_mark_block_bitmap2(rfs->new_fs->block_map, blk);
+ group = ext2fs_group_of_blk2(rfs->new_fs, blk);
+ ext2fs_clear_block_uninit(rfs->new_fs, group);
+ } else {
+ ext2fs_block_alloc_stats2(rfs->new_fs, blk, +1);
+
+ ext2fs_mark_block_bitmap2(rfs->old_fs->block_map, blk);
+ group = ext2fs_group_of_blk2(rfs->old_fs, blk);
+ ext2fs_clear_block_uninit(rfs->old_fs, group);
+ }
+ } else {
+ ext2fs_mark_block_bitmap2(rfs->old_fs->block_map, blk);
+ ext2fs_mark_block_bitmap2(rfs->new_fs->block_map, blk);
+
+ group = ext2fs_group_of_blk2(rfs->old_fs, blk);
+ ext2fs_clear_block_uninit(rfs->old_fs, group);
+ group = ext2fs_group_of_blk2(rfs->new_fs, blk);
+ ext2fs_clear_block_uninit(rfs->new_fs, group);
+ }
*ret = (blk64_t) blk;
return 0;
}
+static inline ext2_filsys get_fs_of_ino(ext2_resize_t rfs, ext2_ino_t ino) {
+ return (rfs->new_itable_status[ext2fs_group_of_ino(rfs->new_fs, ino)]
+ == itable_status_populated) ? rfs->new_fs : rfs->old_fs;
+}
+
static errcode_t block_mover(ext2_resize_t rfs)
{
blk64_t blk, old_blk, new_blk;
@@ -1770,11 +1863,18 @@ static errcode_t block_mover(ext2_resize_t rfs)
int to_move, moved;
ext2_badblocks_list badblock_list = 0;
int bb_modified = 0;
+ ext2_filsys fs_bb_inode = old_fs;
+
+ if (rfs->flags & RESIZE_DECREASE_INODE_COUNT)
+ return 0;
fs->get_alloc_block = resize2fs_get_alloc_block;
old_fs->get_alloc_block = resize2fs_get_alloc_block;
- retval = ext2fs_read_bb_inode(old_fs, &badblock_list);
+ if (rfs->flags & RESIZE_INCREASE_INODE_COUNT)
+ fs_bb_inode = get_fs_of_ino(rfs, EXT2_BAD_INO);
+
+ retval = ext2fs_read_bb_inode(fs_bb_inode, &badblock_list);
if (retval)
return retval;
@@ -1810,9 +1910,15 @@ static errcode_t block_mover(ext2_resize_t rfs)
}
new_blk = get_new_block(rfs);
- if (!new_blk) {
- retval = ENOSPC;
- goto errout;
+ if (rfs->flags & RESIZE_INCREASE_INODE_COUNT) {
+ if (!new_blk)
+ break;
+ ext2fs_block_alloc_stats2(old_fs, new_blk, +1);
+ } else {
+ if (!new_blk) {
+ retval = ENOSPC;
+ goto errout;
+ }
}
ext2fs_block_alloc_stats2(fs, new_blk, +1);
ext2fs_add_extent_entry(rfs->bmap, B2C(blk), B2C(new_blk));
@@ -1865,6 +1971,13 @@ static errcode_t block_mover(ext2_resize_t rfs)
retval = io_channel_write_blk64(fs->io, new_blk, c,
rfs->itable_buf);
if (retval) goto errout;
+
+ if (rfs->flags & RESIZE_INCREASE_INODE_COUNT) {
+ ext2fs_block_alloc_stats_range(fs, old_blk,
+ c, -1);
+ ext2fs_block_alloc_stats_range(old_fs, old_blk,
+ c, -1);
+ }
size -= c;
new_blk += c;
old_blk += c;
@@ -1884,7 +1997,7 @@ static errcode_t block_mover(ext2_resize_t rfs)
errout:
if (badblock_list) {
if (!retval && bb_modified)
- retval = ext2fs_update_bb_inode(old_fs,
+ retval = ext2fs_update_bb_inode(fs_bb_inode,
badblock_list);
ext2fs_badblocks_list_free(badblock_list);
}
@@ -1954,7 +2067,7 @@ static int process_block(ext2_filsys fs, blk64_t *block_nr,
}
}
- if (pb->is_dir) {
+ if (pb->is_dir && !(pb->rfs->flags & RESIZE_INCREASE_INODE_COUNT)) {
retval = ext2fs_add_dir_block2(fs->dblist, pb->ino,
block, (int) blockcnt);
if (retval) {
@@ -1993,35 +2106,35 @@ static errcode_t progress_callback(ext2_filsys fs,
return 0;
}
-static errcode_t migrate_ea_block(ext2_resize_t rfs, ext2_ino_t ino,
- struct ext2_inode *inode, int *changed)
+static errcode_t migrate_ea_block(ext2_extent bmap, ext2_filsys fs,
+ ext2_ino_t ino, struct ext2_inode *inode, int *changed)
{
char *buf = NULL;
blk64_t new_block;
errcode_t err = 0;
/* No EA block or no remapping? Quit early. */
- if (ext2fs_file_acl_block(rfs->old_fs, inode) == 0 || !rfs->bmap)
+ if (ext2fs_file_acl_block(fs, inode) == 0 || !bmap)
return 0;
- new_block = extent_translate(rfs->old_fs, rfs->bmap,
- ext2fs_file_acl_block(rfs->old_fs, inode));
+ new_block = extent_translate(fs, bmap,
+ ext2fs_file_acl_block(fs, inode));
if (new_block == 0)
return 0;
/* Set the new ACL block */
- ext2fs_file_acl_block_set(rfs->old_fs, inode, new_block);
+ ext2fs_file_acl_block_set(fs, inode, new_block);
/* Update checksum */
- if (ext2fs_has_feature_metadata_csum(rfs->new_fs->super)) {
- err = ext2fs_get_mem(rfs->old_fs->blocksize, &buf);
+ if (ext2fs_has_feature_metadata_csum(fs->super)) {
+ err = ext2fs_get_mem(fs->blocksize, &buf);
if (err)
return err;
- rfs->old_fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
- err = ext2fs_read_ext_attr3(rfs->old_fs, new_block, buf, ino);
- rfs->old_fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ err = ext2fs_read_ext_attr3(fs, new_block, buf, ino);
+ fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
if (err)
goto out;
- err = ext2fs_write_ext_attr3(rfs->old_fs, new_block, buf, ino);
+ err = ext2fs_write_ext_attr3(fs, new_block, buf, ino);
if (err)
goto out;
}
@@ -2184,30 +2297,34 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
struct process_block_struct pb;
ext2_ino_t ino, new_inode;
struct ext2_inode *inode = NULL;
- ext2_inode_scan scan = NULL;
errcode_t retval;
char *block_buf = 0;
ext2_ino_t start_to_move;
int inode_size;
int update_ea_inode_refs = 0;
+ ext2_filsys fs = rfs->old_fs;
if ((rfs->old_fs->group_desc_count <=
rfs->new_fs->group_desc_count) &&
- !rfs->bmap)
+ !rfs->bmap &&
+ !(rfs->flags & RESIZE_DECREASE_INODE_COUNT))
return 0;
set_com_err_hook(quiet_com_err_proc);
- retval = ext2fs_open_inode_scan(rfs->old_fs, 0, &scan);
- if (retval) goto errout;
-
retval = ext2fs_init_dblist(rfs->old_fs, 0);
if (retval) goto errout;
retval = ext2fs_get_array(rfs->old_fs->blocksize, 3, &block_buf);
if (retval) goto errout;
- start_to_move = (rfs->new_fs->group_desc_count *
- rfs->new_fs->super->s_inodes_per_group);
+ /*the new_fs is not yet updated with the new_inodes_per_group*/
+ if (rfs->flags & RESIZE_DECREASE_INODE_COUNT)
+ start_to_move = (rfs->new_fs->group_desc_count *
+ rfs->new_inodes_per_group);
+ else
+ start_to_move = (rfs->new_fs->group_desc_count *
+ rfs->new_fs->super->s_inodes_per_group);
+
if (rfs->progress) {
retval = (rfs->progress)(rfs, E2_RSZ_INODE_SCAN_PASS,
@@ -2215,7 +2332,6 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
if (retval)
goto errout;
}
- ext2fs_set_inode_callback(scan, progress_callback, (void *) rfs);
pb.rfs = rfs;
pb.inode = inode;
pb.error = 0;
@@ -2226,16 +2342,26 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
retval = ENOMEM;
goto errout;
}
+
/*
* First, copy all of the inodes that need to be moved
* elsewhere in the inode table
*/
- while (1) {
- retval = ext2fs_get_next_inode_full(scan, &ino, inode, inode_size);
- if (retval) goto errout;
+ for (ino = 1; ino <= rfs->old_fs->super->s_inodes_count; ino++) {
if (!ino)
break;
+ if (rfs->flags & RESIZE_INCREASE_INODE_COUNT)
+ fs = get_fs_of_ino(rfs, ino);
+
+ retval = ext2fs_read_inode2(fs, ino, inode, inode_size,
+ READ_INODE_NOCSUM);
+ if (retval) goto errout;
+ if (ino > 1 && ext2fs_group_of_ino(rfs->old_fs, ino)
+ != ext2fs_group_of_ino(rfs->old_fs, ino-1))
+ progress_callback(rfs->old_fs, NULL,
+ ext2fs_group_of_ino(rfs->old_fs, ino-1), rfs);
+
if (inode->i_links_count == 0 && ino != EXT2_RESIZE_INO)
continue; /* inode not in use */
@@ -2243,7 +2369,7 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
pb.changed = 0;
/* Remap EA block */
- retval = migrate_ea_block(rfs, ino, inode, &pb.changed);
+ retval = migrate_ea_block(rfs->bmap, fs, ino, inode, &pb.changed);
if (retval)
goto errout;
@@ -2291,7 +2417,7 @@ static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
remap_blocks:
if (pb.changed)
- retval = ext2fs_write_inode_full(rfs->old_fs,
+ retval = ext2fs_write_inode_full(fs,
new_inode,
inode, inode_size);
if (retval)
@@ -2302,13 +2428,13 @@ remap_blocks:
* blocks for inode remapping. Need to write out dir blocks
* with new inode numbers if we have metadata_csum enabled.
*/
- rfs->old_fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
- if (ext2fs_inode_has_valid_blocks2(rfs->old_fs, inode) &&
+ fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ if (ext2fs_inode_has_valid_blocks2(fs, inode) &&
(rfs->bmap || pb.is_dir)) {
pb.ino = new_inode;
pb.old_ino = ino;
pb.has_extents = inode->i_flags & EXT4_EXTENTS_FL;
- retval = ext2fs_block_iterate3(rfs->old_fs,
+ retval = ext2fs_block_iterate3(fs,
new_inode, 0, block_buf,
process_block, &pb);
if (retval)
@@ -2328,7 +2454,8 @@ remap_blocks:
/* Fix up extent block checksums with the new inode number */
if (ext2fs_has_feature_metadata_csum(rfs->old_fs->super) &&
- (inode->i_flags & EXT4_EXTENTS_FL)) {
+ (inode->i_flags & EXT4_EXTENTS_FL) &&
+ !(rfs->flags & RESIZE_INCREASE_INODE_COUNT)) {
retval = ext2fs_fix_extents_checksums(rfs->old_fs,
new_inode, NULL);
if (retval)
@@ -2344,16 +2471,16 @@ remap_blocks:
goto errout;
}
io_channel_flush(rfs->old_fs->io);
+ progress_callback(rfs->old_fs, NULL, fs->group_desc_count-1, rfs);
errout:
reset_com_err_hook();
rfs->old_fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
+ rfs->new_fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS;
if (rfs->bmap) {
ext2fs_free_extent_table(rfs->bmap);
rfs->bmap = 0;
}
- if (scan)
- ext2fs_close_inode_scan(scan);
if (block_buf)
ext2fs_free_mem(&block_buf);
free(inode);
@@ -2438,7 +2565,7 @@ static errcode_t inode_ref_fix(ext2_resize_t rfs)
errcode_t retval;
struct istruct is;
- if (!rfs->imap)
+ if (!rfs->imap || rfs->flags & RESIZE_INCREASE_INODE_COUNT)
return 0;
/*
@@ -2513,6 +2640,10 @@ static errcode_t move_itables(ext2_resize_t rfs)
unsigned int j;
ext2fs_block_bitmap new_bmap = NULL;
+ if (rfs->flags
+ & (RESIZE_INCREASE_INODE_COUNT | RESIZE_DECREASE_INODE_COUNT))
+ return 0;
+
max_groups = fs->group_desc_count;
if (max_groups > rfs->old_fs->group_desc_count)
max_groups = rfs->old_fs->group_desc_count;
@@ -3321,3 +3452,727 @@ blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags)
return blks_needed;
}
+
+/*
+ * The function ext2fs_block_alloc_stats_range() doesn't expect to
+ * receive unaligned blocks or uneven lengths, which may be the case
+ * with itables for bigalloc filesystems. This function will modify the input
+ * values to compensate for those issues before calling
+ * ext2fs_block_alloc_stats_range(). We assume any other metadata (blockmaps,
+ * inodemaps, etc...) is always placed before inode tables if they share the
+ * same cluster (which is how mkfs creates the filesystems), so we avoid to
+ * free the cluster if the inode table doesn't own its first block
+ */
+static void tweak_values_for_bigalloc(ext2_resize_t rfs,
+ blk64_t *first_block, unsigned int *num_blocks)
+{
+ blk64_t start, end, diff,
+ cluster_size = EXT2FS_CLUSTER_RATIO(rfs->new_fs);
+
+ start = *first_block;
+ end = *first_block + *num_blocks - 1;
+
+ /*If the first block to free is not aligned with the
+ beginning of the cluster, we will move it to the
+ beginning of the next cluster. */
+ if (start % cluster_size) {
+ diff = cluster_size - (start % cluster_size);
+ *first_block += diff;
+
+ /*overflow case if we are freeing very few blocks */
+ if (*num_blocks <= diff)
+ *num_blocks = 0;
+ else
+ *num_blocks -= diff;
+ }
+
+ /*If the end block is not aligned with the end of
+ the cluster, we will move it to the end of
+ the current cluster. */
+ if (!
+ ((end & EXT2FS_CLUSTER_MASK(rfs->new_fs)) ==
+ EXT2FS_CLUSTER_MASK(rfs->new_fs)) && *num_blocks != 0)
+ *num_blocks += cluster_size - (end % cluster_size) - 1;
+
+ /*If we have many itables in the same cluster (aka.: very
+ small num_blocks), the responsible to free the cluster will
+ be the call having the first block of the cluster. The other
+ calls shall not attempt to free it to avoid duplicated frees */
+ if (*first_block > (end & ~EXT2FS_CLUSTER_MASK(rfs->new_fs))) {
+ if (*num_blocks <= cluster_size)
+ *num_blocks = 0;
+ else
+ *num_blocks -= cluster_size;
+ }
+}
+
+
+/*
+ * This function will free the unused space of inode tables after
+ * reducing the inode count. For fs with the flex_bg feature set, if
+ * the inodes tables were contiguous for several groups, it will also
+ * move the blocks to keep the inode tables contiguous
+ */
+static errcode_t reubicate_and_free_itables(ext2_resize_t rfs)
+{
+ errcode_t retval = 0;
+ dgrp_t group;
+ unsigned int len;
+ blk64_t after_prev_itable;
+
+ if (!rfs->itable_buf) {
+ retval = ext2fs_get_array(rfs->new_fs->blocksize,
+ rfs->new_fs->inode_blocks_per_group,
+ &rfs->itable_buf);
+ if (retval)
+ goto errout;
+
+ memset(rfs->itable_buf, 0,
+ rfs->new_fs->blocksize *
+ rfs->new_fs->inode_blocks_per_group);
+ }
+
+ after_prev_itable = ext2fs_inode_table_loc(rfs->old_fs, 0) +
+ rfs->new_fs->inode_blocks_per_group;
+
+ for (group = 1; group < rfs->new_fs->group_desc_count; group++) {
+
+ if (ext2fs_has_feature_flex_bg(rfs->new_fs->super)
+ && ext2fs_inode_table_loc(rfs->old_fs, group - 1) +
+ rfs->old_fs->inode_blocks_per_group ==
+ ext2fs_inode_table_loc(rfs->old_fs, group)) {
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf("moving itables, contiguous groups "
+ "%u, %u, itables in %llu, %llu\n",
+ group - 1, group,
+ ext2fs_inode_table_loc(rfs->old_fs, group - 1),
+ ext2fs_inode_table_loc(rfs->old_fs, group));
+#endif
+ retval = io_channel_read_blk64(rfs->old_fs->io,
+ ext2fs_inode_table_loc(rfs->old_fs, group),
+ rfs->new_fs->inode_blocks_per_group,
+ rfs->itable_buf);
+ if (retval)
+ goto errout;
+ retval = io_channel_write_blk64(rfs->old_fs->io,
+ after_prev_itable,
+ rfs->new_fs->inode_blocks_per_group,
+ rfs->itable_buf);
+ if (retval)
+ goto errout;
+
+ ext2fs_inode_table_loc_set(rfs->new_fs, group, after_prev_itable);
+ ext2fs_group_desc_csum_set(rfs->new_fs, group);
+ after_prev_itable += rfs->new_fs->inode_blocks_per_group;
+
+ } else {
+ len = ext2fs_inode_table_loc(rfs->old_fs, group - 1) +
+ rfs->old_fs->inode_blocks_per_group -
+ after_prev_itable;
+ if (ext2fs_has_feature_bigalloc(rfs->new_fs->super))
+ tweak_values_for_bigalloc(rfs,
+ &after_prev_itable,
+ &len);
+ ext2fs_block_alloc_stats_range(rfs->new_fs,
+ after_prev_itable,
+ len, -1);
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf("unmarking %u blocks "
+ "starting on block %llu\n",
+ len, after_prev_itable);
+#endif
+
+ after_prev_itable = ext2fs_inode_table_loc(rfs->old_fs, group) +
+ rfs->new_fs->inode_blocks_per_group;
+ }
+ }
+
+ len = ext2fs_inode_table_loc(rfs->old_fs, group - 1) +
+ rfs->old_fs->inode_blocks_per_group - after_prev_itable;
+ if (ext2fs_has_feature_bigalloc(rfs->new_fs->super))
+ tweak_values_for_bigalloc(rfs, &after_prev_itable, &len);
+ ext2fs_block_alloc_stats_range(rfs->new_fs, after_prev_itable,
+ len, -1);
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf("unmarking %u blocks starting on block %llu\n",
+ len, after_prev_itable);
+#endif
+
+ errout:
+ if (rfs->itable_buf)
+ ext2fs_free_mem(&rfs->itable_buf);
+ return retval;
+}
+
+/******************************************************************************
+We will migrate the inodes in-place to the existing tables. We do not allocate
+new itables when reducing the inode count. The advantage of this approach is
+that we don't need any free blocks in the filesystem for this operation. It
+could be done in a fs with zero free blocks, which might also be the reason to
+run the reducer: free some space for data. What follow is a simple proof that
+there is no risk of overwriting an inode before needing to read it.
+
+Let:
+g: block group, 0-based
+ipg: inodes per group
+pos: inode position within the inode table of the block group, 1-based
+inum: inode number
+
+This gives the inode number formula:
+inum = g * ipg + pos
+
+When reducing the inode count, we start from the highest inode number
+and go down to 1.
+
+This operation shall not overwrite a needed inode before it is migrated:
+To overwrite a needed inode in a given g & pos before being migrated, its
+inum_old shall be less than its inum_new, as the migration loop is
+moving backwards. So:
+
+inum_old < inum_new
+
+Substitute. As we talk about the same memory position, g & pos are the same,
+only ipg changes:
+
+g * ipg_old + pos < g * ipg_new + pos
+
+Thus, an overwrite will require ipg_olg < ipg_new.
+However, we are reducing the inode count, so we are doing ipg_old > ipg_new.
+Therefore, the migration loop will not overwrite needed inodes before
+migrating them.
+******************************************************************************/
+static errcode_t migrate_inodes_backwards_loop(ext2_resize_t rfs)
+{
+ ext2_ino_t ino;
+ struct ext2_inode *inode = NULL;
+ errcode_t retval;
+ int inode_size = EXT2_INODE_SIZE(rfs->new_fs->super);
+
+ inode = malloc(inode_size);
+ if (!inode) {
+ retval = ENOMEM;
+ goto errout;
+ }
+
+ for (ino = rfs->new_fs->super->s_inodes_count; ino > 0; ino--) {
+
+ retval = ext2fs_read_inode2(rfs->old_fs, ino, inode, inode_size, 0);
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf("Migrating inode %u to new position: old group: "
+ "%u, new group: %u, read_retval: %li", ino,
+ ext2fs_group_of_ino(rfs->old_fs, ino),
+ ext2fs_group_of_ino(rfs->new_fs, ino), retval);
+#endif
+ /*we require to run fsck before changing the inode count,
+ and that will fix inode checksums on used inodes. However,
+ an unused inode with a wrong checksum will not be detected
+ by fsck. We don't want to stop the whole process now and
+ leave a messy fs because of that, just log it and continue */
+ if (retval && retval != EXT2_ET_INODE_CSUM_INVALID)
+ goto errout;
+
+ if (inode->i_links_count != 0
+ || ino < EXT2_FIRST_INODE(rfs->new_fs->super)) {
+ ext2fs_inode_alloc_stats2(rfs->new_fs, ino, +1,
+ LINUX_S_ISDIR(inode->i_mode));
+ }
+
+ /*if not in use, write the zeros from the inode to the itable
+ anyway, to overwrite any previous inode */
+ retval = ext2fs_write_inode2(rfs->new_fs, ino, inode, inode_size, 0);
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf(", write_retval: %li\n", retval);
+#endif
+ if (retval)
+ goto errout;
+
+ }
+
+ errout:
+ if (inode)
+ free(inode);
+
+ return retval;
+}
+
+static void update_inode_info_in_fs(ext2_resize_t rfs) {
+ dgrp_t g;
+ ext2_filsys fs = rfs->new_fs;
+
+ fs->super->s_inodes_per_group = rfs->new_inodes_per_group;
+ fs->inode_blocks_per_group =
+ ext2fs_div_ceil(fs->super->s_inodes_per_group *
+ fs->super->s_inode_size,
+ fs->blocksize);
+ fs->super->s_inodes_count =
+ fs->group_desc_count * fs->super->s_inodes_per_group;
+ fs->super->s_free_inodes_count = fs->super->s_inodes_count;
+
+ for (g = 0; g < fs->group_desc_count; g++) {
+ ext2fs_bg_used_dirs_count_set(fs, g, 0);
+ ext2fs_bg_free_inodes_count_set(fs, g,
+ fs->super->s_inodes_per_group);
+ if (ext2fs_has_group_desc_csum(fs))
+ ext2fs_bg_itable_unused_set(fs, g,
+ fs->super->s_inodes_per_group);
+ }
+
+}
+
+
+/*At this point all inodes above the new s_inodes_count have been
+relocated to smaller numbers. Now, we just need to move them to
+their new inode table positions*/
+static errcode_t move_inodes_to_smaller_tables(ext2_resize_t rfs)
+{
+ errcode_t retval;
+
+ if (!(rfs->flags & RESIZE_DECREASE_INODE_COUNT))
+ return 0;
+
+ io_channel_flush(rfs->old_fs->io);
+
+ update_inode_info_in_fs(rfs);
+
+ retval = migrate_inodes_backwards_loop(rfs);
+ if (retval)
+ goto errout;
+
+ retval = reubicate_and_free_itables(rfs);
+ if (retval)
+ goto errout;
+
+ ext2fs_mark_super_dirty(rfs->new_fs);
+ io_channel_flush(rfs->new_fs->io);
+
+ errout:
+
+ return retval;
+}
+
+
+/*initialize the data structures needed to increase the inode count*/
+static errcode_t init_increase_inode_count(ext2_resize_t rfs)
+{
+ errcode_t retval;
+
+ if (!(rfs->flags & RESIZE_INCREASE_INODE_COUNT))
+ return 0;
+
+ retval = ext2fs_get_memzero(rfs->new_fs->group_desc_count *
+ sizeof(unsigned int), &rfs->evacuated_inodes);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_get_memzero(rfs->new_fs->group_desc_count *
+ sizeof(itable_status), &rfs->new_itable_status);
+ if (retval)
+ goto errout;
+
+/*avoid early stop if the first iteration doesn't allocate any new inode table*/
+#define ALLOCATED_NEW_ITABLE_FIRST_ITERATION 0xFFFFFFFF
+ rfs->allocated_new_itables = ALLOCATED_NEW_ITABLE_FIRST_ITERATION;
+
+ update_inode_info_in_fs(rfs);
+
+ retval =
+ ext2fs_resize_inode_bitmap2(rfs->new_fs->super->s_inodes_count,
+ rfs->new_fs->super->s_inodes_count,
+ rfs->new_fs->inode_map);
+ if (retval) {
+ fprintf(stderr, "error %li when calling "
+ "ext2fs_resize_inode_bitmap2()\n", retval);
+ goto errout;
+ }
+
+ errout:
+ return retval;
+}
+
+/*
+ * For a bigalloc filesystem, we may have a cluster whose blocks are used
+ * for different purposes: some are used for blockmaps/inodemaps and others
+ * are used for inode tables. This function will fix group stats
+ */
+static void fix_itables_stats_bigalloc(ext2_filsys fs, blk64_t start,
+ unsigned int len)
+{
+ blk64_t blk, rem, cluster_size;
+ dgrp_t group_of_block;
+
+ cluster_size = EXT2FS_CLUSTER_RATIO(fs);
+
+ /*we assume start of the new itable given by the
+ ext2fs_allocate_group_table function is aligned
+ with start of the cluster, we only do the fix for
+ end of itable */
+ blk = start + len - 1;
+ group_of_block = ext2fs_group_of_blk2(fs, blk);
+
+ rem = (blk + 1) % cluster_size;
+ if (rem) {
+ ext2fs_bg_free_blocks_count_set(fs, group_of_block,
+ ext2fs_bg_free_blocks_count(fs, group_of_block) - 1);
+ ext2fs_bg_flags_clear(fs, group_of_block, EXT2_BG_BLOCK_UNINIT);
+ ext2fs_group_desc_csum_set(fs, group_of_block);
+ ext2fs_free_blocks_count_add(fs->super, -(cluster_size - rem));
+ ext2fs_mark_super_dirty(fs);
+ ext2fs_mark_bb_dirty(fs);
+ }
+}
+
+
+/*
+ * This function will migrate inodes to the new inode tables
+ * when increasing the inode count
+ */
+static errcode_t migrate_inodes_forward_loop(ext2_resize_t rfs)
+{
+ ext2_ino_t ino_num;
+ struct ext2_inode *inode = NULL;
+ int inode_size = EXT2_INODE_SIZE(rfs->new_fs->super);
+ dgrp_t group, new_group, old_group;
+ errcode_t retval = 0;
+ blk64_t itable_start;
+ unsigned int len;
+
+ inode = malloc(inode_size);
+ if (!inode) {
+ retval = ENOMEM;
+ goto errout;
+ }
+
+ for (ino_num = 1; ino_num <= rfs->old_fs->super->s_inodes_count;
+ ino_num++) {
+ if (!ino_num)
+ break;
+
+ new_group = ext2fs_group_of_ino(rfs->new_fs, ino_num);
+ old_group = ext2fs_group_of_ino(rfs->old_fs, ino_num);
+ if (rfs->new_itable_status[new_group] !=
+ itable_status_allocated) {
+ if (new_group == rfs->new_fs->group_desc_count - 1)
+ break; /*no more groups? break loop */
+ ino_num = (new_group + 1)
+ * rfs->new_fs->super->s_inodes_per_group;
+ continue;
+ }
+
+ retval = ext2fs_read_inode2(rfs->old_fs, ino_num,
+ inode, inode_size, 0);
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf("Migrating inode %u to new position: "
+ "old group: %u, new group: %u, read_retval: %li",
+ ino_num, old_group, new_group, retval);
+#endif
+ /*we require to run fsck before changing the inode count,
+ and that will fix inode checksums on used inodes. However,
+ an unused inode with a wrong checksum will not be detected
+ by fsck. We don't want to stop the whole process now and
+ leave a messy fs because of that, just log it and continue */
+ if (retval && retval != EXT2_ET_INODE_CSUM_INVALID)
+ goto errout;
+
+ (rfs->evacuated_inodes[old_group])++;
+
+ if (inode->i_links_count != 0
+ || ino_num < EXT2_FIRST_INODE(rfs->new_fs->super)) {
+ ext2fs_inode_alloc_stats2(rfs->new_fs, ino_num, +1,
+ LINUX_S_ISDIR(inode->i_mode));
+ }
+
+ retval = ext2fs_write_inode2(rfs->new_fs, ino_num,
+ inode, inode_size, 0);
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf(", write_retval: %li\n", retval);
+#endif
+ if (retval)
+ goto errout;
+
+ /*are we about to completely migrate this inode table? */
+ if (ino_num == rfs->old_fs->super->s_inodes_count ||
+ ext2fs_group_of_ino(rfs->new_fs, ino_num + 1) != new_group) {
+ rfs->new_itable_status[new_group] = itable_status_populated;
+ }
+ }
+
+ for (group = 0; group < rfs->new_fs->group_desc_count; group++) {
+ itable_start = ext2fs_inode_table_loc(rfs->old_fs, group);
+ if (itable_start != 0
+ && rfs->evacuated_inodes[group] ==
+ rfs->old_fs->super->s_inodes_per_group) {
+ len = rfs->old_fs->inode_blocks_per_group;
+ if (ext2fs_has_feature_bigalloc(rfs->new_fs->super)) {
+ tweak_values_for_bigalloc(rfs, &itable_start,
+ &len);
+ }
+
+ ext2fs_block_alloc_stats_range(rfs->old_fs,
+ itable_start,
+ len, -1);
+ ext2fs_block_alloc_stats_range(rfs->new_fs,
+ itable_start,
+ len, -1);
+ ext2fs_inode_table_loc_set(rfs->old_fs, group, 0);
+ }
+ }
+
+ errout:
+ if (inode)
+ free(inode);
+ return retval;
+}
+
+
+/*
+ * This function will allocate new inode tables
+ * when increasing the inode count
+ */
+static errcode_t allocate_new_itables(ext2_resize_t rfs)
+{
+ blk64_t itable_start;
+ errcode_t retval = 0;
+ dgrp_t group = 0;
+ int len = 0;
+ dgrp_t prev_allocated_new_itables = rfs->allocated_new_itables;
+
+ if (!(rfs->flags & RESIZE_INCREASE_INODE_COUNT))
+ return 0;
+
+ if (rfs->allocated_new_itables == ALLOCATED_NEW_ITABLE_FIRST_ITERATION)
+ rfs->allocated_new_itables = 0;
+
+ if (rfs->allocated_new_itables == rfs->old_fs->group_desc_count)
+ return 0;
+
+ for (group = 0; group < rfs->new_fs->group_desc_count; group++) {
+ if (rfs->new_itable_status[group] != itable_status_not_allocated)
+ continue;
+
+ ext2fs_inode_table_loc_set(rfs->new_fs, group, 0);
+ retval = ext2fs_allocate_group_table(rfs->new_fs, group, 0);
+ if (retval == EXT2_ET_BLOCK_ALLOC_FAIL) {
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf("unsuccessful ext2fs_allocate_group_table"
+ " for group %u with EXT2_ET_BLOCK_ALLOC_FAIL "
+ "(%li), will retry later\n", group, retval);
+#endif
+ } else if (!retval) {
+ itable_start = ext2fs_inode_table_loc(rfs->new_fs, group);
+ len = rfs->new_fs->inode_blocks_per_group;
+
+ /*ext2fs_allocate_group_table() doesn't update stats in
+ group if flex_bg is not set, we have to do it ourselves */
+ if (!ext2fs_has_feature_flex_bg(rfs->new_fs->super))
+ ext2fs_block_alloc_stats_range(rfs->new_fs,
+ itable_start, len, +1);
+
+ ext2fs_block_alloc_stats_range(rfs->old_fs,
+ itable_start,
+ len, +1);
+ retval = ext2fs_zero_blocks2(rfs->new_fs,
+ itable_start, len,
+ &itable_start, &len);
+ if (retval) {
+ fprintf(stderr,
+ _("\nCould not write %d blocks in "
+ "inode table starting at %llu: %s\n"),
+ len, (unsigned long long)
+ itable_start, error_message(retval));
+ goto errout;
+ }
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf("successful ext2fs_allocate_group_table "
+ "for group %u with retval %li in block "
+ "%llu\n", group, retval, itable_start);
+#endif
+ if (ext2fs_has_feature_bigalloc(rfs->new_fs->super)) {
+ fix_itables_stats_bigalloc(rfs->new_fs,
+ itable_start, len);
+ fix_itables_stats_bigalloc(rfs->old_fs,
+ itable_start, len);
+ }
+ rfs->new_itable_status[group] = itable_status_allocated;
+ (rfs->allocated_new_itables)++;
+ } else {
+ fprintf(stderr, "failed ext2fs_allocate_group_table for"
+ " group %u with retval %li\n", group, retval);
+ goto errout;
+ }
+ }
+
+ io_channel_flush(rfs->new_fs->io);
+
+ if (prev_allocated_new_itables != ALLOCATED_NEW_ITABLE_FIRST_ITERATION
+ && prev_allocated_new_itables == rfs->allocated_new_itables) {
+ fprintf(stderr, "FATAL, breaking loop because no inode table "
+ "could be allocated in last iteration\n");
+ retval = ENOSPC;
+ goto errout;
+ }
+
+ /*populate the newly allocated inode tables*/
+ retval = migrate_inodes_forward_loop(rfs);
+ if (retval)
+ goto errout;
+
+ errout:
+ return retval;
+}
+
+/*
+ * If there are no contiguous free blocks to allocate
+ * the new inode tables, this function will mark blocks
+ * to be moved to make room for them
+ */
+static errcode_t make_room_for_new_itables(ext2_resize_t rfs)
+{
+ ext2_filsys fs = rfs->old_fs;
+ ext2fs_block_bitmap meta_bmap;
+ ext2_badblocks_list badblock_list = 0;
+ unsigned int j;
+ int flexbg_size = 1U << fs->super->s_log_groups_per_flex,
+ retried_from_beginning = 0;
+ dgrp_t g;
+ blk64_t blk, blk2, first_blk, last_blk;
+ blk64_t required_blocks = 0;
+ errcode_t retval;
+
+ if (!(rfs->flags & RESIZE_INCREASE_INODE_COUNT))
+ return 0;
+
+ /*free data from any previous iteration*/
+ if (rfs->reserve_blocks)
+ ext2fs_free_block_bitmap(rfs->reserve_blocks);
+ if (rfs->move_blocks)
+ ext2fs_free_block_bitmap(rfs->move_blocks);
+
+ if (rfs->allocated_new_itables == fs->group_desc_count)
+ return 0;
+
+ retval = ext2fs_allocate_block_bitmap(fs, _("blocks to be moved"),
+ &rfs->move_blocks);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_allocate_block_bitmap(fs, _("reserved blocks"),
+ &rfs->reserve_blocks);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_allocate_block_bitmap(fs, _("meta-data blocks"),
+ &meta_bmap);
+ if (retval)
+ goto errout;
+
+ retval = ext2fs_read_bb_inode(get_fs_of_ino(rfs, EXT2_BAD_INO),
+ &badblock_list);
+ if (retval)
+ goto errout;
+
+ /*exclude metadata from candidate blocks to be moved */
+ retval = mark_table_blocks(rfs->old_fs, meta_bmap);
+ if (retval)
+ goto errout;
+
+ for (g = 0; g < fs->group_desc_count; g++) {
+ blk = ext2fs_inode_table_loc(rfs->new_fs, g);
+ if (blk) {
+ ext2fs_mark_block_bitmap_range2(meta_bmap, blk,
+ rfs->new_fs->inode_blocks_per_group);
+ }
+ }
+
+ for (g = 0; g < fs->group_desc_count; g++) {
+ if (ext2fs_free_blocks_count(rfs->new_fs->super) <
+ rfs->new_fs->inode_blocks_per_group + required_blocks)
+ break; /*don't search if we are not going to find */
+
+ if (ext2fs_has_feature_flex_bg(fs->super)
+ && !(g % (1U << fs->super->s_log_groups_per_flex))) {
+ first_blk = ext2fs_group_first_block2(fs,
+ g & ~(flexbg_size - 1));
+ last_blk = (g | (flexbg_size - 1) >= fs->group_desc_count - 1) ?
+ ext2fs_blocks_count(fs->super) - 1 :
+ ext2fs_group_first_block2(fs, (g | (flexbg_size - 1)) + 1) - 1;
+ retried_from_beginning = 0;
+ }
+ if (rfs->new_itable_status[g] != itable_status_not_allocated)
+ continue;
+
+ if (!ext2fs_has_feature_flex_bg(fs->super)) {
+ first_blk = ext2fs_group_first_block2(fs, g);
+ last_blk = (g == fs->group_desc_count - 1) ?
+ ext2fs_blocks_count(fs->super) - 1 :
+ ext2fs_group_first_block2(fs, g + 1) - 1;
+ }
+search_for_space:
+ for (blk = first_blk; blk <= last_blk; blk++) {
+ for (blk2 = blk, j = 0;
+ j < rfs->new_fs->inode_blocks_per_group
+ && blk2 <= last_blk; blk2++, j++) {
+ if (ext2fs_test_block_bitmap2(meta_bmap, blk2)
+ || ext2fs_test_block_bitmap2(rfs->reserve_blocks, blk2)
+ || ext2fs_badblocks_list_test(badblock_list, blk2)) {
+ blk = blk2;
+ break;
+ }
+ }
+ if (j == rfs->new_fs->inode_blocks_per_group) {
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ printf(" --->blocks to move in group %u "
+ "are %llu - %llu\n", g, blk, blk2 - 1);
+#endif
+ ext2fs_mark_block_bitmap_range2(rfs->move_blocks,
+ blk, rfs->new_fs->inode_blocks_per_group);
+ ext2fs_mark_block_bitmap_range2(rfs->reserve_blocks,
+ blk, rfs->new_fs->inode_blocks_per_group);
+ /* multiplied by 2 to account for possible extent tree rebalancing */
+ required_blocks += 2 * rfs->new_fs->inode_blocks_per_group;
+ first_blk = blk2;
+ break;
+ }
+ /* break loop, enter next if about failed search */
+ if (blk2 > last_blk) {
+ blk = blk2;
+ break;
+ }
+ }
+ if (blk > last_blk) {
+ /*ext2fs_allocate_group_table() -> flexbg_offset() will
+ ultimately search from 0 up to the last block of the flex_bg
+ group, but not afterwards */
+ if (!retried_from_beginning
+ && ext2fs_has_feature_flex_bg(fs->super)) {
+ retried_from_beginning = 1;
+ first_blk = fs->super->s_first_data_block;
+ goto search_for_space;
+ }
+#ifdef RESIZE2FS_DEBUG
+ if (rfs->flags & RESIZE_DEBUG_INODECOUNT)
+ fprintf(stderr, "unable to locate a suitable "
+ "area to make room for group %u\n", g);
+#endif
+ break;
+ }
+ }
+
+ errout:
+ if (meta_bmap)
+ ext2fs_free_block_bitmap(meta_bmap);
+ if (badblock_list)
+ ext2fs_badblocks_list_free(badblock_list);
+
+ return retval;
+
+}
diff --git a/resize/resize2fs.h b/resize/resize2fs.h
index 96a878a7..b294fe60 100644
--- a/resize/resize2fs.h
+++ b/resize/resize2fs.h
@@ -78,6 +78,7 @@ typedef struct ext2_sim_progress *ext2_sim_progmeter;
#define RESIZE_DEBUG_ITABLEMOVE 0x0008
#define RESIZE_DEBUG_RTRACK 0x0010
#define RESIZE_DEBUG_MIN_CALC 0x0020
+#define RESIZE_DEBUG_INODECOUNT 0x0040
#define RESIZE_PERCENT_COMPLETE 0x0100
#define RESIZE_VERBOSE 0x0200
@@ -85,6 +86,9 @@ typedef struct ext2_sim_progress *ext2_sim_progmeter;
#define RESIZE_ENABLE_64BIT 0x0400
#define RESIZE_DISABLE_64BIT 0x0800
+#define RESIZE_INCREASE_INODE_COUNT 0x1000
+#define RESIZE_DECREASE_INODE_COUNT 0x2000
+
/*
* This structure is used for keeping track of how much resources have
* been used for a particular resize2fs pass.
@@ -99,6 +103,12 @@ struct resource_track {
unsigned long long bytes_written;
};
+typedef enum {
+ itable_status_not_allocated = 0, /*must be zero*/
+ itable_status_allocated = 1,
+ itable_status_populated = 2
+} itable_status;
+
/*
* The core state structure for the ext2 resizer
*/
@@ -115,6 +125,14 @@ struct ext2_resize_struct {
int flags;
char *itable_buf;
+ /*
+ * Specific fields to change inode count
+ */
+ unsigned int new_inodes_per_group;
+ unsigned int *evacuated_inodes;
+ itable_status *new_itable_status;
+ dgrp_t allocated_new_itables;
+
/*
* For the block allocator
*/
@@ -141,7 +159,8 @@ struct ext2_resize_struct {
/* prototypes */
-extern errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size, int flags,
+extern errcode_t resize_fs(ext2_filsys fs, blk64_t *new_size,
+ unsigned int new_inodes_per_group, int flags,
errcode_t (*progress)(ext2_resize_t rfs,
int pass, unsigned long cur,
unsigned long max));
@@ -151,6 +170,7 @@ extern errcode_t adjust_fs_info(ext2_filsys fs, ext2_filsys old_fs,
blk64_t new_size);
extern blk64_t calculate_minimum_resize_size(ext2_filsys fs, int flags);
extern void adjust_new_size(ext2_filsys fs, blk64_t *sizep);
+extern errcode_t mark_table_blocks(ext2_filsys fs, ext2fs_block_bitmap bmap);
/* extent.c */
diff --git a/tests/r_inode_count_decrease_translate_number/expect b/tests/r_inode_count_decrease_translate_number/expect
new file mode 100644
index 00000000..a9cf4930
--- /dev/null
+++ b/tests/r_inode_count_decrease_translate_number/expect
@@ -0,0 +1,41 @@
+Creating filesystem with 147456 1k blocks and 36864 inodes
+Superblock backups stored on blocks:
+ 8193, 24577, 40961, 57345, 73729
+
+Allocating group tables: .....done
+Writing inode tables: .....done
+Writing superblocks and filesystem accounting information: .....done
+
+Checksum is 1569341821
+debugfs: write file_outside file
+Allocated inode: 12
+debugfs: copy_inode file <36864>
+debugfs: seti <36864>
+debugfs: ln <36864> file2
+debugfs: sif <12> block[0] 0
+debugfs: rm file
+
+debugfs: set_bg 17 free_inodes_count 2047
+debugfs: ssv free_inodes_count 36852
+debugfs: quit
+
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 12/36864 files (0.0% non-contiguous), 10816/147456 blocks
+Changing inode count on the filesystem.
+The filesystem on test.img now has 144 inodes.
+
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 12/144 files (0.0% non-contiguous), 1636/147456 blocks
+debugfs: dump /file2 file_outside
+debugfs: quit
+
+../tests/progs/crcsum file_outside
+Checksum is 1569341821
diff --git a/tests/r_inode_count_decrease_translate_number/name b/tests/r_inode_count_decrease_translate_number/name
new file mode 100644
index 00000000..cc261215
--- /dev/null
+++ b/tests/r_inode_count_decrease_translate_number/name
@@ -0,0 +1 @@
+decrease inode count with number translate
diff --git a/tests/r_inode_count_decrease_translate_number/script b/tests/r_inode_count_decrease_translate_number/script
new file mode 100644
index 00000000..fb1d36bd
--- /dev/null
+++ b/tests/r_inode_count_decrease_translate_number/script
@@ -0,0 +1,76 @@
+
+if ! test -x $RESIZE2FS_EXE -o ! -x $DEBUGFS_EXE; then
+ echo "$test_name: $test_description: skipped (no debugfs/resize2fs)"
+ return 0
+fi
+
+LOG=$test_name.log
+OUT_TMP=file_outside
+EXP=$test_dir/expect
+fail=0
+
+$MKE2FS -N 36864 $TMPFILE 144m >> $LOG 2>&1
+
+
+echo AAAAAAAA > $OUT_TMP 2>> $LOG
+CSUM_1=$($CRCSUM $OUT_TMP)
+echo Checksum is $CSUM_1 >> $LOG
+
+
+#make sure we create an inode that needs to be translated for the test
+$DEBUGFS -w $TMPFILE >> $LOG 2>&1 << EOF
+write $OUT_TMP file
+copy_inode file <36864>
+seti <36864>
+ln <36864> file2
+sif <12> block[0] 0
+rm file
+set_bg 17 free_inodes_count 2047
+ssv free_inodes_count 36852
+quit
+EOF
+echo " " >> $LOG
+
+$FSCK -fy -N test_filesys $TMPFILE >> $LOG 2>&1 || fail=1
+
+$RESIZE2FS -i 136 -f $TMPFILE >> $LOG 2>&1 || fail=1
+
+$FSCK -fy -N test_filesys $TMPFILE >> $LOG 2>&1 || fail=1
+
+
+rm -f $OUT_TMP
+
+$DEBUGFS $TMPFILE >> $LOG 2>&1 << EOF
+dump /file2 $OUT_TMP
+quit
+EOF
+echo " " >> $LOG
+
+echo $CRCSUM $OUT_TMP >> $LOG 2>&1
+CSUM_2=$($CRCSUM $OUT_TMP)
+echo Checksum is $CSUM_2 >> $LOG
+
+rm -f $OUT_TMP $TMPFILE
+
+
+if test "$CSUM_1" != "$CSUM_2"
+then
+ fail=1
+ echo crc check failed >> $LOG
+fi
+
+sed -f $cmd_dir/filter.sed < $LOG > $LOG.new
+mv $LOG.new $LOG
+
+cmp -s $LOG $EXP || fail=1
+
+if [ "$fail" = 0 ]; then
+ echo "$test_name: $test_description: ok"
+ touch $test_name.ok
+else
+ echo "$test_name: $test_description: failed"
+ diff $DIFF_OPTS $LOG $EXP >> $test_name.failed
+fi
+
+unset CSUM_1 CSUM_2 LOG OUT_TMP fail
+
diff --git a/tests/r_inode_count_increase_almost_full/expect b/tests/r_inode_count_increase_almost_full/expect
new file mode 100644
index 00000000..9efc8c65
--- /dev/null
+++ b/tests/r_inode_count_increase_almost_full/expect
@@ -0,0 +1,35 @@
+Creating filesystem with 147456 1k blocks and 36864 inodes
+Superblock backups stored on blocks:
+ 8193, 24577, 40961, 57345, 73729
+
+Allocating group tables: .....done
+Writing inode tables: .....done
+Writing superblocks and filesystem accounting information: .....done
+
+Checksum is 3565307003
+debugfs: mkdir test
+debugfs: cd test
+debugfs: write file_outside file_inside
+Allocated inode: 13
+debugfs: quit
+
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 13/36864 files (0.0% non-contiguous), 123257/147456 blocks
+Changing inode count on the filesystem.
+The filesystem on test.img now has 54432 inodes.
+
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 13/54432 files (0.0% non-contiguous), 127649/147456 blocks
+debugfs: dump /test/file_inside file_outside
+debugfs: quit
+
+../tests/progs/crcsum file_outside
+Checksum is 3565307003
diff --git a/tests/r_inode_count_increase_almost_full/name b/tests/r_inode_count_increase_almost_full/name
new file mode 100644
index 00000000..c876654a
--- /dev/null
+++ b/tests/r_inode_count_increase_almost_full/name
@@ -0,0 +1 @@
+increase inode count fs almost full
diff --git a/tests/r_inode_count_increase_almost_full/script b/tests/r_inode_count_increase_almost_full/script
new file mode 100644
index 00000000..bbdc5e17
--- /dev/null
+++ b/tests/r_inode_count_increase_almost_full/script
@@ -0,0 +1,67 @@
+
+if ! test -x $RESIZE2FS_EXE -o ! -x $DEBUGFS_EXE; then
+ echo "$test_name: $test_description: skipped (no debugfs/resize2fs)"
+ return 0
+fi
+
+LOG=$test_name.log
+OUT_TMP=file_outside
+EXP=$test_dir/expect
+fail=0
+
+$MKE2FS $TMPFILE 144m >> $LOG 2>&1
+
+dd if=/dev/zero bs=4096 count=28000 status=none 2>>$LOG | tr '\000' '\377' > $OUT_TMP 2>> $LOG
+CSUM_1=$($CRCSUM $OUT_TMP)
+echo Checksum is $CSUM_1 >> $LOG
+
+$DEBUGFS -w $TMPFILE >> $LOG 2>&1 << EOF
+mkdir test
+cd test
+write $OUT_TMP file_inside
+quit
+EOF
+echo " " >> $LOG
+
+$FSCK -fy -N test_filesys $TMPFILE >> $LOG 2>&1 || fail=1
+
+$RESIZE2FS -i 54400 -f $TMPFILE >> $LOG 2>&1 || fail=1
+
+$FSCK -fy -N test_filesys $TMPFILE >> $LOG 2>&1 || fail=1
+
+
+rm -f $OUT_TMP
+
+$DEBUGFS $TMPFILE >> $LOG 2>&1 << EOF
+dump /test/file_inside $OUT_TMP
+quit
+EOF
+echo " " >> $LOG
+
+echo $CRCSUM $OUT_TMP >> $LOG 2>&1
+CSUM_2=$($CRCSUM $OUT_TMP)
+echo Checksum is $CSUM_2 >> $LOG
+
+rm -f $OUT_TMP $TMPFILE
+
+if test "$CSUM_1" != "$CSUM_2"
+then
+ fail=1
+ echo crc check failed >> $LOG
+fi
+
+sed -f $cmd_dir/filter.sed < $LOG > $LOG.new
+mv $LOG.new $LOG
+
+cmp -s $LOG $EXP || fail=1
+
+if [ "$fail" = 0 ]; then
+ echo "$test_name: $test_description: ok"
+ touch $test_name.ok
+else
+ echo "$test_name: $test_description: failed"
+ diff $DIFF_OPTS $LOG $EXP >> $test_name.failed
+fi
+
+unset CSUM_1 CSUM_2 LOG OUT_TMP fail
+
--
2.43.0
Powered by blists - more mailing lists