Resizing / on ext3 remotely

This is a feature request we had at work, and ignoring the RedHat consultant’s “If all you have is a hammer…” mentality (he suggested Anaconda again, no matter how unsuitable it is for updating 4,000 remote servers), it’s not actually that difficult of a task. The prime difficult here, of course, is that you cannot shrink a live filesystem, and good luck unmounting / while the system is running. Additionally, it seems like nobody else on the internet has had to do it (or documented the process and changes needed), other than one guy whose documentation is useless (he put up a frakking binary of resize2fs, took it down because it was “a binary without source” [which the GPL abhors], and said he “didn’t have the sources anymore”. You’ll see further down exactly how large the necessary change to publicly available sources is [one line, and not a complicated one]).

In case you’ve never look at it before, the initramfs (and initrd before it, though RHEL still calls the initramfs an initrd, despite the fact that you cannot stuff an ext2 filesystem in there anymore to my knowledge) is a gzipped cpio archive, which makes mucking with it dead simple. Just make yourself a directory somewhere (I used ~/initrd), and issue:

gzip -d --suffix=".img" | cpio -id < /path/to/initrd

You'll find a directory structure resembling:

drwxr-xr-x 2 root root 4096 May  8 13:03 bin
drwxr-xr-x 2 root root 4096 Mar  6 08:54 dev
drwxr-xr-x 4 root root 4096 Mar  6 08:54 etc
-rwxr-xr-x 1 root root 2007 May 11 04:58 init
drwxr-xr-x 2 root root 4096 May  8 09:52 lib
lrwxrwxrwx 1 root root    3 May  8 12:09 lib64 -> lib
drwxr-xr-x 2 root root 4096 Mar  6 08:54 loopfs
drwxr-xr-x 2 root root 4096 Mar  6 08:54 proc
lrwxrwxrwx 1 root root    3 Mar  6 08:54 sbin -> bin
drwxr-xr-x 2 root root 4096 Mar  6 08:54 sys
drwxr-xr-x 2 root root 4096 Mar  6 08:54 sysroot

The bulk of the work is done in ./init, which calls out to nash (an ridiculously limited shell with almost zero constructs for doing useful stuff other than booting). It mounts /proc and /sys, makes device nodes for the consoles, null, zero, and shared memory, loads drivers (LVM/SCSI), then finds the root filesystem, mounts it, and pivots the root to continue normal bootup. The only real caveat here is that every binary in there is statically linked -- and that's not necessarily a bad thing, since portability is a lot easier.

To resize, we'll need resize2fs built statically, and e2fsck wouldn't be a bad idea either. There are make targets for both of them in e2fsprogs (either an SRPM or just sources, your pick). All the routines in e2fsprogs check to see whether or not the filesystem is mounted before they do anything by checking /etc/mtab or /proc/mounts (and nash's mount doesn't put an entry in /proc/mounts for reasons that I haven't dug into). That file does not exist during the init process, and while e2fsck will spit out a warning and continue to run, resize2fs isn't so lucky. From resize/main.c:

/*
* Figure out whether or not the device is mounted, and if it is
* where it is mounted.
*/
len=80;
while (1) {
  mtpt = malloc(len);
  if (!mtpt)
     return ENOMEM;
  mtpt[len-1] = 0;
  retval = ext2fs_check_mount_point(device_name, &mount_flags,
                                                  mtpt, len);
  if (retval) {
    com_err("ext2fs_check_mount_point", retval,
                _("while determining whether %s is mounted."),
                 device_name);
                exit(1);
  }
  if (!(mount_flags & EXT2_MF_MOUNTED) || (mtpt[len-1] == 0))
     break;
  free(mtpt);
  len = 2 * len;
}

That comes from lib/ext2fs/ismounted.c, which is 50% comments from Theodore T'so bitching about the HURD, and I don't feel like editing it (or reposting it verbatim, since the sources are easy enough to get). The gist is that it's checking for a non-zero return value on retval and bailing if it find one (which it will). The decision of modifying ismounted.c or just making a quick hack to resize/main.c is up to you (I opted for the quick hack option, which could be commenting out the if statement, deleting the block, or just setting retval to zero, which involves the least typing).

As an aside, I know braces aren't necessary in lots of languages if there's only one statement in the block (C being one of them), but it makes it seriously ugly without them. Let's hope he never has to add another statement and forgets to add braces.

With that modified (I opted not to modify e2fsck, since it runs anyway), we have handy-dandy make targets for the utilities we need.

make -C resize resize2fs.static && make -C e2fsck e2fsck.static

It should be obvious, but, ah... Don't do a `make install` on this. resize2fs is dangerous now.

Move the new binaries (located in $src/{resize,e2fsck}/*.static) to $initrd/bin, and `$EDITOR $initrd/init`.

At least on CentOS/RHEL, we're running an LVM root. If you're not, the correct place may be slightly different, but not too much. Find `lvm vgchange -ay...`, and tack the following lines after it:

echo Creating root device node...
#RHEL/CentOS ignore the 'defaults' parameter, but other systems may not
mkrootdev -t ext3 -o defaults,ro /dev/your/device/here
echo Resizing filesystem...
e2fsck -fy /dev/your/device/here
/bin/resize2fs -p /dev/your/device/here $newsize
#If you're running lvm, the initrd will contain a static lvm binary, too
#but not of the aliases to `lvmresize` and such, which is basically `lvm $argv[1] $@`
#Caveat: lvm will not resize volumes with snapshots yet, but if you don't have any
#you can resize now if you like.  You don't have to, since lvm will shrink online volumes
#once you're into the actual system
lvm lvresize -L /dev/your/device/here

I would probably recommend putting this into a different initrd than the one you usually use, setting it as the default in grub, and having a one-off script change grub back and delete itself (rc.local would be a good place for this, since knocking itself out of there would be trivial).

find . -print | cpio -o -c | gzip -c9 >/boot/$newinitrd

The new initrd will be ~1.5MB larger than the old one (less if you stripped the binaries), but that's a trivial size different compared to a rescue image or (gods forbid) Anaconda simply to resize. Remember: TMTOWTDI, and what OS vendors/consultants recommend is not always ideal for your situation (it was not for ours).

1 Comment

  • By shaborb, January 21, 2010 @ 2:38 am

    Thanks, this was very helpful.

Other Links to this Post

RSS feed for comments on this post. TrackBack URI

Leave a comment

You must be logged in to post a comment.