diff -rub -x .[a-z]* linux-2.2.16t/CREDITS linux-2.2.16-online/CREDITS
--- linux-2.2.16t/CREDITS	Tue Jan  4 11:12:10 2000
+++ linux-2.2.16-online/CREDITS	Wed May 24 18:21:08 2000
@@ -461,7 +461,7 @@
 N: Andreas Dilger
 E: adilger@turbolinux.com
 W: http://www-mddsp.enel.ucalgary.ca/People/adilger/
-D: Ext2 filesystem online resize capability
+D: Ext2/Ext3 filesystem online resize capability
 S: 630 Schooner Cove N.W.
 S: Calgary, AB
 S: Canada   T3L 1Z1
diff -rub -x .[a-z]* linux-2.2.16t/Documentation/Configure.help linux-2.2.16-online/Documentation/Configure.help
--- linux-2.2.16t/Documentation/Configure.help	Tue Jan  4 11:12:10 2000
+++ linux-2.2.16-online/Documentation/Configure.help	Mon May 29 16:00:42 2000
@@ -7808,6 +7808,19 @@
   order to actually resize your filesystem, otherwise this feature
   will just sit unused inside the kernel.

+Online resize for ext3 filesystems
+CONFIG_EXT3_RESIZE
+  This option gives you the ability to increase the size of an ext3
+  filesystem while it is mounted (in use).  In order to do this, you
+  must also be able to resize the underlying disk partition, probably
+  via a Logical Volume Manager (LVM), metadevice (MD), or hardware
+  RAID device - none of that capability is included in this feature.
+  If you don't know what any of these things are, or you haven't
+  configured your kernel for them, you should probably say N here.  If
+  you choose Y, then your kernel will be about 3k larger, and you need
+  to get some more software (http://ext2resize.sourceforge.net/) in
+  order to actually resize your filesystem, otherwise this feature
+  will just sit unused inside the kernel.
 
 ISO 9660 CDROM filesystem support
 CONFIG_ISO9660_FS
diff -rub -x .[a-z]* linux-2.2.16t/fs/Config.in linux-2.2.16-online/fs/Config.in
--- linux-2.2.16t/fs/Config.in	Wed May 31 13:13:57 2000
+++ linux-2.2.16-online/fs/Config.in	Wed May 31 10:18:03 2000
@@ -56,6 +56,9 @@
 # CONFIG_JFS should be its own option, but for now we make it depend on ext3
 if [ "$CONFIG_EXT3_FS" != "n" ]; then
   define_bool CONFIG_JFS y
+fi
+if [ "$CONFIG_EXT3_FS" != "n" -a "$CONFIG_EXPERIMENTAL" = "y" ]; then
+  bool '  Online ext3 resize support (DANGEROUS)' CONFIG_EXT3_RESIZE
 fi
 
 tristate 'System V and Coherent filesystem support' CONFIG_SYSV_FS
diff -ru linux-2.2.16t/fs/ext3/balloc.c linux-2.2.16-online/fs/ext3/balloc.c
--- linux-2.2.16t/fs/ext3/balloc.c	Tue Jul  4 18:07:43 2000
+++ linux-2.2.16-online/fs/ext3/balloc.c	Wed Jul  5 14:32:47 2000
@@ -643,13 +643,13 @@
 
 unsigned long ext3_count_free_blocks (struct super_block * sb)
 {
-#ifdef EXT3FS_DEBUG
 	struct ext3_super_block * es;
 	unsigned long desc_count, bitmap_count, x;
 	int bitmap_nr;
 	struct ext3_group_desc * gdp;
 	int i;
 	
+	if (test_opt (sb, DEBUG) && test_opt (sb, CHECK_STRICT)) {
 	lock_super (sb);
 	es = sb->u.ext3_sb.s_es;
 	desc_count = 0;
@@ -670,13 +670,14 @@
 			i, le16_to_cpu(gdp->bg_free_blocks_count), x);
 		bitmap_count += x;
 	}
-	printk("ext3_count_free_blocks: stored = %lu, computed = %lu, %lu\n",
-	       le32_to_cpu(es->s_free_blocks_count), desc_count, bitmap_count);
+	printk ("ext3_count_free_blocks: stored = %u, computed gdt = %lu, "
+		"bitmap = %lu\n", le32_to_cpu(es->s_free_blocks_count),
+		desc_count, bitmap_count);
 	unlock_super (sb);
 	return bitmap_count;
-#else
+	}
+	else
 	return le32_to_cpu(sb->u.ext3_sb.s_es->s_free_blocks_count);
-#endif
 }
 
 static inline int block_in_use (unsigned long block,
diff -ru linux-2.2.16t/fs/ext3/inode.c linux-2.2.16-online/fs/ext3/inode.c
--- linux-2.2.16t/fs/ext3/inode.c	Tue Jul  4 18:07:43 2000
+++ linux-2.2.16-online/fs/ext3/inode.c	Wed Jul  5 15:13:37 2000
@@ -460,6 +461,149 @@
 			     inode->i_sb->s_blocksize, b, err);
 }
 
+#ifdef CONFIG_EXT3_RESIZE
+/* Set an inode block at a specific block offset in the inode. */
+static
+int inode_set_block(struct inode *inode, unsigned long block, unsigned int val)
+{
+	handle_t *handle;
+	struct ext3_iloc iloc;
+	int addr_per_block = EXT3_ADDR_PER_BLOCK(inode->i_sb);
+	int err = 0;
+
+	/* we don't handle triple indirect blocks */
+	if (block >
+	    EXT3_NDIR_BLOCKS + addr_per_block + addr_per_block * addr_per_block)
+		return -EINVAL;
+
+	/* We will change only one of: inode OR indirect OR dindirect block */
+	handle = ext3_journal_start(inode, 2);
+	if (IS_ERR(handle))
+		return PTR_ERR(handle);
+	if ((err = ext3_reserve_inode_write(handle, inode, &iloc)))
+		goto exit_handle;
+	if ((block + 1) * inode->i_sb->s_blksize - 1 > inode->i_size)
+		inode->i_size = (block + 1) * inode->i_sb->s_blksize - 1;
+	if (block < EXT3_NDIR_BLOCKS) {
+		jfs_debug(5, "setting direct block %ld\n", block);
+		inode_bmap(inode, block) = val;
+		goto exit;
+	}
+	block -= EXT3_NDIR_BLOCKS;
+	if (block < addr_per_block) {
+		unsigned long indir;
+		struct buffer_head *bh;
+
+		indir = inode_bmap (inode, EXT3_IND_BLOCK);
+		jfs_debug(5, "setting block %ld in indirect block %ld\n", block,
+			  indir);
+		bh = bread (inode->i_dev, indir, inode->i_sb->s_blocksize);
+
+		journal_get_write_access(handle, bh);
+		((u32 *) bh->b_data)[block] = 0;
+		journal_dirty_metadata(handle, bh);
+		brelse(bh);
+		goto exit;
+	}
+	block -= addr_per_block;
+	{
+		unsigned long indir;
+		int addr_per_block_bits = EXT3_ADDR_PER_BLOCK_BITS(inode->i_sb);
+		struct buffer_head *bh;
+
+		indir = inode_bmap (inode, EXT3_DIND_BLOCK);
+		jfs_debug(5, "getting block %ld in indirect block %ld\n",
+			  block >> addr_per_block_bits, indir);
+		indir = block_bmap (bread (inode->i_dev, indir,
+					   inode->i_sb->s_blocksize),
+				    block >> addr_per_block_bits);
+		jfs_debug(5, "setting block %ld in dindirect block %ld\n",
+			  block & (addr_per_block - 1), indir);
+		bh = bread (inode->i_dev, indir, inode->i_sb->s_blocksize);
+		block &= (addr_per_block - 1);
+		journal_get_write_access(handle, bh);
+		((u32 *) bh->b_data)[block] = val;
+		journal_dirty_metadata(handle, bh);
+		brelse(bh);
+	}
+exit:
+	err = ext3_mark_iloc_dirty(handle, inode);
+exit_handle:
+	ext3_journal_stop(handle, inode);
+	return err;
+}
+
+/* Add a block to the reserved inode from a new group */
+int ext3_ioc_resize_rsv_add(struct inode *inode, struct ext3_rsv_data *input)
+{
+	struct super_block *sb;
+	struct ext3_super_block *es;
+	long block;
+	int offset;
+	int group;
+
+	if (!input || !inode || inode->i_ino != EXT3_RESIZE_INO ||
+	    input->disk_block_num < 2)
+		return -EINVAL;
+
+	sb = inode->i_sb;
+	es = EXT3_SB(sb)->s_es;
+	group = (input->disk_block_num - le32_to_cpu(es->s_first_data_block)) /
+		EXT3_BLOCKS_PER_GROUP(sb);
+	offset = (input->disk_block_num - le32_to_cpu(es->s_first_data_block)) %
+		EXT3_BLOCKS_PER_GROUP(sb);
+	block = input->inode_block_num;
+
+	if (ext3_bmap(inode, block) != 0 || !ext3_group_sparse(group) ||
+	    input->disk_block_num < le32_to_cpu(es->s_blocks_count) ||
+	    offset < 1) {
+		ext3_warning(inode->i_sb, __FUNCTION__,
+			     "inode %ld had block %ld for group %d set "
+			     "at offset %ld, or rel block %d not in GDT\n",
+			     inode->i_ino, input->disk_block_num, group,
+			     block, offset);
+		return -EINVAL;
+	}
+
+	return inode_set_block(inode, block, input->disk_block_num);
+}
+
+/* Delete a block from the reserved inode which is now a group descriptor */
+int ext3_ioc_resize_rsv_del(struct inode *inode, struct ext3_rsv_data *input)
+{
+	struct super_block *sb;
+	struct ext3_super_block *es;
+	long block;
+	int offset;
+	int group;
+
+	if (!input || !inode || inode->i_ino != EXT3_RESIZE_INO ||
+	    input->disk_block_num < 2)
+		return -EINVAL;
+
+	sb = inode->i_sb;
+	es = EXT3_SB(sb)->s_es;
+	group = (input->disk_block_num - le32_to_cpu(es->s_first_data_block)) /
+		EXT3_BLOCKS_PER_GROUP(sb);
+	offset = (input->disk_block_num - le32_to_cpu(es->s_first_data_block)) %
+		EXT3_BLOCKS_PER_GROUP(sb);
+	block = input->inode_block_num;
+
+	if (ext3_bmap(inode, block) != input->disk_block_num ||
+	    !ext3_group_sparse(group) ||
+	    offset < 1 || offset > EXT3_SB(sb)->s_gdb_count) {
+		ext3_warning(inode->i_sb, __FUNCTION__,
+			     "inode %ld did not have block %ld for group %d "
+			     "at offset %ld, or rel block %d not in GDT\n",
+			     inode->i_ino, input->disk_block_num, group,
+			     block, offset);
+		return -EINVAL;
+	}
+
+	return inode_set_block(inode, block, 0);
+}
+#endif /* CONFIG_EXT3_RESIZE */
+
 /* For ext3_bread, we need a transaction handle iff create is true. */
 
 struct buffer_head * ext3_bread (handle_t *handle, 
@@ -532,7 +668,7 @@
 		
 	if ((inode->i_ino != EXT3_ROOT_INO && inode->i_ino != EXT3_ACL_IDX_INO &&
 	     inode->i_ino != EXT3_ACL_DATA_INO && inode->i_ino != EXT3_JOURNAL_INO &&
-	     inode->i_ino < EXT3_FIRST_INO(inode->i_sb)) ||
+	     inode->i_ino != EXT3_RESIZE_INO && inode->i_ino < EXT3_FIRST_INO(inode->i_sb)) ||
 	    inode->i_ino > le32_to_cpu(inode->i_sb->u.ext3_sb.s_es->s_inodes_count)) {
 		ext3_error (inode->i_sb, __FUNCTION__,
 			    "bad inode number: %lu", inode->i_ino);
diff -ru linux-2.2.16t/fs/ext3/ioctl.c linux-2.2.16-online/fs/ext3/ioctl.c
--- linux-2.2.16t/fs/ext3/ioctl.c	Tue Jul  4 18:07:43 2000
+++ linux-2.2.16-online/fs/ext3/ioctl.c	Wed Jul  5 15:16:26 2000
@@ -11,11 +11,108 @@
 
 #include <linux/errno.h>
 #include <linux/fs.h>
-#include <linux/ext3_fs.h>
+#include <linux/ext3_jfs.h>
+#include <linux/locks.h>
 #include <linux/ioctl.h>
 #include <linux/sched.h>
 #include <linux/mm.h>
 
+#ifdef CONFIG_EXT3_RESIZE
+/* Add group descriptor data to an existing or new group descriptor table. */
+static
+int ext3_ioc_add_group(struct inode *inode, struct ext3_new_group_data *input)
+{
+	struct super_block *sb;
+	struct ext3_super_block *es;
+	int gdbnum;
+	unsigned long gdblock;
+	struct buffer_head *bh;
+	struct ext3_group_desc *gdp;
+	handle_t *handle;
+	int err;
+
+	if (!input || !inode || inode->i_ino != EXT3_RESIZE_INO)
+		return -EINVAL;
+
+	sb = inode->i_sb;
+	es = EXT3_SB(sb)->s_es;
+	if (input->group_num <= EXT3_SB(sb)->s_groups_count) {
+		ext3_warning(sb, __FUNCTION__,
+			     "won't overwrite existing group %d\n",
+			     input->group_num);
+		return -EINVAL;
+	}
+
+	gdbnum = input->group_num / EXT3_DESC_PER_BLOCK(sb);
+	gdblock = gdbnum + EXT3_SB(sb)->s_sbh->b_blocknr + 1;
+
+	/* If we are updating a group past the last current gd block,
+	 * we need to load it ourselves.
+	 */
+	handle = ext3_journal_start(inode, 1);
+	if (!handle)
+		return -ENOMEM;
+
+	if (gdbnum >= EXT3_SB(sb)->s_gdb_count) {
+
+		bh = ext3_getblk(handle, inode, input->inode_block_num,0,&err);
+		if (!bh) {
+			ext3_warning(sb, __FUNCTION__,
+				     "error reading inode %ld block %d\n",
+				     inode->i_ino, input->inode_block_num);
+			goto exit;
+		}
+
+		/* check that we have this block reserved */
+		if (bh->b_blocknr != gdblock) {
+			ext3_warning(sb, __FUNCTION__,
+				     "inode %ld did not have block %ld for "
+				     "group %d at offset %d (got block %ld)\n",
+				     inode->i_ino, gdblock, input->group_num,
+				     input->inode_block_num, bh->b_blocknr);
+			journal_release_buffer(handle, bh);
+			err = -EINVAL;
+			goto exit;
+		}
+
+		if (!buffer_uptodate(bh)) {
+			ll_rw_block (READ, 1, &bh);
+			wait_on_buffer (bh);
+			if (!buffer_uptodate(bh)) {
+				journal_release_buffer(handle, bh);
+				err = -EIO;
+				goto exit;
+			}
+		}
+	} else {
+		bh = EXT3_SB(sb)->s_group_desc[gdbnum];
+	}
+	gdp = ((struct ext3_group_desc *)
+	       bh->b_data) + (input->group_num % EXT3_DESC_PER_BLOCK(sb));
+
+	journal_get_write_access(handle, bh);
+
+	/* It is OK if we put potentially bad data into the new GDT,
+	 * as it is verified later by ext3_check_descriptors() when
+	 * the group is acutally put into use.
+	 */
+	gdp->bg_block_bitmap = cpu_to_le32(input->block_bitmap);
+	gdp->bg_inode_bitmap = cpu_to_le32(input->inode_bitmap);
+	gdp->bg_inode_table = cpu_to_le32(input->inode_table);
+	gdp->bg_free_blocks_count = cpu_to_le16(input->free_blocks_count);
+	gdp->bg_free_inodes_count = cpu_to_le16(input->free_inodes_count);
+	journal_dirty_metadata(handle, bh);
+
+	err = 0;
+exit:
+	handle->h_sync = 1;
+	ext3_journal_stop(handle, inode);
+	if (gdbnum >= EXT3_SB(sb)->s_gdb_count)
+		brelse(bh);
+	return err;
+}
+#endif /* CONFIG_EXT3_RESIZE */
+
 int ext3_ioctl (struct inode * inode, struct file * filp, unsigned int cmd,
 		unsigned long arg)
 {
@@ -80,6 +177,45 @@
 		inode->i_generation = inode->u.ext3_i.i_version;
 		mark_inode_dirty(inode);
 		return 0;
+#ifdef CONFIG_EXT3_RESIZE
+	case EXT3_IOC_RESIZE_NEW_GROUP: {
+		struct ext3_new_group_data input;
+		struct inode * resize;
+		int err;
+
+		if (!capable(CAP_SYS_RESOURCE))
+			return -EPERM;
+		if (copy_from_user(&input, (struct ext3_new_group_data *)arg,
+				   sizeof(input)))
+			return -EFAULT;
+		/* use the resize inode, no matter what inode we are passed */
+		resize = iget(inode->i_sb, EXT3_RESIZE_INO);
+		err = ext3_ioc_add_group(resize, &input);
+		iput(resize);
+		return err;
+	}
+	case EXT3_IOC_RESIZE_RSV_ADD:
+	case EXT3_IOC_RESIZE_RSV_DEL: {
+		struct ext3_rsv_data input;
+		struct inode * resize;
+		int err;
+
+		if (!capable(CAP_SYS_RESOURCE))
+			return -EPERM;
+		if (copy_from_user(&input, (struct ext3_rsv_data *)arg,
+				   sizeof(input)))
+			return -EFAULT;
+
+		/* use the resize inode, no matter what inode we are passed */
+		resize = iget(inode->i_sb, EXT3_RESIZE_INO);
+		if (cmd == EXT3_IOC_RESIZE_RSV_DEL)
+			err = ext3_ioc_resize_rsv_del(resize, &input);
+		else
+			err = ext3_ioc_resize_rsv_add(resize, &input);
+		iput(resize);
+		return err;
+	}
+#endif /* CONFIG_EXT3_RESIZE */
 	default:
 		return -ENOTTY;
 	}
diff -ru linux-2.2.16t/fs/ext3/super.c linux-2.2.16-online/fs/ext3/super.c
--- linux-2.2.16t/fs/ext3/super.c	Tue Jul  4 18:07:43 2000
+++ linux-2.2.16-online/fs/ext3/super.c	Wed Jul  5 15:19:56 2000
@@ -14,6 +14,7 @@
  *
  *  Big-endian to little-endian byte-swapping/bitmaps by
  *        David S. Miller (davem@caip.rutgers.edu), 1995
+ *  Online resize by Andreas Dilger (adilger@turbolinux.com), July 1999
  */
 
 #include <linux/module.h>
@@ -47,6 +48,10 @@
 static void ext3_mark_recovery_complete(struct super_block * sb,
 					struct ext3_super_block * es);
 
+#ifndef MIN
+# define MIN(m,n) ((m) < (n) ? (m) : (n))
+#endif
+
 void ext3_error (struct super_block * sb, const char * function,
 		 const char * fmt, ...)
 {
@@ -164,6 +169,8 @@
 			  unsigned short *resuid, unsigned short * resgid,
 			  unsigned long * mount_options, 
 			  unsigned long * inum,
+			  unsigned long * n_blocks_count,
+			  unsigned long * resgdt,
 			  int is_remount)
 {
 	char * this_char;
@@ -237,6 +244,31 @@
 		else if (!strcmp (this_char, "nogrpid") ||
 			 !strcmp (this_char, "sysvgroups"))
 			clear_opt (*mount_options, GRPID);
+#ifdef CONFIG_EXT3_RESIZE
+		else if (!strcmp (this_char, "resize")) {
+			printk ("EXT3-fs: parse_options: resize=%s\n", value);
+			if (!n_blocks_count) {
+				printk("EXT3-fs: resize option only available "
+				       "for remount\n");
+				return 0;
+			}
+			if (!value || !*value) {
+				printk ("EXT3-fs: resize requires number of "
+					"blocks\n");
+				return 0;
+			}
+			*n_blocks_count = simple_strtoul (value, &value, 0);
+			if (*value == ':') {
+				value++;
+				*resgdt = simple_strtoul (value, &value, 0);
+			}
+			if (*value) {
+				printk ("EXT3-fs: invalid resize option: %s\n",
+					value);
+				return 0;
+			}
+		}
+#endif /* CONFIG_EXT3_RESIZE */
 		else if (!strcmp (this_char, "resgid")) {
 			if (!value || !*value) {
 				printk ("EXT3-fs: the resgid option requires "
@@ -349,10 +381,10 @@
 		EXT3_SET_INCOMPAT_FEATURE(sb, RECOVER);
 		ext3_commit_super (sb, es, 1);
 		if (test_opt (sb, DEBUG))
-			printk ("[EXT II FS %s, %s, bs=%lu, fs=%lu, gc=%lu, "
+			printk ("[EXT II FS %s, %s, bs=%lu, bc=%u, gc=%lu, "
 				"bpg=%lu, ipg=%lu, mo=%04lx]\n",
 				EXT3FS_VERSION, EXT3FS_DATE, sb->s_blocksize,
-				sb->u.ext3_sb.s_frag_size,
+				le32_to_cpu(es->s_blocks_count),
 				sb->u.ext3_sb.s_groups_count,
 				EXT3_BLOCKS_PER_GROUP(sb),
 				EXT3_INODES_PER_GROUP(sb),
@@ -387,7 +419,7 @@
 		if (le32_to_cpu(gdp->bg_block_bitmap) < block ||
 		    le32_to_cpu(gdp->bg_block_bitmap) >= block + EXT3_BLOCKS_PER_GROUP(sb))
 		{
-			ext3_error (sb, "ext3_check_descriptors",
+			ext3_warning (sb, "ext3_check_descriptors",
 				    "Block bitmap for group %d"
 				    " not in group (block %lu)!",
 				    i, (unsigned long) le32_to_cpu(gdp->bg_block_bitmap));
@@ -396,7 +428,7 @@
 		if (le32_to_cpu(gdp->bg_inode_bitmap) < block ||
 		    le32_to_cpu(gdp->bg_inode_bitmap) >= block + EXT3_BLOCKS_PER_GROUP(sb))
 		{
-			ext3_error (sb, "ext3_check_descriptors",
+			ext3_warning (sb, "ext3_check_descriptors",
 				    "Inode bitmap for group %d"
 				    " not in group (block %lu)!",
 				    i, (unsigned long) le32_to_cpu(gdp->bg_inode_bitmap));
@@ -406,7 +438,7 @@
 		    le32_to_cpu(gdp->bg_inode_table) + sb->u.ext3_sb.s_itb_per_group >=
 		    block + EXT3_BLOCKS_PER_GROUP(sb))
 		{
-			ext3_error (sb, "ext3_check_descriptors",
+			ext3_warning (sb, "ext3_check_descriptors",
 				    "Inode table for group %d"
 				    " not in group (block %lu)!",
 				    i, (unsigned long) le32_to_cpu(gdp->bg_inode_table));
@@ -418,6 +450,292 @@
 	return 1;
 }
 
+static int ext3_read_descriptors (struct super_block * sb,
+				  unsigned long blocks_count)
+{
+	int db_orig;
+	int db_count;
+	unsigned long groups_count;
+	unsigned long o_groups_count;
+	struct buffer_head ** group_desc = NULL;
+	struct buffer_head ** o_group_desc;
+	int i, j;
+
+	o_group_desc = sb->u.ext3_sb.s_group_desc;
+	o_groups_count = sb->u.ext3_sb.s_groups_count;
+
+	db_orig = sb->u.ext3_sb.s_gdb_count;
+	groups_count = (blocks_count -
+			le32_to_cpu(sb->u.ext3_sb.s_es->s_first_data_block) +
+			EXT3_BLOCKS_PER_GROUP(sb) - 1) /
+			EXT3_BLOCKS_PER_GROUP(sb);
+	db_count = (groups_count + EXT3_DESC_PER_BLOCK(sb) - 1) /
+		   EXT3_DESC_PER_BLOCK(sb);
+	if (test_opt (sb, DEBUG)) {
+		printk ("EXT3-fs: ext3_read_descriptors: o_groups_count=%lu, "
+			"groups_count=%lu, blocks_count=%lu\n", o_groups_count,
+			groups_count, blocks_count);
+		printk ("EXT3-fs: ext3_read_descriptors: db_orig=%d, "
+			"db_count=%d\n", db_orig, db_count);
+	}
+
+	if (db_orig != db_count) {
+		unsigned long logic_sb_block = EXT3_SB(sb)->s_sbh->b_blocknr;
+
+		group_desc = (struct buffer_head **) kmalloc (db_count *
+				sizeof (struct buffer_head *), GFP_KERNEL);
+		if (group_desc == NULL) {
+			ext3_warning (sb, __FUNCTION__,
+				      "not enough memory for %d groups",
+				      db_count);
+			return -ENOMEM;
+		}
+		for (i = db_orig; i < db_count; i++) {	/* Add group blocks */
+			group_desc[i] = bread (sb->s_dev,
+					       logic_sb_block + i + 1,
+					       sb->s_blocksize);
+			if (!group_desc[i]) {
+				for (j = db_orig; j < i; j++)
+					brelse (group_desc[j]);
+				kfree_s (group_desc, db_count *
+					 sizeof (struct buffer_head *));
+				ext3_warning (sb, __FUNCTION__,
+					      "unable to read group block %d",
+					      i);
+				return -EIO;
+			}
+		}
+
+		/*
+		 * Copy over pointers to old descriptor blocks already loaded.
+		 * We need to set s_group_desc and s_groups_count now so that
+		 * ext3_check_descriptors() will check the new groups as well.
+		 */
+		memcpy (group_desc, o_group_desc,
+			MIN(db_orig, db_count) * sizeof (struct buffer_head *));
+		sb->u.ext3_sb.s_group_desc = group_desc;
+	}
+	sb->u.ext3_sb.s_groups_count = groups_count;
+
+	if (!ext3_check_descriptors (sb)) {
+		sb->u.ext3_sb.s_groups_count = o_groups_count;
+		sb->u.ext3_sb.s_group_desc = o_group_desc;
+		for (j = db_orig; j < db_count; j++)
+			brelse (group_desc[j]);
+		if (group_desc != NULL)
+			kfree_s (group_desc,
+				 db_count * sizeof (struct buffer_head *));
+		return -EINVAL;
+	}
+
+	if (o_group_desc && o_group_desc != sb->u.ext3_sb.s_group_desc)
+		kfree_s (o_group_desc, db_orig * sizeof (struct buffer_head *));
+
+	sb->u.ext3_sb.s_gdb_count = db_count;
+
+	return 0;
+} /* ext3_read_descriptors */
+
+#ifdef CONFIG_EXT3_RESIZE
+/* Make the disk blocks in a new group available to the filesystem */
+static int ext3_update_group (struct super_block * sb, unsigned int block_group,
+			      unsigned int reserved, unsigned int resgdt)
+{
+	struct ext3_group_desc * gdp;
+	struct ext3_super_block * es;
+	int blocks;
+	int m_blocks;
+	int inodes;
+	int shrink = reserved > 100 ? 1 : 0;
+	handle_t *handle;
+
+	lock_super (sb);
+	es = sb->u.ext3_sb.s_es;
+	gdp = ext3_get_group_desc (sb, block_group, NULL);
+
+	inodes = le32_to_cpu(gdp->bg_free_inodes_count);
+	blocks = le32_to_cpu(gdp->bg_free_blocks_count);
+	m_blocks = sb->u.ext3_sb.s_itb_per_group + 2 +
+		((!(sb->u.ext3_sb.s_feature_ro_compat &
+		    EXT3_FEATURE_RO_COMPAT_SPARSE_SUPER) ||
+		  ext3_group_sparse(block_group)) ?
+		 1 + sb->u.ext3_sb.s_gdb_count + resgdt : 0) + blocks;
+
+	if (block_group < sb->u.ext3_sb.s_groups_count - 1 &&
+	    m_blocks != sb->u.ext3_sb.s_blocks_per_group) {
+		ext3_warning(sb, __FUNCTION__,
+			     "bad group size for %s group %d (%d blocks) "
+			     "(iblk = %ld, GDT blk %ld, resgdt %d, blocks %d\n",
+			     (!(sb->u.ext3_sb.s_feature_ro_compat &
+				EXT3_FEATURE_RO_COMPAT_SPARSE_SUPER) ||
+			      ext3_group_sparse(block_group)) ? "normal" :
+			     "no-backup", block_group, m_blocks,
+			     sb->u.ext3_sb.s_itb_per_group,
+			     sb->u.ext3_sb.s_gdb_count, resgdt, blocks);
+		unlock_super (sb);
+		return -EINVAL;
+	}
+
+	/* We will update only the superblock at this time */
+	handle = journal_start(EXT3_SB(sb)->s_journal, 1);
+	journal_get_write_access(handle, sb->u.ext3_sb.s_sbh);
+	es->s_free_inodes_count =
+		cpu_to_le32(le32_to_cpu(es->s_free_inodes_count) + inodes);
+	es->s_inodes_count = cpu_to_le32(le32_to_cpu(es->s_inodes_count) +
+		inodes);
+
+	es->s_free_blocks_count =
+		cpu_to_le32(le32_to_cpu(es->s_free_blocks_count) + blocks);
+	es->s_blocks_count = cpu_to_le32(le32_to_cpu(es->s_blocks_count) +
+		m_blocks);
+	es->s_r_blocks_count = cpu_to_le32(le32_to_cpu(es->s_r_blocks_count) +
+		m_blocks * reserved / 100);
+
+	journal_dirty_metadata(handle, sb->u.ext3_sb.s_sbh);
+	journal_stop(handle);
+	sb->s_dirt = 1;
+	unlock_super (sb);
+
+	if (test_opt (sb, DEBUG)) {
+		printk ("EXT3-fs: ext3_update_group: %s %s group %u: "
+			"%u inodes, %u blocks (%u free), %d (%d%%) reserved\n",
+			shrink ? "removing" : "adding",
+			(!(sb->u.ext3_sb.s_feature_ro_compat &
+			   EXT3_FEATURE_RO_COMPAT_SPARSE_SUPER) ||
+			 ext3_group_sparse(block_group)) ? "normal" :
+			"no-super", block_group, inodes, m_blocks, blocks,
+			m_blocks * reserved / 100, reserved);
+		printk ("EXT3-fs: ext3_update_group: %u inodes (%u free), "
+			"%u blocks (%u free)\n",
+			le32_to_cpu(es->s_inodes_count),
+			le32_to_cpu(es->s_free_inodes_count),
+			le32_to_cpu(es->s_blocks_count),
+			le32_to_cpu(es->s_free_blocks_count));
+	}
+
+	return 0;
+} /* ext3_update_group */
+
+/* Resize the filesystem to the new number of blocks specified.  If required,
+ * the new group descriptors should have already been configured for us (we
+ * only check that they are valid).
+ */
+static int ext3_resize_fs (struct super_block * sb,
+			   struct ext3_super_block * es,
+			   unsigned long n_blocks_count, unsigned long resgdt)
+{
+	unsigned long o_blocks_count;
+	unsigned long o_groups_count;
+	unsigned long last, add;
+	unsigned long reserved;
+	struct buffer_head * bh;
+	struct inode inode;
+	unsigned long i;
+	int err = 0;
+
+	o_blocks_count = le32_to_cpu(es->s_blocks_count);
+	o_groups_count = sb->u.ext3_sb.s_groups_count;
+
+	if (test_opt (sb, DEBUG))
+		printk ("EXT3-fs: ext3_resize_fs: from %lu to %lu blocks\n",
+			o_blocks_count, n_blocks_count);
+
+	if (n_blocks_count == 0 || n_blocks_count == o_blocks_count)
+		return 0;
+
+	/* Until we can shrink the FS, we need to check this */
+	if (n_blocks_count < o_blocks_count) {
+		ext3_warning (sb, __FUNCTION__, "error: can't shrink FS!");
+		return -EBUSY;
+	}
+
+	/* See if the device is actually as big as what was requested */
+	bh = bread (sb->s_dev, n_blocks_count - 1, EXT3_BLOCK_SIZE(sb));
+	if (!bh) {
+		ext3_warning (sb, __FUNCTION__,
+			      "unable to read last block, resize aborted");
+		return -ENOSPC;
+	}
+	brelse (bh);
+
+	/* For reserved percentage calculation, we avoid 32-bit overflow. */
+	reserved = o_blocks_count > 10000000 ?
+		(le32_to_cpu(es->s_r_blocks_count) + o_blocks_count / 200) /
+			(o_blocks_count / 100) :
+		(le32_to_cpu(es->s_r_blocks_count) * 100 + o_blocks_count / 2) /
+			o_blocks_count;
+
+	lock_super (sb);
+	if ((err = ext3_read_descriptors (sb, n_blocks_count))) {
+		ext3_warning (sb, __FUNCTION__,
+			      "group descriptor error %d, resize aborted", err);
+		unlock_super (sb);
+		return err;
+	}
+
+	/* Handle the remaining blocks in the last partial group. */
+	last = (o_blocks_count -
+		le32_to_cpu(sb->u.ext3_sb.s_es->s_first_data_block)) %
+		EXT3_BLOCKS_PER_GROUP(sb);
+	if (last != 0) {	/* The last group isn't full yet */
+		handle_t *handle;
+
+		add = EXT3_BLOCKS_PER_GROUP(sb) - last;
+		if (add + o_blocks_count > n_blocks_count)
+			add = n_blocks_count - o_blocks_count;
+		/* We will update the superblock, one inode bitmap and group
+		 * descriptor via ext3_free_blocks().
+		 */
+		handle = journal_start(EXT3_SB(sb)->s_journal, 3);
+		journal_get_write_access(handle, sb->u.ext3_sb.s_sbh);
+		es->s_blocks_count =
+			cpu_to_le32(le32_to_cpu(es->s_blocks_count) + add);
+		es->s_r_blocks_count =
+			cpu_to_le32(le32_to_cpu(es->s_r_blocks_count) +
+				    add * reserved / 100);
+		journal_dirty_metadata (handle, sb->u.ext3_sb.s_sbh);
+		sb->s_dirt = 1;
+		unlock_super (sb);
+		/*
+		 * Fake out an inode enough to "free" the new blocks in this
+		 * group.  Turn off quotas for this inode so it doesn't get
+		 * confused by freeing blocks that don't really exist yet.
+		 */
+		inode.i_sb = sb;
+		for (i = 0; i < MAXQUOTAS; i++)
+			inode.i_dquot[i] = NODQUOT;
+		ext3_free_blocks (handle, &inode, o_blocks_count, add);
+		journal_stop(handle);
+		if (test_opt (sb, DEBUG))
+			printk ("EXT3-fs: ext3_resize_fs: added %lu new blocks "
+				"to %lu blocks in last group\n",
+				add, o_blocks_count);
+	} else
+		unlock_super (sb);
+
+	/*
+	 * Update superblock with remaining new group block/inode counts
+	 */
+	for (i = o_groups_count; i < sb->u.ext3_sb.s_groups_count &&
+	     !(err = ext3_update_group (sb, i, reserved, resgdt)); i++)
+		/* empty loop */;
+
+	if (err || le32_to_cpu(es->s_blocks_count) != n_blocks_count) {
+		ext3_warning (sb, __FUNCTION__,
+			      "specified size does not match new block count "
+			      "(%lu != %u) (err %d)", n_blocks_count,
+			      le32_to_cpu(es->s_blocks_count), err);
+		sb->u.ext3_sb.s_mount_state &= ~EXT3_VALID_FS;
+	} else
+		sb->u.ext3_sb.s_mount_state |= EXT3_VALID_FS;
+
+	journal_flush(EXT3_SB(sb)->s_journal);
+	return err;
+} /* ext3_resize_fs */
+#else  /* !CONFIG_EXT3_RESIZE */
+#define ext3_resize_fs(sb, es, n_blocks_count, resgdt) 0
+#endif /* CONFIG_EXT3_RESIZE */
+
 /* ext3_orphan_cleanup() walks a singly-linked list of inodes (starting at
  * the superblock) which were deleted from all directories, but held open by
  * a process at the time of a crash.  We walk the list and try to delete these
@@ -502,8 +868,7 @@
 	kdev_t dev = sb->s_dev;
 	int blocksize = BLOCK_SIZE;
 	int hblock;
-	int db_count;
-	int i, j, err;
+	int i, err;
 	int needs_recovery;
 	
 	/*
@@ -521,7 +886,7 @@
 	sb->u.ext3_sb.s_mount_opt = 0;
 	set_opt (sb->u.ext3_sb.s_mount_opt, CHECK_NORMAL);
 	if (!parse_options ((char *) data, &sb_block, &resuid, &resgid,
-	    &sb->u.ext3_sb.s_mount_opt, &journal_inum, 0)) {
+	    &sb->u.ext3_sb.s_mount_opt, &journal_inum, NULL, NULL, 0)) {
 		sb->s_dev = 0;
 		return NULL;
 	}
@@ -696,35 +1061,12 @@
 		goto failed_mount;
 	}
 
-	sb->u.ext3_sb.s_groups_count = (le32_to_cpu(es->s_blocks_count) -
-				        le32_to_cpu(es->s_first_data_block) +
-				       EXT3_BLOCKS_PER_GROUP(sb) - 1) /
-				       EXT3_BLOCKS_PER_GROUP(sb);
-	db_count = (sb->u.ext3_sb.s_groups_count + EXT3_DESC_PER_BLOCK(sb) - 1) /
-		   EXT3_DESC_PER_BLOCK(sb);
-	sb->u.ext3_sb.s_group_desc = kmalloc (db_count * sizeof (struct buffer_head *), GFP_KERNEL);
-	if (sb->u.ext3_sb.s_group_desc == NULL) {
-		printk ("EXT3-fs: not enough memory\n");
-		goto failed_mount;
-	}
-	for (i = 0; i < db_count; i++) {
-		sb->u.ext3_sb.s_group_desc[i] = bread (dev, logic_sb_block + i + 1,
-						       sb->s_blocksize);
-		if (!sb->u.ext3_sb.s_group_desc[i]) {
-			for (j = 0; j < i; j++)
-				brelse (sb->u.ext3_sb.s_group_desc[j]);
-			kfree_s (sb->u.ext3_sb.s_group_desc,
-				 db_count * sizeof (struct buffer_head *));
-			printk ("EXT3-fs: unable to read group descriptors\n");
-			goto failed_mount;
-		}
-	}
-	if (!ext3_check_descriptors (sb)) {
-		for (j = 0; j < db_count; j++)
-			brelse (sb->u.ext3_sb.s_group_desc[j]);
-		kfree_s (sb->u.ext3_sb.s_group_desc,
-			 db_count * sizeof (struct buffer_head *));
-		printk ("EXT3-fs: group descriptors corrupted !\n");
+	sb->u.ext3_sb.s_gdb_count = 0;
+	sb->u.ext3_sb.s_groups_count = 0;
+	sb->u.ext3_sb.s_group_desc = NULL;
+	if ((i = ext3_read_descriptors (sb, le32_to_cpu(es->s_blocks_count)))) {
+		ext3_error (sb, __FUNCTION__,
+			    "group descriptor error %d, unable to mount", i);
 		goto failed_mount;
 	}
 	for (i = 0; i < EXT3_MAX_GROUP_LOADED; i++) {
@@ -735,7 +1077,6 @@
 	}
 	sb->u.ext3_sb.s_loaded_inode_bitmaps = 0;
 	sb->u.ext3_sb.s_loaded_block_bitmaps = 0;
-	sb->u.ext3_sb.s_gdb_count = db_count;
 	/*
 	 * set up enough so that it can read an inode
 	 */
@@ -779,11 +1120,11 @@
  error_out:
 	sb->s_dev = 0;
 
-	for (i = 0; i < db_count; i++)
+	for (i = 0; i < sb->u.ext3_sb.s_gdb_count; i++)
 		if (sb->u.ext3_sb.s_group_desc[i])
 			brelse (sb->u.ext3_sb.s_group_desc[i]);
 	kfree_s (sb->u.ext3_sb.s_group_desc,
-		 db_count * sizeof (struct buffer_head *));
+		 sb->u.ext3_sb.s_gdb_count * sizeof (struct buffer_head *));
 	brelse (bh);
 	printk ("EXT3-fs: get root inode failed\n");
 	MOD_DEC_USE_COUNT;
@@ -822,6 +1163,7 @@
 {
 	journal_t *journal;
 	int journal_inum = le32_to_cpu(es->s_journal_inum);
+	unsigned long o_blocks = le32_to_cpu(es->s_blocks_count);
 	int err;
 	
 	/*
@@ -860,7 +1202,19 @@
 
 	EXT3_SB(sb)->s_journal = journal;
	ext3_clear_journal_err(sb, es);
-	return 0;
+
+	/*
+	 * If the total number of blocks in the filesystem has changed,
+	 * we need to try and re-load the group descriptors because a
+	 * resize was in the journal when the system crashed.
+	 */
+	if (o_blocks != le32_to_cpu(es->s_blocks_count)) {
+		jfs_debug(4, "resize(s) from %ld to %ld blocks in journal\n",
+			  o_blocks, le32_to_cpu(es->s_blocks_count));
+		err = ext3_read_descriptors(sb,le32_to_cpu(es->s_blocks_count));
+	}
+
+	return err;
 }
 
 static int ext3_create_journal(struct super_block * sb,
@@ -971,6 +1325,8 @@
 	struct ext3_super_block * es;
 	unsigned short resuid = sb->u.ext3_sb.s_resuid;
 	unsigned short resgid = sb->u.ext3_sb.s_resgid;
+	unsigned long n_blocks_count = 0;
+	unsigned long resgdt = 0;
 	unsigned long new_mount_opt;
 	unsigned long tmp;
 
@@ -979,7 +1335,7 @@
 	 */
 	new_mount_opt = sb->u.ext3_sb.s_mount_opt;
 	if (!parse_options (data, &tmp, &resuid, &resgid,
-			    &new_mount_opt, &tmp, 1))
+			    &new_mount_opt, &tmp, &n_blocks_count, &resgdt, 1))
 		return -EINVAL;
 
 	if (new_mount_opt & EXT3_MOUNT_ABORT)
@@ -989,7 +1345,9 @@
 	sb->u.ext3_sb.s_resuid = resuid;
 	sb->u.ext3_sb.s_resgid = resgid;
 	es = sb->u.ext3_sb.s_es;
-	if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY))
+	if ((*flags & MS_RDONLY) == (sb->s_flags & MS_RDONLY) &&
+	    (n_blocks_count == 0 ||
+	     n_blocks_count == le32_to_cpu(es->s_blocks_count)))
 		return 0;
 	if (*flags & MS_RDONLY) {
 		/* 
@@ -1014,6 +1372,8 @@
 		 */
 		sb->u.ext3_sb.s_mount_state = le16_to_cpu(es->s_state);
 		sb->s_flags &= ~MS_RDONLY;
+		if ((tmp = ext3_resize_fs (sb, es, n_blocks_count, resgdt)))
+			return tmp;
 		ext3_setup_super (sb, es);
 		sb->s_dirt = 1;
 	}
diff -rub -x .[a-z]* linux-2.2.14-ext3e/include/linux/ext3_fs.h linux-2.2.14-lvm/include/linux/ext3_fs.h
--- linux-2.2.14-ext3e/include/linux/ext3_fs.h	Wed May 24 17:33:00 2000
+++ linux-2.2.14-lvm/include/linux/ext3_fs.h	Thu May 25 16:32:51 2000
@@ -61,6 +61,7 @@
 #define EXT3_ACL_DATA_INO	 4	/* ACL inode */
 #define EXT3_BOOT_LOADER_INO	 5	/* Boot loader inode */
 #define EXT3_UNDEL_DIR_INO	 6	/* Undelete directory inode */
+#define EXT3_RESIZE_INO		 7	/* Reserved group descriptors inode */
 
 /* First non-reserved inode for old ext3 filesystems */
 #define EXT3_GOOD_OLD_FIRST_INO	11
@@ -207,6 +208,22 @@
 #define EXT3_FL_USER_VISIBLE		0x00001FFF /* User visible flags */
 #define EXT3_FL_USER_MODIFIABLE		0x000000FF /* User modifiable flags */
 
+/* used to add group descriptor data when online resize is done */
+struct ext3_new_group_data {
+	unsigned long block_bitmap;
+	unsigned long inode_bitmap;
+	unsigned long inode_table;
+	unsigned int free_blocks_count;
+	unsigned int free_inodes_count;
+	unsigned int group_num;
+	unsigned int inode_block_num;
+};
+
+struct ext3_rsv_data {
+	unsigned long disk_block_num;
+	unsigned int inode_block_num;
+};
+
 /*
  * ioctl commands
  */
@@ -210,6 +228,10 @@
 #define	EXT3_IOC_SETFLAGS		_IOW('f', 2, long)
 #define	EXT3_IOC_GETVERSION		_IOR('v', 1, long)
 #define	EXT3_IOC_SETVERSION		_IOW('v', 2, long)
+#define	EXT3_IOC_RESIZE_NEW_GROUP	_IOW('v', 3, struct ext3_new_group_data)
+#define	EXT3_IOC_RESIZE_RSV_DEL		_IOW('v', 4, struct ext3_rsv_data)
+#define	EXT3_IOC_RESIZE_RSV_ADD		_IOW('v', 5, struct ext3_rsv_data)
+
 
 /*
  * Structure of an inode on the disk
@@ -633,6 +655,10 @@
 extern int  ext3_notify_change(struct dentry *, struct iattr *);
 extern void ext3_discard_prealloc (struct inode *);
 extern int ext3_change_inode_journal_flag(struct inode *, int);
+extern int ext3_ioc_resize_rsv_del(struct inode *resize,
+				   struct ext3_rsv_data *input);
+extern int ext3_ioc_resize_rsv_add(struct inode *resize,
+				   struct ext3_rsv_data *input);
 
 /* ioctl.c */
 extern int ext3_ioctl (struct inode *, struct file *, unsigned int,
