#!/usr/bin/perl use warnings; use strict; # swapfile.pl -- Perl script for moving swapfile in Mac OS X 10.4 # # Copyright (c) 2004-2006 Dave Bayer # Subject to the terms and conditions of the MIT License. (my $rc_swapfile = <<'EOF') =~ s/^: ?//mg; : #!/bin/sh : # : # rc.swapfile -- set up a separate swapfile partition in Mac OS X 10.4 : # : # Copyright (c) 2004-2006 Dave Bayer : # Subject to the terms and conditions of the MIT License. : # : # http://www.math.columbia.edu/~bayer/OSX/swapfile/ : # : # Permission is hereby granted, free of charge, to any person obtaining a : # copy of this software and associated documentation files (the "Software"), : # to deal in the Software without restriction, including without limitation : # the rights to use, copy, modify, merge, publish, distribute, sublicense, : # and/or sell copies of the Software, and to permit persons to whom the : # Software is furnished to do so, subject to the following conditions: : # : # The above copyright notice and this permission notice shall be included in : # all copies or substantial portions of the Software. : # : # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR : # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, : # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE : # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER : # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING : # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER : # DEALINGS IN THE SOFTWARE. : : swapsearch='0 1 2 3 4 5 6 7 8 9' : swapmap='10' : swapvolume='Swap' : swaptype='ufs' : swapprint='f7904e44f9d7b5043d90126fb74a0bca' : swaplog='/var/log/rc.swapfile.log' : : echo >>${swaplog} : echo `date` >>${swaplog} : : swskip='d41d8cd98f00b204e9800998ecf8427e' : swcount=1 : swfound=0 : while [ $swcount -le 5 ] && [ $swfound -eq 0 ] : do : if [ $swcount -ge 2 ]; then echo '--' >>${swaplog}; fi : for swapdisk in ${swapsearch} : do : swaphash=`pdisk /dev/disk${swapdisk} -dump 2>/dev/null | grep -v '/dev/disk' | md5 -q` : if echo ${swaphash} | grep -qF ${swskip}; then continue; fi : echo /dev/disk${swapdisk} ${swaphash} >>${swaplog} : : if echo ${swaphash} | grep -qF ${swapprint} : then : swfound=1 : break : fi : done : sleep 2 : swcount=`expr $swcount + 1` : done : : swmounted=0 : if [ $swfound -eq 1 ] : then : swapdevice=/dev/disk${swapdisk}s${swapmap} : swcount=0 : swmounted=1 : until /sbin/mount | grep -qF ${swapdevice} : do : if [ $swcount -ge 5 ] : then : echo "Unable to mount swap volume ${swapvolume}; using default configuration" >>${swaplog} : swmounted=0 : break : fi : sleep $swcount : if [ $swcount -eq 2 ] : then : echo "/sbin/fsck -y ${swapdevice}" >>${swaplog} : /sbin/fsck -y ${swapdevice} >/dev/null 2>&1 : sleep 1 : fi : echo "/sbin/mount -vt ${swaptype} ${swapdevice} /Volumes/${swapvolume}" >>${swaplog} : /sbin/mount -vt ${swaptype} ${swapdevice} "/Volumes/${swapvolume}" >>${swaplog} 2>&1 : sleep $swcount : swcount=`expr $swcount + 1` : done : else : echo "Swap volume ${swapvolume} not found; using default configuration" >>${swaplog} : fi : : if [ $swmounted -eq 1 ] : then : if [ -f "/Volumes/${swapvolume}/.enablevm" ] : then : echo "Using ${swapdevice} for swapfile" >>${swaplog} : if [ -f ${swapdir}/swapfile0 ]; then rm -rf ${swapdir}/swap*; fi : swapdir="/Volumes/${swapvolume}/.vm" : else : echo "/Volumes/${swapvolume}/.enablevm not found; using default configuration" >>${swaplog} : echo "/sbin/umount -v /Volumes/${swapvolume}" >>${swaplog} : /sbin/umount -v "/Volumes/${swapvolume}" >>${swaplog} 2>&1 : fi : fi : : unset swapsearch swapmap swapvolume swaptype swapprint swskip swaplog swcount swfound swapdisk swaphash swapdevice swmounted EOF (my $help = <<'EOF') =~ s/^: ?//mg; : : Usage: : : swapfile.pl install volume : swapfile.pl uninstall : swapfile.pl inspect : swapfile.pl automount bool : swapfile.pl cleanup volume : swapfile.pl volumes : : Examples: : : sudo swapfile.pl install Swap : Moves swapfile to partition mounted at /Volumes/Swap, on next restart : : sudo swapfile.pl uninstall : Restores location of swapfile to system partition, on next restart : : swapfile.pl inspect : View diagnostic information about current status of swap partition : : sudo swapfile.pl automount true : Sets system preference to mount external drives during startup : : [sudo] swapfile.pl cleanup volume : Remove swap files from volume no longer used for swapping : : [sudo] swapfile.pl volumes : View disk fingerprints and associated volumes : EOF my ($rc_key, $rc_before, $rc_insert, $rc_remove, %rc_md5, $rc_swapfile_md5, $hr, %run, $do, $arg); $rc_key = ".enablevm"; $rc_before = "swapdir=/private/var/vm"; $rc_insert = "if [ -f /etc/rc.swapfile ]; then . /etc/rc.swapfile; fi # inserted locally"; %rc_md5 = ( # OS X 10.3 /etc/rc '79c0a57e77161ed614dfc72e3bf833f5' => 0, 'cdb80266e5ee95f478ced5e32142dc46' => 1, # OS X 10.4 /etc/rc '0406f688163230496ee55f85fdd413da' => 0, '64e03196440f0cd5c4bbbe563d269d55' => 1, ); sub cmd { my ($do) = @_; print "\n % $do\n"; my $res = `$do`; (my $show = $res) =~ s/^/ /mg; print $show; $res; } sub rc_modded { my $fingerprint = `md5 -q /etc/rc`; chop($fingerprint); my $mod = $rc_md5{$fingerprint}; if (! defined($mod)) { printf "Warning: unrecognized version of /etc/rc\n%s\n", $fingerprint; $mod = `grep -F "$rc_insert" "/etc/rc"` ? 1 : 0; } if ($mod) { print "/etc/rc calls /etc/rc.swapfile\n"; } else { print "/etc/rc does not call /etc/rc.swapfile\n"; } $mod; } sub edit_rc { local $/; open(FILE,"; close(FILE); if ($do eq "install") { s:($rc_before\n):$1\n$rc_insert\n\n:; } elsif ($do eq "uninstall") { (my $rc_delete = $rc_insert) =~ s:\[:\\[:; s:\n\s*$rc_delete\s*:\n:; } open(FILE,">/etc/rc") or die "unable to write to file /etc/rc\n\n"; print FILE; close(FILE); } sub automount_status { my $status = "false"; if ( -f "/Library/Preferences/SystemConfiguration/autodiskmount.plist") { $status = `defaults read /Library/Preferences/SystemConfiguration/autodiskmount AutomountDisksWithoutUserLogin`; $status = $status == 1 ? "true" : "false"; printf "AutomountDisksWithoutUserLogin is %s\n\n", $status; } else { printf "autodiskmount.plist has not yet been created\n\n"; } $status; } sub install_cmd { my ($vol) = @_; local $/; die "/etc/rc does not support whitespace in swap volume names\n\n" if $vol =~ /\s/; (my $df = cmd("df | grep '/Volumes/$vol\$'")) or die "/Volumes/$vol not found\n\n"; (my $disk = $df) =~ s:/dev/disk(\d*)s\d*.*\n:$1:; (my $map = $df) =~ s:/dev/disk\d*s(\d*).*\n:$1:; my $entry = cmd("pdisk /dev/disk$disk -partitionEntry $map"); my $type = "hfs"; if (1 == `echo '$entry' | grep -c ' Apple_HFS '`) { $type = "hfs"; } elsif (1 == `echo '$entry' | grep -c ' Apple_UFS '`) { $type = "ufs"; } else { print "\nWarning: unable to determine type of partition format; defaulting to hfs\n"; } my $fingerprint = cmd("pdisk /dev/disk${disk} -dump 2>/dev/null | grep -v '/dev/disk' | md5 -q"); chop($fingerprint); my $skip = `echo -n | md5 -q`; chop($skip); my $search = '0 1 2 3 4 5 6 7 8 9'; $search =~ s:$disk ?::; $search = "$disk " . $search; if (-f "/etc/rc.swapfile") { print "\n/etc/rc.swapfile already exists, and will be updated\n" } else { print "\ncreating /etc/rc.swapfile\n" } $_ = $rc_swapfile; s:(swapsearch=)'[ \d]*':$1'$search':; s:(swapmap=)'\d*':$1'$map':; s:(swapvolume=)'\w*':$1'$vol':; s:(swaptype=)'\w*':$1'$type':; s:(swapprint=)'\w*':$1'$fingerprint':; s:(swskip=)'\w*':$1'$skip':; open(FILE,">/etc/rc.swapfile") or die "unable to write to file /etc/rc.swapfile\n\n"; print FILE; close(FILE); cmd("grep '^swap.*=\'.*\'\$' /etc/rc.swapfile"); print "\n"; automount_status(); if (! rc_modded()) { print "editing /etc/rc to insert call to rc.swapfile\n"; edit_rc(); } print "\n"; if (-f "/Volumes/$vol/$rc_key") { print "/Volumes/$vol/$rc_key already exists\n"; } else { print "writing /Volumes/$vol/$rc_key\n"; `echo > '/Volumes/$vol/$rc_key'`; } print "\n"; } sub uninstall_cmd { print "\n"; automount_status(); if (rc_modded()) { print "editing /etc/rc to delete call to rc.swapfile\n\n"; edit_rc(); } if (-f "/etc/rc.swapfile") { print "deleting /etc/rc.swapfile\n"; `rm /etc/rc.swapfile`; } else { print "/etc/rc.swapfile not found\n"; } print "\n"; } sub inspect_cmd { print "\n"; automount_status(); rc_modded(); cmd("grep '^swap.*=\'.*\'\$' /etc/rc.swapfile") if ( -f "/etc/rc.swapfile"); cmd("tail -n 16 /var/log/rc.swapfile.log"); my $path = cmd("ps -wwax | grep dynamic_pager | grep -v grep"); $path =~ s/.* (\S+)\n/$1/; cmd("ls -l $path*"); print "\n"; } sub automount_cmd { my ($bool) = @_; $bool eq "true" or $bool eq "false" or die "true or false required as parameter to automount\n\n"; print "\n"; if ($bool ne automount_status()) { print "setting AutomountDisksWithoutUserLogin to $arg\n"; `defaults write /Library/Preferences/SystemConfiguration/autodiskmount AutomountDisksWithoutUserLogin -bool $arg`; } } sub cleanup_cmd { my ($vol) = @_; print "\n"; die "/Volumes/$vol not found\n\n" unless `df | grep '/Volumes/$vol\$'`; die "$vol is in use as the swap partition\n\n" if `ps -wwax | grep dynamic_pager -m1 | grep '/Volumes/$vol'`; automount_status(); if (-f "/Volumes/$vol/$rc_key") { print "deleting /Volumes/$vol/$rc_key\n"; `rm '/Volumes/$vol/$rc_key'`; } else { print "/Volumes/$vol/$rc_key not found\n"; } if (-d "/Volumes/$vol/.vm") { print "deleting /Volumes/$vol/.vm\n"; `rm -r '/Volumes/$vol/.vm'`; } else { print "/Volumes/$vol/.vm not found\n"; } print "\n"; } sub volumes_cmd { my @mount; for my $line (split /\n/, `mount | grep /dev/disk | sort`) { $line =~ s:^/dev/disk(\d+)s\d+ on (.*) \([^()]*\)$:$1\n$2:; my ($disk, $vol) = split /\n/, $line; $vol =~ s:/Volumes/::; if ($vol =~ /[,\s]/) { $vol =~ s/.*/"$vol"/; } push @{$mount[$disk]}, $vol; } my $skip = `echo -n | md5 -q`; chop $skip; print "\n"; for (my $disk=0; ; $disk++) { my $hash = `pdisk /dev/disk${disk} -dump 2>/dev/null | grep -v '/dev/disk' | md5 -q`; chop $hash; if ( $hash eq $skip ) { if ($disk == 0) { next; } else { last; } } printf "%s /dev/disk%d ", $hash, $disk; for my $vol (@{$mount[$disk]}) { printf "%s ", $vol; } print "\n"; } print "\n"; } %run = ( 'install' => [\&install_cmd, 1], 'uninstall' => [\&uninstall_cmd, 0], 'inspect' => [\&inspect_cmd, 0], 'automount' => [\&automount_cmd, 1], 'cleanup' => [\&cleanup_cmd, 1], 'volumes' => [\&volumes_cmd, 0], ); ($do, $arg) = @ARGV; if (not $do or not $run{$do} or $run{$do}[1] != $#ARGV) { print $help; exit(1); } $run{$do}[0]($arg); print "done\n\n";