Moving the swapfile in Mac OS X 10.4 (Tiger)

Copyright © 2004-2006 Dave Bayer. Subject to the terms and conditions of the MIT License.

This page is www.math.columbia.edu/~ bayer/OSX/swapfile/


Note for Mac OS X 10.3 (Panther)

The solution given on this page also works for OS X 10.3 (Panther). An earlier version works only for Panther.

OS X 10.3 (Panther) solution: This page, or Moving the swapfile in Mac OS X 10.3 (Panther)

OS X 10.4 (Tiger) solution: This page.

In Panther, one can simply wait at the appropriate point in /etc/rc for the needed volume to mount. In Tiger, often no amount of waiting suffices; one needs to explicitly mount the needed volume. Finding the needed volume is tricky, because the only way to refer to a volume this early in the boot process is by its BSD name of the form /dev/disk1s10, but BSD names are not consistent across boots.


Download

swapfile.dmg

(Version of February 13, 2006)


Annotated source code

Here is annotated source code for the Perl script swapfile.pl.

To execute this script, please download the disk image swapfile.dmg. Browsers are unreliable at downloading bare text files, and will change file permissions, while the disk image contains an intact copy of swapfile.pl with correct permissions.


Expert instructions

  1. Read the MIT License, in particular the line

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    In other words, this software is free. In particular, you may modify and redistribute it, and you may freely link to this web page. However, if you want to post minor variations of this code on various forums, you have to include the above copyright notice in its entirety.

    If you can re-engineer from scratch using the key line

    swaphash=`pdisk /dev/disk${swapdisk} -dump 2>/dev/null | grep -v '/dev/disk' | md5 -q`

    you're in the clear. For minor variations of my exact code, my goal is to make any user aware of changes that I might have subsequently posted.

  2. Print out this page and the page Moving the swapfile in Mac OS X 10.3 (Panther). If all hell breaks loose, it might be nice to have some hard copy describing how to get your computer back.

  3. Download the disk image swapfile.dmg and find the Perl script swapfile.pl. Read the script and this documentation, until you are comfortable with the methods used. It is a poor idea to run any code one doesn't understand, as superuser.

    swapfile.pl configures and writes the helper file /etc/rc.swapfile, to be called by /etc/rc during the boot process, to set up a swapfile on a different partition. The source for /etc/rc.swapfile is embedded in the Perl script code.

  4. To move one's swap file to a partition named Swap, execute the following command in a terminal window:
    sudo swapfile.pl install Swap
    
    This syntax assumes that swapfile.pl is in the current execution path, which it won't be. Instead, drag swapfile.pl onto the terminal window to insert its full path. If you're running swapfile.pl from the mounted disk image swapfile.dmg, chances are that /Volumes/swapfile/swapfile.dmg will be the full path.

  5. If the swap partition is on a non-IDE drive, also execute the following command:
    sudo swapfile.pl automount true
    
    This executes the shell command

    defaults write /Library/Preferences/SystemConfiguration/autodiskmount AutomountDisksWithoutUserLogin -bool true

  6. Restart

  7. To see what happened, execute the following command:
    swapfile.pl inspect
    

  8. To uninstall, execute the command:
    sudo swapfile.pl uninstall
    Now restart, and execute the command:
    sudo swapfile.pl cleanup Swap
    This undoes all changes. We wait until after restarting to delete the old swapfiles, to avoid pulling the rug out from under the virtual memory manager.

  9. To see a list of volumes, with disk fingerprints, execute the command:
    sudo swapfile.pl volumes
    One can leave off sudo, in which case /dev/disk0 will be skipped.


Commentary

This discussion is not intended to be a tutorial. If you are not up to speed on the issues involved in moving the swap partition, please read Moving the swapfile in Mac OS X 10.3 (Panther). Many crucial issues are explained in detail there, but assumed as background here.

How well does the code work?

I have never seen the current code fail to relocate the swapfile, experimenting with three different PowerPC computers and an assortment of internal and external drives of varying vintages. Earlier versions sometimes didn't wait long enough for system events to complete, but they always failed gracefully, reverting to the default swapfile location.

This code has not been tested on the new Intel Macs. They use a different partition scheme, which could be an issue; see Booting an Intel iMac from an External Drive.

One should be very wary of any approach to moving the swapfile, including this one. The internet abounds with poor solutions, on forums and in shareware programs. When someone says a solution works, generally this means that they haven't done enough testing. I've run many tests, but not with your configuration. You're on your own. No one needs to move their swapfile, so don't even try this unless you think you know what you're doing, you understand my code, and you prefer running my code to writing your own.

How does the code work?

Early in the boot process, Apple's startup shell script /etc/rc sets up virtual memory. To use a helper file /etc/rc.swapfile to customize virtual memory, one can insert the highlighted line into /etc/rc:

echo "Starting virtual memory"

swapdir=/private/var/vm

if [ -f /etc/rc.swapfile ]; then . /etc/rc.swapfile; fi # inserted locally

if [ "${NetBoot}" = "-N" ]; then
    sh /etc/rc.netboot setup_vm ${swapdir}
fi

if [ ! -d ${swapdir} ]; then
    echo "Creating default swap directory"
    mkdir -p -m 755 ${swapdir}
    chown root:wheel ${swapdir}
else
    RMRF_ITEMS="${RMRF_ITEMS} ${swapdir}/swap*"
fi
		    
echo Removing $RMRF_ITEMS
rm -rf $RMRF_ITEMS

if [ ${ENCRYPTSWAP:=-NO-} = "-YES-" ]; then
    encryptswap="-E"
else
    encryptswap=""
fi
/sbin/dynamic_pager ${encryptswap} -F ${swapdir}/swapfile

This line executes the contents of /etc/rc.swapfile exactly as if they were inserted into /etc/rc at this point. In particular, any typo in /etc/rc.swapfile can derail the startup process. However, if the file /etc/rc.swapfile doesn't exist or has been renamed, this line has no effect.

/etc/rc.swapfile checks to see if the BSD device is already mounted. Otherwise, it waits until the BSD device appears to be available to be mounted, and then issues a series of mount requests, waiting between each request, until the device successfully mounts. In the vast majority of cases, no waiting is required, obscuring the fact that waiting is sometimes necessary. Nevertheless, code that does not conditionally wait at each of these junctures will be less reliable.

/etc/rc.swapfile then redefines swapdir to point to a hidden directory .vm in the desired swap partition. If anything goes wrong, it leaves swapdir alone.

At this point in the startup process, one can only mount volumes via their BSD device names, of the form /dev/disk1s10. However, OS X doesn't number disks consistently from boot to boot; any swap code that relies on consistent BSD device names (including numerous shareware and forum solutions) is flawed. With multiple contemporary FireWire drives attached to a hub, I have experienced essentially random disk numbering from boot to boot. This is a real issue, not a hypothetical concern.

One solution, proposed in 2001 by Markus Hitter (see Re: warning: /etc/fstab doesn't do what you think), is to use the command pdisk to examine the properties of unmounted BSD devices. For example, here is how pdisk describes my swap partition:

% pdisk /dev/disk1 -dump

Partition map (with 512 byte blocks) on '/dev/disk1'
 #:                type name                    length   base      ( size )
 1: Apple_partition_map Apple                       63 @ 1        
 2:      Apple_Driver43*Macintosh                   56 @ 64       
 3:      Apple_Driver43*Macintosh                   56 @ 120      
 4:    Apple_Driver_ATA*Macintosh                   56 @ 176      
 5:    Apple_Driver_ATA*Macintosh                   56 @ 232      
 6:      Apple_FWDriver Macintosh                  512 @ 288      
 7:  Apple_Driver_IOKit Macintosh                  512 @ 800      
 8:       Apple_Patches Patch Partition            512 @ 1312     
 9:          Apple_Free                         262144 @ 1824      (128.0M)
10:           Apple_UFS Apple_HFS_Untitled_1  10475280 @ 263968    (  5.0G)
11:          Apple_Free                         262144 @ 10739248  (128.0M)
12:           Apple_HFS Apple_HFS_Untitled_2 477395760 @ 11001392  (227.6G)
13:          Apple_Free                             16 @ 488397152

Device block size=512, Number of Blocks=488397168 (232.9G)
DeviceType=0x0, DeviceId=0x0
Drivers-
1:  23 @ 64, type=0x1
2:  36 @ 120, type=0xffff
3:  21 @ 176, type=0x701
4:  34 @ 232, type=0xf8ff

% pdisk /dev/disk1 -partitionEntry 10
Apple_HFS_Untitled_1 Apple_UFS 263968 10475280

The only information that is variable from boot to boot is the reference to /dev/disk1 on the first line of output. One can fingerprint this drive by excluding this line and computing an md5 checksum:

% pdisk /dev/disk1 -dump 2>/dev/null | grep -v '/dev/disk' | md5 -q
f7904e44f9d7b5043d90126fb74a0bca

Note that nonexistent drives yield a consistent fingerprint:

% pdisk /dev/disk9 -dump 2>/dev/null

% pdisk /dev/disk9 -dump 2>/dev/null | grep -v '/dev/disk' | md5 -q
d41d8cd98f00b204e9800998ecf8427e

% echo -n | md5 -q
d41d8cd98f00b204e9800998ecf8427e

In my case, /dev/disk1 is the only hard drive in my possession containing an Apple_UFS Unix partition, so fingerprinting uniquely identifies this drive. However, it is possible for two drives to appear to be identical clones at this level, yet be intended for different purposes. For example, I might have bought two identical IDE drives, put them into two identical FireWire enclosures, and formatted them using identical partition types and sizes, but using different Finder names for different purposes. One drive might consist of the volumes Swap and Backups, while its twin might consist of the volumes Sys and Music, where Sys is a fairly full emergency boot partition. So an additional step is necessary to uniquely designate our swap partition. In my experience, this is largely a hypothetical concern.

If one has the luxury of planning in advance, clearly one should choose a unique partition size for one's swap volume. Add a random increment one would never again come up with by accident. End of story.

To address this issue, and as an additional convenience, /etc/rc.swapfile checks for the existence of a hidden file named .enablevm on the swap volume. If it doesn't find this file, it unmounts the volume, and leaves the default swapping mechanism alone. Later, the unmounted volume is remounted in the normal course of events.

It usually suffices to simply mount the desired volume:

/sbin/mount -vt ${swaptype} ${swapdevice} "/Volumes/${swapvolume}" >>${swaplog} 2>&1

However, in testing some volumes failed to mount, that could be successfully mounted after a call to fsck:

/sbin/fsck -y ${swapdevice} >/dev/null 2>&1
/sbin/mount -vt ${swaptype} ${swapdevice} "/Volumes/${swapvolume}" >>${swaplog} 2>&1

It is slightly unsettling to use fsck here; it always finds something to complain about, but it is sometimes helpful. We respond yes to all prompts, and ignore anything it says.

One might ask, why stay in such a relationship? The cases where a call to fsck is helpful seem to also be handled by waiting. I believe that this call can be commented out; asking the system to go compute a bunch of primes would probably do as much good here. However, this call is pretty standard Unix genuflecting.

The current version of rc.swapfile defers calling fsck until the third of five mount attempts. If you believe that one must always call fsck before mount, then change this logic.

The umount command, executed when .enablevm could not be found, can also probably be commented out:

/sbin/umount -v "/Volumes/${swapvolume}" >>${swaplog} 2>&1

As a bit of magic for which I can find no documentation, mount refuses to mount volumes in /Volumes/ except by their given name. Thus, this mount is fine, and we could choose to leave well enough alone. I observed in testing that the unmounted volume remounts later just fine, I haven't tested commenting out this line, and I can't find documentation to support the observed magic that would justify commenting out this line, so I'm leaving it in.

In the event of twin drives, one could imagine a rewrite of /etc/rc.swapfile that exploited this fact, successfully waiting for and finding the second of two apparent clones after rejecting the first. I choose not to do this, at least for now: This would unnecessarily complicate already elaborate code, focused on taking all possible measures to insure the successful mounting of a drive with a unique fingerprint. Given this BSD numbering gotcha!, I believe that people should keep drive fingerprints unique, just as we strive to keep volume names unique. As stated earlier, this can be accomplished by making tiny changes to partition sizes, and in most cases drives are already unique for other reasons.

Spaces in volume names

Don't. I made an effort to keep my code spaces-in-names clean, only to discover that such names shipwrecked in Apple's /etc/rc code. You could edit Apple's code, but that would be pretty obstinate.

Single-user mode

My code shouldn't throw you into single-user mode, where the OS X graphical user interface is replaced by a black terminal screen with white text, but if you decide to experiment further on your own, it could happen. Tiger appears to be more tolerant than Panther of syntax errors in rc.swapfile, but nevertheless, one should be prepared to recover from single-user mode. For example, if one of our calls to mount returns an error, the startup process is derailed, and our interface gets rolled back 30 years. It looks to the uninitiated like the computer is now hosed, but don't be alarmed; recovery is easy.

The simplest way to recover is by renaming rc.swapfile:

cd /etc
mv rc.swapfile rc.swapfile.off
reboot
This is why you printed out a hard copy of this page, right? See Moving the swapfile in Mac OS X 10.3 (Panther) for further information.

It is good practice to have several boot volumes available, for many reasons including moments like this. If another boot volume is available, then as an alternative one can type reboot at the black screen, and hold down the option key while OS X restarts, to be presented with all available options for the boot volume. Booting into a working volume, one can then fix the mess using a contemporary graphical user interface. This takes longer than the first remedy.

Using this code with OS X 10.3 (Panther)

The current code also works with Panther, although I haven't extensively tested it with Panther.

The logfile

fsck reports its progress to /var/log/rc.swapfile.log. For example, my most recent entry is

Mon Feb 6 14:25:19 PST 2006
/dev/disk3 f7904e44f9d7b5043d90126fb74a0bca
/sbin/fsck -y /dev/disk3s10
/sbin/mount -vt ufs /dev/disk3s10 /Volumes/Swap
/dev/disk3s10 on /Volumes/Swap (asynchronous, local)
Using /dev/disk3s10 for swapfile

Further reading


Current version: February 22, 2006

% md5 -q /Volumes/swapfile/swapfile.pl 
006c70ad81a1685608784bac50472864

Version history