Using an MMC or SD card to Expand The Size of Your iPAQ's Familiar Linux Filesystem

by Toby Gray (toby@yargybot.com)
 

Step one (backup) - Step two (partitions) - Step three (filesystems) - Step four (install find) - Step five (copy) - Step six (test) - Step seven (delete)
 

2003-10-26

This is a re-write of the 2003-7-4 version of this document, which contained some inaccuracies. I have also now discovered a better way to do this (I am also now using a 256MB SD card rather than the 128MB MMC I had then).

I am writing this after having successfully managed to move the /usr and /home directories on my iPAQ 3870 to a 256MB Secure Digital (SD) card. It was an interesting and sometimes painful journey, so I thought I'd write down how I did it in the hope that I can save others at least some of the pain. Note that to the best of my knowledge and as of this writing, only the MMC/SD slot on the 3800-series iPAQ's is supported by the Familiar kernel.

If you're interested in why I did this, how I decided to do it, and/or what I was trying to acheive, there's some background here.

My "initial" hardware and software setup was an iPAQ 3870 flashed with bootgpe2-v0.7.1-h3600.jffs2, upgraded to kernel 2.4.19-rmk6-pxa1-hh26 from http://www.handhelds.org/feeds/2.4.19 and to Familiar unstable from http://familiar.handhelds.org/familiar/feeds/unstable/packages/armv4l. These upgrades were done in October 2003. The kernel upgrade is necessary to get the latest MMC/SD card support, but you probably don't need to upgrade to Familiar unstable (and it can be a little tricky to do so).

Before I get into any of the "what I did" part, I should mention that it's possible to boot my iPAQ to a state where the serial console is up and running (so I can log in and do/fix stuff) without my SD card inserted. Hopefully this means that if you get into a mess setting this up, you should be able to pull out your card and reboot successfully, provided that you only move the same bits of your filesystem to your MMC/SD card as I did. I can also put up the USB network interface (or Bluetooth, although I haven't actually tried that yet) and do an NFS mount (with -o nolock). The X Window system (and consequently GPE) will NOT run on my iPAQ without the SD card, however (although I did manage to keep the GPE boot-up splash screen, as you'll see below).

I did everything as root. I don't do this on my desktop machine, but on a device as personal as my PDA, I don't really bother much about distinguishing my normal user from the root user, especially for this sort of thing. Of course, you do actually NEED to be root to do a lot of the things involved in setting this up.

What follows on this page is a fairly complete discussion of the steps involved in setting up your "MMC/SD-enhanced filesystem". If you just want some "1. do this, 2.do that" instructions, go here.

My objective here was twofold: free up some space in the on-board flash by moving parts of the filesystem to the SD card, and set things up so that a lot of the newly installed files will go to the SD card too. Keeping this in mind, I had a look at the existing filesystem's directory structure. To make this easier, and as a backup, I copied the root filesystem to a directory on my desktop machine. There are several ways to do this, but some work better than others.

A simple
# cp -a /. /some/mount/point
won't work because you'd get into a loop copying /some/mount/point/. to /some/mount/point/some/mount/point/. and so on. The way I copy root filesystems on desktop machines is
# cd /
# find . -xdev | cpio -pmv /some/mount/point

The -xdev tells find not to descend into "other filesystems" mounted below the base directory (the root directory, in this instance). The find command produces a list of filenames that is passed to the cpio command, which runs verbosely (listing filenames as it goes - I use this option so I can see where it's up to; -v) in "copy-pass mode" (-p), preserving file modification times (-m). At the time of writing, cpio is not available as a "standard" ipkg, but the Debian ARM version can be installed on an iPAQ. Also, the Busybox version of find doesn't support the -xdev option, so you need to install the full version, which does come in an ipkg.

Since installing cpio and the non-Busybox version of find on the iPAQ was a bit of a fiddle (although I did succeed and I used find later on), and because having a backup in JFFS2 form can be useful in the event of a reflash, I think that the best thing to do is copy your root filesystem to your desktop machine as a jffs2 file. The easiest (but not the best -- see below) way to do this is to use bootldr's "save root" command to upload the entire flash partition over the serial link. You can achieve the same end from the Linux (as opposed to bootloader) command line. See this page for more about that. As the root partition fills almost all of the 32MB (on my 3870) of flash memory, this upload takes some time. You get the entire partition, including any garbage and any unused blocks. This leads to a file that is too big for the current version of bootldr (I'm using 2.21.11) to re-flash back into the flash memory.

Thankfully, there is a way to create a new jffs2 image containing no unnecessary garbage or unused blocks (a few unused blocks are needed for JFFS2 to actually work properly). So, step one is to backup your existing root filesystem as a jffs2 image and copy the jffs2 file to your desktop computer.
# mkfs.jffs2 --root=/ --eraseblock=262144 --pad --output=/where/to/save/RootBackup.jffs2
This file will be quite large (well over ten megabytes unless you don't even have a GUI installed on your iPAQ), so it may not fit in your iPAQ's on-board flash. You want to copy the file to your desktop machine anyway, so if you can create it there directly (through an NFS mount or whatever), you avoid having to transfer the file separately. Other options are to create the file on your MMC/SD card or a CF card and use that to transfer it to your desktop machine (you'll obviously need a suitable card reader for the desktop machine), or to pipe the output of mkfs.jffs2 over a serial link using zmodem or some such (see
this page for some instructions on zmodem piping). Phew! You now have a backup of your system as it stands. If this file is not too big, you'll be able to re-flash it to your iPAQ if anything goes wrong. How big is too big? I don't know, but I have successfully re-flashed a 20MB file, and I think the smallest one I've tried that failed was 24MB. Apparently you can use "load root <addr>" to re-flash the partition in stages after splitting your jffs2 image into two (or more) files, and the bootloader has a "copy" command that can (apparently) be used to copy a file from a CF card to a flash partition.

Remember that apart from backing up the iPAQ's root filesystem, I also wanted to be able to browse through it using the tools available on my desktop computer. To do that, I need my desktop computer to be able to read the JFFS2 filesystem. This is not as simple as it may seem, because JFFS2 only works with Memory Technology Devices (MTD). To access a simple file (your jffs2 image file) as an MTD, you need to set up a loopback device and then "pretend" that this loopback device is an MTD. Support for all this must be in your kernel (and modules). If you don't think you're up to kernel building or just don't want to bother, you can skip this part as it's just convenient, not required.

Depending on your distibution, you may already have the necessary kernel support (although it's unlikely because MTDs are rarely used with desktop computers). The files you need for MTD are mtdcore.o, mtdblock.o and blkmtd.o. Probably the easiest way to see if you have these is
# find /lib/modules -iname '*mtd*'
If none of the above-mentioned files is listed by this find command, you will need to do some kernel configuration and module- or even kernel-building. If blkmtd.o is listed but mtdcore.o is not, then mtdcore is almost certainly compiled into your kernel, and you're OK on the MTD front. JFFS2 support is much simpler. The only module you need is jffs2.o, and it might be compiled into your existing kernel if you have MTD support (remember JFFS2 only works with MTD, so you're not likely to find a kernel that has JFFS2 but not MTD support). For the loopback device, you just need loop.o, which may very well be compiled into your kernel, as it's a handy little thingummy.

I'm not going to go into a complete tutorial on configuring and compiling a custom kernel, as this has been done and much better than I could do it (e.g. http://www.ibiblio.org/mdw/HOWTO/Kernel-HOWTO and http://www.linuxchix.org/content/courses/kernel_hacking). If you're using menuconfig (xconfig is presumably similar), the options you need are (I recommend compiling them all as modules - blkmtd MUST be a module):

  • Memory Technology Devices (MTD) ->
    • Memory Technology Device (MTD) support this is mtdcore]
    • Caching block device access to MTD devices [mtdblock]
    • Self-contained MTD device drivers ->
      • MTD emulation using block device [blkmtd]
  • Block devices ->
    • Loopback device support
  • File systems ->
    • Journalling Flash File System v2 (JFFS2) support
I also set "JFFS2 debugging verbosity (0 = quiet, 2 = noisy)" to 1, but I don't think I've ever referred to the output produced by it.

If you don't use devfs, you will need to create nodes for your loopback device and MTD block device (unless you already have them).
# mknod /dev/loop0 b 7 0
# mknod /dev/mtdblock0 b 31 0

Once you've got your kernel and device nodes straightened out, and assuming the kernel bits are all modules, do this
# modprobe loop
# modprobe mtdblock

(the above will also load mtdcore, because mtdblock requires it)
# losetup /dev/loop0 /path/to/RootBackup.jffs2
(use '/dev/loop/0' if you have devfs)
# insmod blkmtd erasesz=256 device=/dev/loop0
# mount -r -t jffs2 /dev/mtdblock0 /some/mount/point

(this should load the jffs2 module for you)
At this point, you should be able to access the files contained in your jffs2 image. Note that it's a really bad idea to write to the jffs2 filesystem because there's not really any space left in it and it would corrupt your backup. That's why I specified the read-only option (-r) in the mount command. I actually copied the contents of the JFFS2 filesystem into a directory in my desktop machine's root filesystem so that I could leave the image file alone and avoid ever needing to re-do all the hoopla of setting up to re-mount the JFFS2 filesystem. By the way, to unmount and cleanup, do this
# umount /some/mount/point
# rmmod blkmtd
# losetup -d /dev/loop0
# rmmod mtdblock mtdcore loop

Once I had desktop access to a duplicate of my iPAQ's filesystem, I used my file manager to look at the amount of space used by the files in each directory. The whole shebang is 25.8MB. Of this, /usr is 18.7MB. Since this is also where many of the files installed by packages go, I started out here. The first thing I tried (when I was still using an MMC) was copying the entire contents of /usr to a card partition, then mounting the card parition over the top of the existing /usr directory. This didn't work. My iPAQ locked up. After resetting, I wondered if it was some problem with doing the mount on a "running" system, so I tried setting up the mount in /etc/fstab so it would happen at boot time. This didn't work either. I still am not sure why. Anyway, there are some advantages to only moving sub-directories of /usr, so I did that on the MMC. Thanks to a message posted to the Familiar mailing list by Tony Godshall, I have since discovered that making /usr a symlink the SD card partition's mount point seems to work well. I also decided to put /home on the SD card to give me plenty of room for my personal data etc.

Once I knew what I was going to move to the SD card, I was ready to partition it. Since I have plenty of room (for my purposes) on my SD card, I just split it half (one half for /usr, the other for /home). For step two you'll need fdisk (or some other partitioning software)
# ipkg install fdisk
According to IpkgFind (as at 2003-10-26), the fdisk package is available from a variety of feeds: stable, unstable, 0.5.2, 0.6 base, 0.6.1 base, and 0.7 base. Partitioning the MMC/SD card is pretty straightforward. First enter the command
# fdisk /dev/mmc/disc
You should see a prompt from fdisk, probably preceeded by a warning about the number of cylinders on the "disc". Press 'P', then 'Enter' to see the current partition table, as well as some information about the layout of the disc. You may want to take note of the size of a cylinder, as this is the basic unit of size for a partition. A new MMC/SD card usually has a single Windoze partition filling all of the available space. You will need to delete this partition before you can create your own partitions. Press 'D', 'Enter', '1', 'Enter' to delete the first partition. If you have more than one partition on your card, you probably want to delete the others too ('D', 'Enter', '2', 'Enter', etc).

To start creating your first new partition, press 'N' then 'Enter'. Your first partition will be a primary partition, so press 'P', 'Enter'. The first partition is number 1 (surprise, surprise), so press '1', 'Enter'. This partition will start at the beginning of the card, cylinder 1. Fdisk should offer cylinder 1 by default, so you can just press 'Enter'. Next, fdisk needs to know how big to make the partition. There are a number of ways you can specify this. The most general-purpose approach is to decide what size you want the partition to be (in megabytes for this example), type '+', then the size you want, then 'M' (for megabytes), then press 'Enter'. I actually tend to specify my partition sizes in cylinders, but I'm not going to go into that here.

If you are going to use all the available space on your SD card, and you want no more than four partitions, I'd advise making them all "primary" partitions. If you want more than four partitions or you want to leave some space available on your SD card, make up to three primary partitions then set up the remainder of your space as an extended partition (which in turn contains all the rest of your "real" partitions). See the fdisk man page on your desktop machine if you want more information about fdisk. Once you've set up all the partitions you need, press 'W', 'Enter' to write the new partition table to the card. This will also exit from fdisk, loading the new partition table into the kernel in the process. If you enter
# ls /dev/mmc
you should see "disc part1 part2... partn" with a "partx" for each partition you created with fdisk (except the extended parition).

Before you can start copying files to these new partitions, you need to do step three: create filesystems on your partitions. What sort of filesystem you use is up to you. I chose to use ext2 because it supports long filenames and a full set of permissions, but the modules and utilities required to support it don't take up much room. You can install ext2 filesystem support like this
# ipkg install ext2-modules
# ipkg install e2fsprogs

To create an ext2 filesystem on the first partition, use this command
# mkfs.ext2 /dev/mmc/part1
then simply repeat for your other partition(s) (changing part1 to part2 then part3 if you have one, and so on).

Later on, we are going to need the complete (as opposed to busybox) version of the find command. Since this is installed in /usr/bin (which obviously is under /usr), we want to install it before copying the contents of /usr to an MMC/SD card partition. Step four: install find. This command SHOULD work.
# ipkg -force-overwrite install find
The -force-overwrite option is required because we need to overwrite (replace) the busybox find. If you get the message "Cannot find package find" from the above command, then you'll probably have to do something like this
# ipkg -force-overwrite install http://familiar.handhelds.org/familiar/releases/v0.7/base/armv4l/find_4.1-2_arm.ipk
This example downloads find's ipkg file from the Familiar 0.7 base feed. This seems to be necessary because ipkg gets "confused" either by the fact that find is already being provided by busybox, or by the fact that the same version of the find ipkg file is available from multiple feeds.

At this point I was ready to mount the SD card partitions and start step five: copying files. I used the cp command for copying. I've noticed in the past that using the asterisk wildcard with cp can lead to it skipping "hidden" files (whose names begin with a full-stop), but I think that using a full-stop to specify that an entire directory is to be copied avoids this problem. I only mounted one partition at a time, partly so that I could lazily use almost identical commands for each copying task and partly to avoid any chance of getting confused about what was mounted where, etc.
# mount -t ext2 /dev/mmc/part1 /mnt/card
# cp -a /usr/. /mnt/card
# umount /mnt/card
# mount -t ext2 /dev/mmc/part2 /mnt/card
# cp -a /home/. /mnt/card
# umount /mnt/card

Unfortunately, this does not result in a "perfect" copy because there may be some symbolic links that use relative paths. Some of these will no longer work because from /usr the relative path ".." is equivalent to the absolute path "/" (the root directory), but from /mnt/card the relative path ".." is equivalent to the absolute path "/mnt". This is a tricky problem to solve because you can't just blindly add an extra ../ to every symlink. A symlink like ../dest in a directory like /usr/x/y will still be OK when copied (as ../dest) to /mnt/card/x/y, so you don't want to change it into ../../dest. The only way I know of to be absolutely certain that these are all fixed properly is to do it by hand (or change the mount point to a top-level directory). You can use your newly-installed find command to get a list of all of the relative symlinks (except ones that do not start with "..", which will only refer to files in the same directory, or in a sub-directory under the link's directory).
# mount -t ext2 /dev/mmc/part1 /mnt/card
# find /mnt/card -lname '..*'

On my system, the vast majority of these are in the bin sub-directory. You can use the following command to look at the destinations of these links.
# ls -l `find /mnt/card -lname '..*'`
On my iPAQ, this shows that they are all links back to the /bin directory, and all but two of them (bin/passwd and bin/vlock) are links to /bin/busybox. I first turned them all into absolute symlinks to /bin/busybox, then fixed the two links to /bin/tinylogin by hand.
# for FNAME in `find /mnt/card -lname '*busybox'`; do ln -sf /bin/busybox $FNAME; done
# ln -sf /bin/tinylogin /mnt/card/bin/passwd
# ln -sf /bin/tinylogin /mnt/card/bin/vlock

Luckily, I had no relative symlinks under /home. You can use the same procedure as above to check this on your system. Just replace part1 with part2 in the mount command, and change all the instances of /mnt/card to /mnt/card2, or unmount /dev/mmc/part1 first so you can mount part2 at /mnt/card (you could mount part2 "over the top" of part1, but this would be asking for trouble).

The card-based filesystems are now ready for step six, testing. I edited the file /etc/sysconfig/hotplug (if you don't have this file, edit /etc/hotplug/mmc.agent instead) and changed this line
MMC_MOUNT_OPTIONS="-t auto -o sync"
to this
MMC_MOUNT_OPTIONS="-t ext2 -o sync,noatime"
Specifying the filesystem type (ext2) instead of auto just prevents the operating system from trying other filesystem types (and failing). I have added the noatime option to reduce the number of writes to the card's flash memory at the expense of not having a valid "access time" field in my directory entries.

It is also a good idea to run fsck on your partitions before you mount them. To do this, you need to edit /etc/hotplug/mmc.agent, adding these two lines
    umount $device
    /sbin/fsck.ext2 -p $device

after this existing line
    echo flash on $device > /dev/console
The umount command just ensures that the system doesn't think that the partition is still mounted (as sometimes happens when the card is ejected). Now remove and re-insert the card to ensure that your partitions are correctly auto-mounted by hotplug. You may need to wait a while for the fsck(s) to complete, especially if you have a high capacity card. Check the output of
# cat /etc/mtab
You should have a line like this
/dev/mmc/part1 /mnt/card ext2 rw,sync,noatime 0 0
for each partition. Only /dev/mmc/part1 has no digit after /mnt/card. The other partitions should have matching "part" and "card" digits.

The fsck command above can cause problems during boot-up because the rest of the boot-up process does not wait for the fsck to complete. What I have at the moment (but I'm not sure it's the best solution) is this extra line near the end of /linuxrc
 
/usr/bin/MMCWait.sh
 
Immediately before the last two existing lines
 
echo "Executing /sbin/init..."
exec /sbin/init $*

 
This runs a script to wait for the MMC/SD card to be fsck'd and mounted before transferring control to init.

The /usr/bin/MMCWait.sh script follows and is available here for your convenience ;)
Remember to put this in the root filesystem's /mnt/card so it will be there before the SD card is mounted. It's a good idea to put it on the card as well in case of odd boot-up timing and for the resume script below...
 
#! /bin/sh
echo "Waiting for card fsck and mount"
card_mounted=none
while test "$card_mounted" = "none" ; do
    if grep 'part1' /etc/mtab ; then
        card_mounted=1
    else
        if ps | grep 'fsck.ext2' - > /dev/null ; then
            sleep 2
        else
            sleep 2
            if ps | grep 'fsck.ext2' - > /dev/null ; then
                sleep 2
            else
                card_mounted=error
            fi
        fi
    fi
done
while test "$card_mounted" = "1" ; do
    if grep 'part2' /etc/mtab ; then
        card_mounted=2
    else
        if ps | grep 'fsck.ext2' - > /dev/null ; then
            sleep 2
        else
            sleep 2
            if ps | grep 'fsck.ext2' - > /dev/null ; then
                sleep 2
            else
                card_mounted=error
            fi
        fi
    fi
done
if test "$card_mounted" = "error" ; then
    echo "Fewer than two MMC/SD card partitions mounted."
fi
 

There is also a related issue when you resume from a suspend. Sometimes the resume scripts need stuff from /usr/bin, so it's a good idea to use this MMCWait script at resume time too. I have a file called R20mmc in /etc/resume-scripts. It looks like this (and is available here)
 
#! /bin/sh
sleep 3
C=0
while test $C -lt 5 ; do
    if ls /dev/mmc/part* > /dev/null; then
        echo MMC/SD card detected > /dev/console
        /usr/bin/MMCWait.sh > /dev/console
        rm /usr/bin/crap*
        C=0
        while ! mv /usr/bin/[ /usr/bin/crap$C ; do
            C=$(($C + 1))
        done
        C=5
        ln -s /bin/busybox /usr/bin/[
        exit 1
    else
        sleep 2
    fi
    C=$(($C + 1))
done
echo MMC/SD card not detected > /dev/console
 

I should probably explain what all this is about, so here goes. The first line (after #! /bin/sh) is just a sleep to give the kernel a chance to sort itself out. I don't think it actually needs to be as long as 3 seconds, I've just been too lazy to try other values.
Next I set up to make up to five attempts at detecting the MMC or SD card. Again, this is probably overkill.
 
    if ls /dev/mmc/part* > /dev/null; then
This line just checks for the existence of any MMC/SD card partitions
 
    echo MMC/SD card detected > /dev/console
report that we found something
 
    /usr/bin/MMCWait.sh > /dev/console
wait for the partitions to be checked and mounted (the check will usually just come straight back "clean").
 
    rm /usr/bin/crap*
Now this is where it gets interesting ;)
I have a recurring problem where the symlink /usr/bin/[ repeatedly gets screwed up somehow. The code below renames /usr/bin/[ to /usr/bin/crapx where x is a number starting from 0 and ascending until these renamed files eventually get deleted by the above command. The trouble is that when this weirdness happens the "crap" file can't be deleted until the next reboot -- it just returns an "Input/output error".
 
    C=0
    while ! mv /usr/bin/[ /usr/bin/crap$C ; do
        C=$(($C + 1))
    done

this loop just keeps upping C until it finds a filename that is not in use.
 
    C=5
This would force the main loop to exit (because we've found the MMC/SD card, so we're done) by setting the loop variable to the loop's limit. In actual fact, I quit from the script soon anyway.
 
    ln -s /bin/busybox /usr/bin/[
This re-creates the /usr/bin/[ link that got renamed above. At the moment I don't know what causes this weird error with it and this is the only fix for it I've come up with so far.
 
    exit 1
All done. Go bye-byes.
 
    else
        sleep 2

This is what to do if the MMC/SD card is not found: wait two seconds and try again. That means that if there really is no MMC/SD card then this script will wait around for ten seconds (five loops of two seconds each).
 
    fi
    C=$(($C + 1))

increment the loop variable.

I also have a simple /etc/suspend-scripts/S60mmc suspend script that tries to unmount the MMC/SD card partitions, but I don't think this helps much
 
#! /bin/sh
 
umount /home/shared
for PART in /mnt/card* ; do
    umount $PART
done

 
The umount /home/shared is for my NFS mount point because (a) it needs to be unmounted anyway because the network will go down on suspend, and (b) because it is mounted under /home it would block the SD card partition mounted at /home from unmounting.

The boot up process needs some things from /usr/bin before the MMC/SD card gets mounted, so these need to be made available somehow. As it turns out, they are all symlinks to /bin/busybox (at least they are with a "standard" Familiar installation -- if you've installed the "full" version of any of the app's that busybox contains then you might have some extra work to do here). There are at least two ways to handle this situation. You can move the symlinks to /bin, which should mostly be OK because /bin is usually in PATH. However, if a script (or anything else, for that matter) specifies an absolute path to the symlink (ie "/usr/bin/someprogram" instead of just "someprgram"), then the symlink still needs to be in /usr/bin.

Before the MMC/SD card gets mounted, /mnt/card is normally just an empty directory. Since /usr will soon be a symlink to /mnt/card, you can just put copies of the busybox symlinks in a new directory, /mnt/card/bin. As symlinks are so small, I just copied all the links in /usr/bin that point to /bin/busybox rather than fiddle about working out which ones are actually required.

To make the root filesystem's version of /mnt/card accessible (instead of the contents of the MMC/SD card's partition), you can either "remount" the root filesystem at an additional mount point, or you can unmount the card partition. The quickest way to unmount it is just to eject the card. Alternatively, you can just use an umount command.
# umount /mnt/card
As we discovered above, some of these links are relative rather than absolute, so a simple copy won't give us what we want. I kind of "re-created" all the links as absolute links like this
# mkdir /mnt/card/bin
# for FNAME in `find /usr/bin -lname '*busybox'`; do FNAME=`basename $FNAME`; ln -s /bin/busybox /mnt/card/bin/$FNAME; done

Now you can remount your MMC/SD card partition (or just re-insert the card).
# mount -t ext2 -o sync,noatime /dev/mmc/part1 /mnt/card

For safety's sake, I kept the original /usr intact, but under a different name, until I was sure the new system was working properly. I used the name usr.hide.
# mv /usr /usr.hide
I then created a new /usr symlink to the mount point for my SD card partition.
# ln -s /mnt/card /usr
And the same thing for /home
# mv /home /home.hide
# ln -s /mnt/card2 /home

The "acid test" is to see if you can now reboot your iPAQ properly.
# shutdown -r now
If something horrendous happens and you can't boot up the iPAQ, just remove the MMC or SD card and reset the iPAQ. Any attempts to mount the MMC/SD card partitions will then fail, leaving you with your original root filesystem intact (except with /usr and /home "hidden). Once the iPAQ boots up successfully, you can "unhide" /usr and/or /home then reinsert your card (if necessary) and fix your problem(s).

If you're running GPE, then the following message (or something similar) probably appears in your boot-up log, and your GPE splash screen is gone.
/etc/rcS.d/S00bootsplash: 27: /usr/bin/gpe-bootsplash: No such file or directory
This is not so easily fixed because the gpe-bootsplash program depends on several libraries, amounting to about 630 kilobytes. Fortunately, you can do without the splash screen, or you can revert to an older method of displaying it. If you really want to keep your splash screen and load it the "new" way, here's what you need to do. First mount /dev/root (the root filesystem device) at a "secondary" mount point so you have access to the root filesystem's version of the /mnt/card directory. I use /mnt/hda in this example because I wasn't using a CF card at the time.
# mount /dev/root /mnt/hda
Then copy gpe-bootsplash to the root filesystem's /mnt/card/bin.
# cp /usr/bin/gpe-bootsplash /mnt/hda/mnt/card/bin
The gpe-bootsplash program also needs some libraries from /usr/lib, so you'll need a lib sub-directory under the root filesystem's /mnt/card.
# mkdir /mnt/hda/mnt/card/lib
Here are the commands for copying the libraries required by the version of gpe-bootsplash on my system.
# cp /usr/lib/libgdk_pixbuf-2.0.so.0 /mnt/hda/mnt/card/lib
# cp /usr/lib/libgobject-2.0.so.0 /mnt/hda/mnt/card/lib
# cp /usr/lib/libgmodule-2.0.so.0 /mnt/hda/mnt/card/lib
# cp /usr/lib/libglib-2.0.so.0 /mnt/hda/mnt/card/lib

You will also need to create a directory for the image file and copy it over.
# mkdir /mnt/hda/mnt/card/share
# mkdir /mnt/hda/mnt/card/share/gpe
# cp /usr/share/gpe/splash.png /mnt/hda/mnt/card/share/gpe

And finally, unmount the secondary "copy" of the root filesystem.
# umount /mnt/hda

The old-style gpe-bootsplash.sh script is much simpler to set up. Create a file /etc/init.d/gpe-bootsplash.sh that looks like this
 
#!/bin/sh
 
case $1 in
  'start')
    DIR=/usr/share/gpe/bootsplash
    FN=`cat /proc/hal/model`.gz
    gunzip -c < $DIR/$FN > /dev/fb0
    ;;
  'stop')
    ;;
  *)
esac

 
Since you will obviously know what model iPAQ this is going to run on, you could get rid of the DIR and FN stuff and just put "gunzip -c < /usr/share/gpe/bootsplash/3800.gz > /dev/fb0", for example.
Of course, you will need the 3800.gz (or whatever) file, too. This has to go in a sub-directory under the root filesystem's /mnt/card.
# mount /dev/root /mnt/hda
# cd /mnt/hda/mnt/card
# mkdir share
# mkdir share/gpe
# mkdir share/gpe/bootsplash
# cp /path/to/3800.gz share/gpe/bootsplash
# cd /
# umount /mnt/hda

The "/path/to/3800.gz" above needs to be replaced with the real path to your gzipped image file. I only have a copy of the 3800.gz file, which you can download here. If you display this on some other models it will be upside down and it is a slightly older image than the current version of GPE uses (it says "GPE 2" instead of "GPE 2.1". You could put this file in a different directory (perhaps one that is not under /usr or /home) and change the gpe-bootsplash.sh script to match, but I prefered to keep it in its original location (well, kind of anyway).
The only thing left to do is fix the symlink /etc/rcS.d/S00bootsplash
# ln -sf /etc/init.d/gpe-bootsplash.sh /etc/rcS.d/S00bootsplash
and make sure that your script is executable. # chmod +x /etc/init.d/gpe-bootsplash.sh

Now that these bits of my filesystem were working from the SD card, I could go ahead with step seven, and delete them from the on-board flash's filesystem.
# rm -r /usr.hide
# rm -r /home.hide

And that's it!