#!/bin/bash
# AppleJack, an open source basic troubleshooting utility for Mac OS X
# Copyright (c) 2002-10 Kristofer Widholm, The Apotek
# $Id: applejack.sh,v 1.144 2010/07/11 04:39:16 kwidholm Exp $
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or -at your
# option- any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# You can reach The Apotek at http://www.theapotek.com
#
########################################################################
# START OF APPLEJACK CONFIGURATION ############# CONSTANTS ###########
########################################################################
VERSION="1.6"
REVISION=`echo '$Revision: 1.144 $' | sed 's/\\$//g' 2> /dev/null`
DEEP=0 # Set deep mode to 0 [off], unless specified at runtime
DEFAULTDELAY=3 # How long should the default delay be
CANCELTIME=10 # How many seconds should the user have to cancel automatic tasks?
DRL=9 # Disk Repair Limit: How many times should disk repair repeat before aborting [auto mode], or posting a notice [manual mode]?
BANNER="\033[4m AppleJack \033[0m"
GOODBYE="*********************** GOODBYE FROM APPLEJACK ***********************"
LOGFILE="/private/var/log/AppleJack.log" # Where does the AppleJack log go?
# Set up a good path--from /etc/rc
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec:/System/Library/CoreServices;export PATH
# standard exit code for usage error [from C, /usr/include/sysexits.h]
EX_OK=0 # successful termination
EX_ERR=1 # general error
EX_USAGE=64 # user error
EX_SOFTWARE=70 # internal software error
EX_OSERR=71 # system error [e.g., can't fork]
# Formatting shortcut codes
# Bold: \033[1m Underline: \033[4m Magenta: \033[35m Yellow: \033[33m Red: \033[31m
# These must be defined with double quotes to be filtered correctly
bE="\033[31m*** " # Start formatting for "errors"
eE=" ***\033[0m" # End of format for errors.
bH="\033[35m\033[1m\033[4m" # Color code for key highlights
eH="\033[0m" # End of color code for key highlights
bK="\033[35m\033[1m" # Color code for key entry
eK="\033[0m" # End of formatting for key entry
bW="\033[33m" # formatting for "warnings"
eW="!\033[0m" # End of formatting for warnings.
bS="\033[1m" # Begin strong
eS="\033[0m" # End strong
bPar='(' # Parentheses in strings confuse some text editors.
ePar=')'
TASKNAMES=( [0]="${bH}a${eH}uto pilot" [1]="repair ${bH}d${eH}isks" [2]="repair ${bH}p${eH}ermissions" [3]="cleanup ${bH}c${eH}ache files" [4]="validate pre${bH}f${eH}erence files" [5]="clean up ${bH}v${eH}irtual memory" [6]="${bH}q${eH}uit" )
TASKCODES=( a d p c f v q )
TASKCODESUC=( A D P C F V Q )
TASKS=( [0]='AUTO=1;selectNext' [1]='repairDisks' [2]='fixPermissions' [3]='cacheCleanup' [4]='validatePreferences' [5]='cleanupVM' [6]='quitScript' )
xTASKNAMES=( [0]="deep clean ${bH}c${eH}ache files" [1]="${bH}v${eH}erify hard drives ${bPar}S.M.A.R.T. Check${ePar}" [2]="test ${bH}m${eH}emory" [3]="${bH}b${eH}less system folder" [4]="disable ${bH}a${eH}uto login" [5]="disable ${bH}l${eH}ogin items for a user" [6]="${bH}r${eH}estore NetInfo database from backup (Mac OS X Tiger only)" [7]="disable system con${bH}f${eH}iguration files" [8]="disable NetInfo ${bH}N${eH}FS mounts (Mac OS X Tiger only)" [9]="enable new machine ${bH}s${eH}etup" [10]="${bH}q${eH}uit" )
xTASKCODES=( c v m b a l r f n s q )
xTASKCODESUC=( C V M B A L R F N S Q )
xTASKS=( [0]='xDeepCacheClean' [1]='xSmartCheck' [2]='xMemTest' [3]='xBlessDrive' [4]='xDisableAutoLogin' [5]='xDisableUserLoginItems' [6]='xRestoreNetinfoFromBackup' [7]='xDisableSysConfigFiles' [8]='xDisableNFSMounts' [9]='xEnableNewSetup' [10]='quitScript' )
# standardize the binaries, if possible
BADENV=0
if [ -x /usr/bin/awk ];then AWK='/usr/bin/awk';else AWK='awk';BADENV=1;fi
if [ -x /usr/sbin/bless ];then BLESS='/usr/sbin/bless';else BLESS='bless';BADENV=1;fi
if [ -x /bin/chmod ];then CHMOD='/bin/chmod';else CHMOD='chmod';BADENV=1;fi
if [ -x /usr/sbin/chown ];then CHOWN='/usr/sbin/chown';else CHOWN='chown';BADENV=1;fi
if [ -x /bin/cp ];then CP='/bin/cp';else CP='cp';BADENV=1;fi
if [ -x /usr/bin/egrep ];then EGREP='/usr/bin/egrep';else EGREP='egrep';BADENV=1;fi
if [ -x /usr/bin/file ];then FILE='/usr/bin/file';else FILE='file';BADENV=1;fi
if [ -x /usr/bin/grep ];then GREP='/usr/bin/grep';else GREP='grep';BADENV=1;fi
if [ -x /usr/libexec/kextd ];then KEXTD='/usr/libexec/kextd';else KEXTD='kextd';BADENV=1;fi
if [ -x /bin/ln ];then LN='/bin/ln';else LN='ln';BADENV=1;fi
if [ -x /bin/ls ];then LS='/bin/ls';else LS='ls';BADENV=1;fi
if [ -x /bin/mkdir ];then MKDIR='/bin/mkdir';else MKDIR='mkdir';BADENV=1;fi
if [ -x /sbin/mount ];then MOUNT='/sbin/mount';else MOUNT='mount';BADENV=1;fi
if [ -x /bin/mv ];then MV='/bin/mv';else MV='mv';BADENV=1;fi
if [ -x /usr/bin/dscl ];then DSCL='/usr/bin/dscl';DSCLav=1;else DSCL='dscl';DSCLav=0;fi
if [ -x /usr/bin/plutil ];then PLUTIL='/usr/bin/plutil';else PLUTIL='plutil';BADENV=1;fi
if [ -x /bin/rm ];then RM='/bin/rm';else RM='rm';BADENV=1;fi
if [ -x /bin/rmdir ];then RMDIR='/bin/rmdir';else RMDIR='rmdir';BADENV=1;fi
if [ -x /usr/bin/sed ];then SED='/usr/bin/sed';else SED='sed';BADENV=1;fi
if [ -x /usr/bin/tee ];then TEE='/usr/bin/tee';else SED='tee';BADENV=1;fi
# Register the IDENTITY variable, for later use
#declare -arx IDENTITY=( `id | cut -d '=' -f 2 | cut -d ' ' -f 1 | $SED -e 's/[()]/ /g;'` )
declare -arx IDENTITY=( `id -u` `id -un` ) # Leopard id utility barfs when running id without options
########################################################################
# END OF APPLEJACK CONFIGURATION #####################################
########################################################################
# Make sure to clean up whenever the script is killed
trap quitScript INT
# Thanks to "Maarten" from the Ars Technica Open Forum for providing this function.
# checkAlias returns 1 if file is an alias; 0 if not.
# When used like so: `if checkAlias "filename";then, the 0 signals NOT an alias,
# so this would evaluate to TRUE
function checkAlias() {
local testfile="$1"
# To be an alias, it must be of zero file size,
# have a resource fork,
# that contains an 'alis' resource.
# Old way
# test -s "$testfile" || test -s "$testfile/rsrc" && grep --quiet 'alis' "$testfile/rsrc" && return 1
# New way
test -s "$testfile" || test -s "$testfile/..namedfork/rsrc" && grep --quiet 'alis' "$testfile/..namedfork/rsrc" && return $EX_ERR
return 0; # Not an alias: success
}
# Allows user to choose a user directory to work on
# 1. Grab usernames and ids from NetInfo filtering for user ids > 100
# dsusers=' ';for u in `nicl . -list /users | awk '{ print $2 }'`;do uid=`nicl . -read "/users/$u" uid | sed -e 's/^uid: //'`;if [ $uid -gt 100 ];then uhome=`nicl . -read "/users/$u" home | sed -e 's/home: //'`;dsusers="${users}$uid:$u:$uhome ";fi;done;echo $dsusers
# 2. Grab usernames and ids from /etc/passwd, filtering for user ids > 100
# pwusers=' ';for user in `cat /etc/passwd | grep -v '#' | sed -e 's/:/ /g' | awk '{ print $1":"$3":"$6 }'`;do uid=`echo $user | cut -f 2 -d ':'`;if [ $uid -gt 100 ];then pwusers="${pwusers}${user} ";fi;done;echo ":$pwusers:"
# 3. Grab usernames and ids from any other system Apple uses for storing user info that does not require higher system level operations. NA
# 4. Grab usernames and ids from the /Users folder
# 5. Condense all the info privileging the information in reverse order
# 6. Filter out any common user ids that are system user ids
# 6. Print a list of usernames, using the userids as menu selects
# 7. In the input, the user can either type in the user's id, the user's name, or an absolute path to the directory in question.
# 8. Parse user input: If it's a number, grab the associated home direcory; if it's a name, go with the username's associated home directory; if it starts with a slash, just go to that directory
function chooseUserDirectory() {
local user
local uid
local uname
local uhome
local uc
local ul
local tail
local homedir=''
local choice
local err=0
local ds_command
if [ -z "$USERLIST" ];then
startServices
local dsusers=''
for uid in `$DSCL . -list /users uid 2>/dev/null | $AWK '{ print $2}'`;do
if [ $uid -gt 400 ];then
uname=`id -un $uid | $AWK '{ print $1 }'`
uhome=`$DSCL . -read "/users/$uname" home 2>/dev/null | $AWK '{ print $2 }'`
dsusers="${dsusers}$uid:$uname:$uhome "
fi
done
if [ ! -z "$dsusers" ];then
USERLIST="$dsusers"
fi
local pwusers=''
for user in `cat /etc/passwd | $GREP -v '#' | grep -v '^_' | cut -d : -f 1,3,6`;do
uid=`echo "$user" | cut -f 2 -d :`
if [ $uid -gt 100 ] && echo " $USERLIST" | $GREP -v " $uid:">/dev/null;then
user=`echo "$uid:$user" | cut -d : -f 1,2,4`
pwusers="${pwusers}${user} "
fi
done;
if [ ! -z "$pwusers" ];then
USERLIST="${USERLIST}$pwusers"
fi
local dirusers=''
for user in `$LS -CLln /Users/ | $GREP '^d' | $EGREP -v '(Shared|Temporary)' | $AWK '{ print $3":/Users/"$9 }'`;do
uid=`echo $user | cut -f 1 -d :`
uhome=`echo $user | cut -f 2 -d :`
uname=`id -nu "$uid"`
if [ $uid -gt 100 ] && echo " $USERLIST" | $GREP -v " $uid:">/dev/null;then
dirusers="${dirusers}${uid}:${uname}:${uhome} "
fi
done
if [ ! -z "$dirusers" ];then
USERLIST="${USERLIST}$dirusers"
fi
fi
echo ""
uc=0
for ul in $(
for user in $USERLIST;do
echo $user
done | sort -n );do
if [ $uc -ge 3 ]; then
echo ""
uc=0
fi
uid=${ul%%:*}
tail=${ul#*:} # leaves us with username:/user/path
un=${tail%%:*}
loggit -n "[${uid}] $un "
let "uc=(uc+1)"
done
echo ""
echo 'Enter the user id or user name of the user whose home folder you want '
echo -n 'to work on. You can also just type in the path directly, if you prefer> '
read choice
echo ''
# check if choice is empty
if [ -z "$choice" ];then
loggit "You did not enter anything. Returning to main menu."
UD='' # clear user directory and send them back
return
fi
# check if choice is a well-formed directory path
if echo $choice | grep -q '^/';then
homedir="$choice"
else
# loop through all the users in the USERLIST and look for matches to input
for user in $USERLIST;do
uid=${user%%:*}
tail=${user#*:} # leaves us with username:/user/path
uname=${tail%%:*}
uhome=${tail##*:}
if [ "$choice" == "$uid" ];then
homedir="$uhome"
loggit "for user $uid, the home directory is $uhome"
break
else
if [ "$choice" == "$uname" ];then
homedir="$uhome"
loggit "for username $uname, the home directory is $uhome"
break
fi
fi
done
fi
if [ -z "$homedir" ];then
loggit "${bW}$choice is not a valid user id, user name, or directory. Please try again${eW}"
err=$EX_ERR
else
if [ ! -d "$homedir" ];then
loggit "${bW}$homedir does not exist. Please try again.${eW}"
err=$EX_ERR
fi
fi
if [ $err -eq $EX_ERR ];then
echo ''
chooseUserDirectory
else
UD="$homedir"
fi
}
function countDown() {
local count
if [ -z "$1" ];then
let count=$DEFAULTDELAY
else
count=$1
fi
for ((i=1; i <= count ; i++));do
echo -n ".";
sleep 1
done
echo ""
return
}
function getUserApproval() {
local ans
echo ""
echo -en "${1} [${bH}y${eH}/${bH}n${eH}]: ${bK}"
read ans
echo -e "${eK}"
echo ""
if [ "$ans" = "y" ];then
return $EX_OK
else
return $EX_ERR
fi
}
function loggit() {
local nls
local string
if [ "$1" = "-n" ];then
nls="$1"
string="$2"
else
nls=""
string="$1"
fi
echo $nls -e "$string"
if [ $WRITEABLEROOT -eq 1 ];then
if [ -z "$TEMPLOG" ];then
# file system is writable and there is nothing in the templog to write to the log
# filter out any formatting codes when you put the string into the log.
echo $nls "$string" | $SED -e 's/\\033\[[0-9]*m//g' | tee -a "$LOGFILE" >/dev/null
else
# file system has become writable and we must dump the templog to the log file
string=`echo "$string" | $SED -e 's/\\\033\[[0-9]*m//g'` # strip color codes for log
TEMPLOG="$TEMPLOG:n:$string" # Wish wish wish I could find a way to create a newline here
# If AppleJack log does not exist, or is greater than 500k, start new log
if [ ! -e "$LOGFILE" ] || [ `/usr/bin/du -k "$LOGFILE" | $AWK '{ print $1 }'` -gt 500 ];then
echo "Resetting $LOGFILE"
echo "****************** ${D}: NEW LOG STARTED ******************" > "$LOGFILE"
echo "" >> "$LOGFILE"
fi
# keep this on two separate lines so we can capture the line break
echo "$TEMPLOG" | $SED -e 's/:n:/ \
/g' >> "$LOGFILE"
# disable temp logging for future iterations
unset TEMPLOG
fi
else
# file system is not writeable, doing temp logging
string=`echo "$string" | $SED -e 's/\\\033\[[0-9]*m//g'` # strip color codes for log
TEMPLOG="$TEMPLOG $string"
fi
}
# Not implemented, but a way to test mounting in real time rather than with
# a status variable such as WRITEABLEROOT
# Snow Leopard: when mounted read-only, mount reports:
# root_device on / (hfs, local, read-only, journaled)
# when mounted read/write, it reports:
# /dev/disk0s2 on / (hfs, local, journaled)
# of course, one can always try to touch a file, like during the init of this
# script.
function writable() {
mount | while read -r line; do
hit=`echo $line | grep 'root_device' | grep 'read-only'`
if [ ! -z "$hit" ];then
loggit "root disk mounted read only"
return $EX_ERR
fi
done
loggit "root disk mounted read+write"
return $EX_OK
}
function mountem() {
if [ -z "$WRITEABLEROOT" ];then
WRITEABLEROOT=0
elif [ $WRITEABLEROOT -eq 1 ];then
loggit "Root file system already mounted. Continue."
return 0
fi
loggit "Let's mount the startup file system for write access..."
if $MOUNT -vuw /;then
WRITEABLEROOT=1
if [ -f /etc/fstab ]; then
loggit "Mounting local filesystems in fstab"
$MOUNT -vat nonfs
fi
loggit "Done."
loggit -n "Checking for /tmp directory: "
if [ -d "/tmp" ];then
loggit "/tmp directory exists."
else
loggit "${bW}/tmp directory does not exist${eW}"
if [ ! -d "/private/tmp" ];then
loggit "${bW}/private/tmp does not exist either${eW}"
loggit -n "Creating /private/tmp: "
$MKDIR -v "/private/tmp" | $TEE -a "$LOGFILE"
loggit -n "Setting correct permissions: "
$CHMOD -v 1777 "/private/tmp" | $TEE -a "$LOGFILE"
fi
loggit -n "Creating symbolic link from /tmp to /private/tmp: "
$LN -sv "/private/tmp" "/tmp" | $TEE -a "$LOGFILE"
if [ -d "/tmp" ];then
loggit "All set."
SANDBOX="/tmp"
cd "$SANDBOX"
else
loggit "${bW}Could not create /tmp directory. Something seems to be wrong "
loggit "with your file system. Proceed with caution${eW}"
fi
fi
loggit "Done."
else
loggit "${bE}Root file system could not be mounted. Script must quit. ${eE}"
exit $EX_OSERR
fi
echo ""
return 0
}
function services() {
if [ -z "$SERVICES" ];then
SERVICES=0 # Start assuming services have not been loaded
elif [ "$SERVICES" -eq 1 ];then
loggit "- All supporting services appear to be loaded."
return 0 # return success: services have been started
fi
# otherwise, check
case "$ANIMAL" in
4)
cs="diskarbitrationd configd memberd notifyd securityd lookupd DirectoryService"
;;
5)
cs="launchd notifyd configd syslogd distnoted DirectoryService diskarbitrationd kdcmond KernelEventAgent securityd"
;;
6)
cs="launchd notifyd configd syslogd distnoted DirectoryService diskarbitrationd KernelEventAgent securityd"
;;
esac
pss=`ps -axco command`
m=0
ct=0
for c in $cs;do
let "ct=$ct+1"
for ps in $pss;do
if [ "$c" = "$ps" ];then
let "m=$m+1"
break
fi
done
done
if [ $ct -eq $m ];then
SERVICES=1
loggit "- All supporting services appear to be loaded."
return $EX_OK # return true
fi
loggit "- All supporting services are not loaded."
return $EX_ERR # return false
}
function startServices() {
if services; then
return 0
fi
# make sure file system is mounted for read/write access
mountem
case "$ANIMAL" in
4 )
loggit "Configuring minimal Tiger services..."
(
SafeBoot='-x'
export -n SafeBoot
# Create mach symbol file
sysctl -n kern.symfile
if [ -f /mach.sym ]; then
ln -sf /mach.sym /mach
else
ln -sf /mach_kernel /mach
fi
echo "Configuring kernel extensions for safe boot"
touch /private/tmp/.SafeBoot
# $KEXTD -x
# c=don't use repositories, -v 1 =quiet, print only errors
# x=run in safe boot mode
$KEXTD -c -v 1 -x
echo "Loading basic launchd services..."
(
launchctl load /System/Library/LaunchDaemons/com.apple.syslogd.plist
launchctl load /etc/mach_init.d/notifyd.plist
wait
syslog -c 0 -p
# throw syslog output away from the screen
syslog -x /dev/null >/dev/null
wait
for plist in configd coreservicesd DirectoryService diskarbitrationd distnoted hdiejectd kuncd lookupd mds memberd notifyd scsid securityd translated; do
launchctl load /etc/mach_init.d/${plist}.plist > /dev/null 2>&1 &
wait
done
for plist in com.apple.KernelEventAgent com.apple.nibindd; do
launchctl load /System/Library/LaunchDaemons/${plist}.plist > /dev/null 2>&1 &
wait
done
) 2>&1>/dev/null &
wait
) 2>&1>/dev/null &
wait
;;
5 )
loggit "Configuring minimal Leopard services..."
(
SafeBoot='-x'
export -n SafeBoot
echo "Configuring kernel extensions for safe boot"
touch /private/tmp/.SafeBoot
# launchd is handling kextd startup in Leopard
# but we can't configure it with the plist
# launchctl load /System/Library/LaunchDaemons/com.apple.kextd.plist
# $KEXTD -x
# c=don't use repositories, v 1 =quiet, print only errors
# x=run in safe boot mode
$KEXTD -c -v 1 -x
############################################################
# Many thanks to Steve Anthony for his substantial effort in
# figuring out the startup sequence for Leopard.
############################################################
echo "Loading basic launchd services..."
(
launchctl load /System/Library/LaunchDaemons/com.apple.notifyd.plist
launchctl load /System/Library/LaunchDaemons/com.apple.configd.plist
launchctl load /System/Library/LaunchDaemons/com.apple.syslogd.plist
wait
syslog -c 0 -p
# throw syslog output away from the screen
syslog -x /dev/null >/dev/null
wait
for plist in com.apple.coreservicesd com.apple.DirectoryServices com.apple.DirectoryServicesLocal com.apple.diskarbitrationd com.apple.kdcmond com.apple.distnoted com.apple.hdiejectd com.apple.notifyd com.apple.scsid com.apple.securityd com.apple.KernelEventAgent com.apple.installdb.system; do
launchctl load /System/Library/LaunchDaemons/${plist}.plist > /dev/null 2>&1 &
wait
done
) 2>&1>/dev/null &
wait
) 2>&1>/dev/null &
wait
;;
6 )
loggit "Configuring minimal Snow Leopard services..."
(
# Probably going overboard here with the environment variable
# AND the flag, but I can't see the logic of how it's being read
# down the line, so playing it safe.
SafeBoot='-x'
export -n SafeBoot
echo "Configuring kernel extensions for safe boot"
touch /private/tmp/.SafeBoot
# launchctl load /System/Library/LaunchDaemons/com.apple.kextd.plist
# launchd should be handling kextd startup in Leopard
# but we can't configure it with the plist
# c=don't use repositories, q=quiet, print only errors
# x=run in safe boot mode
$KEXTD -c -q -x
############################################################
# Many thanks to Steve Anthony for his substantial effort in
# figuring out the startup sequence for Snow Leopard.
############################################################
echo "Loading basic launchd services..."
(
launchctl load /System/Library/LaunchDaemons/com.apple.notifyd.plist
launchctl load /System/Library/LaunchDaemons/com.apple.configd.plist
launchctl load /System/Library/LaunchDaemons/com.apple.syslogd.plist
wait
syslog -c 0 -p
# throw syslog output away from the screen
syslog -x /dev/null >/dev/null
wait
for plist in com.apple.kuncd com.apple.KernelEventAgent com.apple.distnoted com.apple.aslmanager com.apple.DirectoryServices com.apple.DirectoryServicesLocal com.apple.coreservicesd com.apple.diskmanagementd com.apple.securityd com.apple.diskarbitrationd com.apple.fseventsd; do
launchctl load /System/Library/LaunchDaemons/${plist}.plist > /dev/null 2>&1 &
wait
done
) 2>&1>/dev/null &
wait
) 2>&1>/dev/null &
wait
;;
* )
;;
esac
echo ""
echo "Waiting for services to start..."
echo ""
sleep 15
# loggit "Done. -${exit_status}-"
loggit "Done."
SERVICES=1
loggit ""
echo ""
return
}
function progress() {
local process="$1"
local message="$2"
local sleeptime=$3
local mLength=`echo $message | wc -m`
let "mLength=(mLength-1)"
let "line=(67-mLength)"
local dot=0
echo -n "$message"
sleep $sleeptime
local ps=`ps -ax | $GREP "$process" | $GREP -v 'grep'`
while [ ! -z "$ps" ];do
if [ $dot -ge $line ];then
echo ""
echo -n "$message"
let "dot=0"
else
echo -n "."
let "dot=(dot+1)"
fi
sleep $sleeptime
ps=`ps -ax | $GREP "$process" | $GREP -v 'grep'`
done
}
function repairDisks() {
local drc # disk repair count
local exit_status
local taskDescription # task description
local options # options to pass to fsck
local redo # redo disk repair?
local speedup
loggit -n "Disk repair"
countDown
# set disk repair count if not passed
if [ -z $1 ];then
drc=0
fi
if [ $WRITEABLEROOT -ne 0 ];then
loggit "${bW}Root disk has already been mounted for write access${eW}"
echo "If you want to repair the disk, restart into single user mode and "
taskDescription=`echo ${TASKNAMES[1]} | $SED -e 's/\\\033\[[0-9]*m//g'`
echo "choose '$taskDescription' as your first task."
loggit "Disk repair aborted."
else
# if [ $JOURNALED -eq 1 ];then
# options=' -f'
# else
# options=''
# fi
# if /sbin/fsck -y${options};then
if /sbin/fsck -y -f;then
exit_status=$?
# No problems found
loggit "Success! Either your disk had no errors, or it was repaired "
loggit "successfully."
echo "(If you were prompted to restart, you can ignore that for now.)"
echo ""
loggit "Done with disk repairs -${exit_status}-"
else
exit_status=$?
let "drc=($drc+1)"
# Problems found
loggit "Some errors were found that were not repaired. You should "
loggit "attempt to repair the disk again. -${exit_status}-"
if [ $AUTO -gt 0 ];then
loggit -n "AppleJack will try to repair the disk again in $CANCELTIME seconds. [${bH}c${eH}ancel] ${bK}"
# Define different REPLY variable for different task
read -t $CANCELTIME REPLY[$AUTO] <&1
echo -en "${eK}"
if [ -z "${REPLY[$AUTO]}" ];then
# Block auto mode from running more than DRL disk repair
# attempts.
if [ $drc -ge $DRL ];then
echo ""
echo ""
loggit "AppleJack has attempted $drc disk repairs without success. ${bW}Apple's "
loggit "standard disk repair utility is having trouble making the necessary "
loggit "repairs${eW} You might want to consider using other tools such as Disk "
loggit "Warrior${bPar}tm${ePar} or TechTool Pro${bPar}tm${ePar}."
redo="n"
else
redo="y"
fi
else
redo="n"
fi
else
# Tell user to maybe give up if more than DRL attempts have been made.
if [ -z $speedup ];then
speedup=0
fi
let "manualLimit=($DRL + $speedup)"
if [ $drc -ge $manualLimit ];then
echo ""
echo -e "You have attempted $drc repairs without success. ${bW}Apple's standard disk "
echo -e "repair utility is having trouble fixing this disk${eW} You might want to "
echo "consider using other tools such as Disk Warrior${bPar}tm${ePar} or TechTool Pro${bPar}tm${ePar}."
# post warning more frequently now, but not every time
let "speedup=(($manualLimit / 2) +1)"
echo ""
fi
if getUserApproval "Repeat disk repair?";then
redo="y"
else
redo="n"
fi
fi
if [ "$redo" = "y" ]; then
repairDisks $drc
else
loggit "${bW}Disk repair aborted. The disk directory is not healthy${eW}"
echo "We recommend you repair the disk before doing anything else."
# only do this if in auto mode
if [ $AUTO -ne 0 ];then
echo -e "${bW}As a precaution, AppleJack will now exit automatic mode${eW}"
AUTO=0
fi
fi
fi
fi
}
function validatePreferences() {
# boolean(y/n): should we check user prefs
local checkUserPrefs
loggit -n "Validating preference files"
countDown
# Make sure disks are mounted and writable first
mountem
if [ -d '/private/etc/mach_init.d' ];then
loggit "Checking mach init preference files ${bPar}/etc/mach_init.d${ePar}: "
prefCheck '/private/etc/mach_init.d'
loggit "Done. -$?-"
loggit ""
fi
if [ -d '/private/var/db/SystemConfiguration' ];then
loggit "Checking system configuration files ${bPar}/var/db/SystemConfiguration${ePar}: "
prefCheck '/private/var/db/SystemConfiguration'
loggit "Done. -$?-"
loggit ""
fi
if [ -d '/private/var/root/Library/Preferences' ];then
loggit "Checking root preference files ${bPar}/var/root/Library/Preferences${ePar}: "
prefCheck '/private/var/root/Library/Preferences'
loggit "Done. -$?-"
loggit ""
fi
if [ -d '/Library/Preferences' ];then
loggit "Checking system preference files ${bPar}/Library/Preferences${ePar}: "
prefCheck '/Library/Preferences'
loggit "Done. -$?-"
loggit ""
fi
# Only run this option if auto mode is off.
if [ $AUTO -eq 0 ];then
if getUserApproval "Would you like to find and remove corrupted preference files for any specific user?";then
checkUserPrefs="y"
else
checkUserPrefs="n"
loggit "User preference files untouched."
fi
while [ "$checkUserPrefs" = "y" ];do
# This function sets the value for UD, user directory
chooseUserDirectory
# if UD is empty, act as if the user has changed their mind
if [ -z "$UD" ];then
checkUserPrefs='n'
else
if [ -d "$UD/Library/Preferences" ];then
loggit "Checking the preference files in $UD/Library/Preferences"
prefCheck "$UD/Library/Preferences"
loggit "Done. -$?-"
else
loggit "${bE}$UD/Library/Preferences does not exist!${eE}"
echo 'Skipping this task.'
fi
if ! getUserApproval 'Would you like to find and remove corrupted preference files for another user?'; then
checkUserPrefs='n'
fi
fi
done
fi
}
function prefCheck() {
local owd=`pwd`
local folder="$1"
local qFolder="$1 ${bPar}Corrupt${ePar}"
local badPrefs # list of bad preference files
local filesMoved # (int) how many preference files were moved?
local tBP # list of bad preference files (temporary)
local bp # name of bad preference file
local bpDir # path to the bad preference file
local rVal # (int) return value: either 0 or 1
cd "$folder"
if [ -z "$2" ];then
badPrefs=`find . -type f \( -name "*.plist" \) -print0 | xargs -0 $PLUTIL -s`
elif [ "$2" = "xml" ];then
badPrefs=`find . -type f \( -name "*.plist" -o -name "*.xml" \) -print0 | xargs -0 $PLUTIL -s`
fi
# This is kind of crummy to have to use the \x00 kluge, but I see no better
# way at present
badPrefs=`echo $badPrefs | $SED -e "s#:[^/]*#:#g" -e "s/ /\x00/g" -e "s/:/ /g"`
if [ "$badPrefs" != "" ];then
if [ ! -d "$qFolder" ]; then
$MKDIR "$qFolder" # Is there a better place for this?
$CHMOD 777 "$qFolder" # Don't want a user stuck with a folder they can't delete
# Is this a possible security problem? No, because all preferences folders
# are already readable by everyone [except /var/root/Library/Preferences]
fi
filesMoved=0
for tBP in $badPrefs;do
# Convert to usable form for processing. Remove any leading dot. (Usually
# only the first corrupt preference file will have this).
bp=`echo $tBP | $SED -e 's/\x00/\ /g' -e 's/&/\&/g' -e 's/^\.//'`
# Create path at which the preference file should live.
# TODO find a way to make this parse even if there are slashes in the filename
bpDir=`echo $bp | $SED -e 's/\/[^\/]*$//'`;
# No need to check for sym or hard links, as plutil correctly follows them
# to the source file
# checkAlias returns 1 if file is an alias; 0 if not.
if checkAlias ".$bp" ; then
loggit "Corrupt preference file: .$bp"
loggit "--> Moving to ${qFolder}${bpDir}"
if [ ! -d '${qFolder}${bpDir}' ];then
$MKDIR -p "${qFolder}${bpDir}"
fi
$MV ".$bp" "${qFolder}${bpDir}"
let "filesMoved=$filesMoved+1"
fi
done
echo ""
if [ $filesMoved -lt 1 ]; then
rVal=$EX_OK
# Since no corrupt files were moved, attempt to remove directory.
# This will fail in cases where files were moved on a previous run, in
# which case we just want it to fail silently, and leave the directory
# in place. This situation is currently only applicable in cases that
# involve aliases.
$RMDIR "$qFolder" 2>/dev/null
elif [ $filesMoved -eq 1 ]; then
loggit "One corrupt preference file was moved."
rVal=$EX_ERR
else
loggit "$filesMoved corrupt preference files were moved."
rVal=$EX_ERR
fi
# Return to old location
cd "$owd"
return $rVal
fi
}
function cleanupVM() {
loggit -n "Virtual memory cleanup"
countDown
mountem # Make sure disks are mounted first
if [ ! -d "$swapdir" ];then
loggit "The virtual memory directory $swapdir does not exist. Please "
loggit "ensure that a correct location has been specified in /etc/rc "
loggit "and that the virtual memory directory actually exists. "
loggit "Virtual memory cleanup aborted."
else
cd "$swapdir"
if [ $? -eq 0 ] && [ `pwd` = "$swapdir" ];then # double check this
loggit -n "Removing swap files: "
$RM -vfd "${swapdir}/"swapfile* | tee -a "$LOGFILE"
loggit "Done."
echo ""
# Should only be applicable to system versions >= 10.4
if [ -d "app_profile" ];then
cd "app_profile"
# remove app_profile contents
loggit -n "Removing VM working sets: "
$RM -vf "${swapdir}/app_profile/"*
loggit "Done."
echo ""
fi
# Should only be applicable to system versions >= 10.4
# TODO: move this to advanced mode?
# Only run this option if automode is off.
if [ $AUTO -eq 0 ] && [ -e "sleepimage" ];then
if getUserApproval "Would you like to delete your safe sleep image?";then
# remove sleepimage
loggit -n "Removing the safe sleep image: "
$RM -vf "${swapdir}/"sleepimage | tee -a "$LOGFILE"
loggit "Done."
echo ""
fi
fi
else
loggit "Could not change working directory to $swapdir."
loggit "Virtual memory cleanup has been aborted."
fi
cd "$SANDBOX"
fi
}
function cacheCleanup() {
local cf # cache file
local cleanUserCache # (boolean) should the user cache be cleaned [y/n]
local cachedir # (string) user cache directory
local keep_going # (boolean) continue processing directories? [0/1]
loggit -n "Cache file cleanup"
countDown
mountem # Make sure disks are mounted first
loggit -n "Removing system cache files: "
# -v option makes the rm command verbose
if [ -d "/System/Library/Caches" ];then
find /System/Library/Caches/* -exec $RM -Rvf {} \; 2>/dev/null | tee -a "$LOGFILE"
fi
if [ -d "/Library/Caches" ];then
if [ "$DEEP" -eq "1" ]; then
find /Library/Caches/* -exec $RM -Rvf {} \; 2>/dev/null | tee -a "$LOGFILE"
else
find /Library/Caches/* ! -name 'com.apple.LaunchServices*' ! -name 'com.apple.user*pictureCache.*' ! -name 'com.apple.dock.iconcache*' -exec $RM -Rvf {} \; 2>/dev/null | tee -a "$LOGFILE"
fi
fi
if [ -d "/private/var/root/Library/Caches" ];then
find "/private/var/root/Library/Caches/*" -exec $RM -Rvf {} \; 2>/dev/null | tee -a "$LOGFILE"
fi
for cf in "/private/var/db/volinfo.database" "/private/var/db/BootCache.playlist" "/private/var/db/prebindOnDemandBadFiles" "/System/Library/Extensions.kextcache" "/System/Library/Extensions.mkext"; do
if [ -f "$cf" ];then
$RM -fv "$cf" 2>/dev/null | tee -a "$LOGFILE"
fi
done
loggit "Done removing system cache files."
loggit ""
if [ $AUTO -eq 0 ];then # Only run this option if auto mode is off.
if getUserApproval "Would you like to find and remove known cache files for a specific user?";then
cleanUserCache="y"
else
cleanUserCache="n"
loggit "User cache files untouched."
fi
while [ "$cleanUserCache" = "y" ];do
chooseUserDirectory
cachedir="${UD}/Library/Caches"
if [ -d "$cachedir" ];then
cd "$cachedir"
if [ $? -eq 0 ] && [ `pwd` = "$cachedir" ];then # Double check we're in the right place
loggit "Removing files in $cachedir:"
if [ "$DEEP" -eq "1" ];then
$RM -Rvf "${cachedir}/"* 2>/dev/null | tee -a "$LOGFILE" # Silence error output on empty directories
else # Currently, the commands here are identical
$RM -Rvf "${cachedir}/"* 2>/dev/null | tee -a "$LOGFILE"
fi
keep_going=1
loggit "Done. -$?-"
else
loggit "Unable to switch to $cachedir for processing."
loggit "Directory appears to be invalid."
keep_going=0
fi
cd "$SANDBOX"
else
loggit "$cachedir does not exist or is not a valid directory."
keep_going=0
fi
if ! getUserApproval "Would you like to find and remove known cache files for another user?";then
cleanUserCache="n"
fi
done
fi
loggit "Done with cache file clean up task."
loggit ""
}
function fixPermissions() {
loggit -n "Permissions repair"
countDown
startServices # We need to start up supporting services for this
echo "Repairing permissions"
echo "${bPar}Depending on operating system, disk size, and processor speed this can"
echo "take up to 30 minutes. Please wait.${ePar}:"
progress diskutil "+" 5 &
diskutil repairPermissions / | tee -a "$LOGFILE"
wait
# TODO: Fix this exit status. This is bogus, since it's actually the result of
# the tee operation rather than the diskutil operation.
loggit "Permissions have been repaired. -$?-"
}
# Let's find out who this user is
# id requires launchd services to be running, use whoami instead
function identityCheck() {
if [ `whoami` != "root" ];then
return $EX_ERR # False [failed]
else
return $EX_OK # True [success]
fi
}
function restart() {
loggit -n "Restarting `hostname`"
countDown
loggit "$GOODBYE"
loggit ""
(reboot &)
}
function quitScript() {
local exeunt # How does the user want to exit
# If user specified automatic restart or shutdown at runtime, quit automatically.
case "$1" in
'restart')
exeunt='r'
;;
'shutdown')
exeunt='h'
;;
*) # Else, quit manually
echo ""
loggit "${eK}Exiting the script."
echo "If you have modified the disk at all, you should restart the computer "
echo "before continuing to work."
echo -en "Would you like to ${bH}r${eH}estart your computer, or s${bH}h${eH}ut down? ${bK}"
read exeunt
echo -en "${eK}"
;;
esac
# Clean up items go here
# Turn syslog master filter off, in case startServices changed it.
syslog -c 0 off
case "$exeunt" in
"r" | "R")
restart
exit $EX_OK
;;
"h" | "H")
shutDown
exit $EX_OK
;;
*)
loggit "$GOODBYE"
echo "${bPar}To restart your computer from the command line, just type 'reboot'${ePar}"
loggit ""
exit $EX_OK
;;
esac
}
function selectNext() {
ANS="NOT_NULL" # We set this to keep it from being null at the outset
echo ""
if [ -z "$AUTO" ];then #show this option only first time through.
echo -e "$BANNER"
echo ""
echo "Enter the associated number or letter to select the next task."
echo -e "It is ${bS}strongly${eS} recommended you do them in the order listed!"
echo ""
echo -e "[${bK}${TASKCODES[0]}${eK}] ${TASKNAMES[0]}. AppleJack will do all tasks sequentially."
echo ""
AUTO=0 # and set AUTO, so it won't show again
elif [ $AUTO -eq 0 ];then # Only show this if not first time, and not in auto mode.
echo -e "$BANNER"
echo ""
echo "Choose the next task..."
echo ""
fi
if [ $AUTO -eq 0 ];then # Only show menu if Auto is not running
# Really wish bash supported multi-dimensional arrays
I=1
let "TASKLIST=${#TASKS[@]}-1"
while [ "$I" -lt "$TASKLIST" ];do # We want to list quit option in a separate format
echo -e "[${bK}$I${eK}] ${TASKNAMES[$I]}";
let "I=$I+1"
done
echo ""
# echo -e "[${bK}X${eK}] : E${bK}x${eK}pert Tasks."
echo ""
echo -en "Your choice ${bPar}Just hit return to quit${ePar}: ${bK}"
read ANS
echo -en "${eK}"
else # AUTO MODE
ANS=$AUTO
echo ""
let "AUTO=$AUTO+1" #increment AUTO
if [ $AUTO -gt 1 ];then # Only give these options if returning to the menu from another task.
if [ "$AUTO" -eq "${#TASKS[@]}" ];then # We're at the last item, which is quit
# If user specified automatic restart or shutdown at runtime, quit using their choice.
echo "AppleJack has finished.";
quitScript "$POSTSCRIPT"
else
echo "AppleJack auto mode: selecting task $ANS"
taskDescription=`echo ${TASKNAMES[$ANS]} | $SED -e 's/\\\033\[[0-9]*m//g'`
loggit "AppleJack will ${bS}${taskDescription}${eS} in $CANCELTIME seconds. "
echo -en "[${bH}s${eH}kip this task/${bH}q${eH}uit AppleJack]${bK} "
read -t $CANCELTIME RESP[$AUTO] <&1 # Set different reply depending on which step is being done.
echo -ne "${eK}"
if [ ! -z "${RESP[$AUTO]}" ];then # Guess I could also just reset the variable to null each time.
case "${RESP[$AUTO]}" in
"s" | "S")
echo ""
loggit "${TASKNAMES[$ANS]} skipped."
selectNext
;;
*) # Set to null so we quit later.
ANS=""
;;
esac
fi
fi
fi
echo ""
fi
if [ ! -z "$ANS" ];then # If user doesn't want to quit, continue
I=0
let "TASKCOUNT=${#TASKNAMES[@]}"
while [ "$I" -lt "$TASKCOUNT" ];do
if [[ "$ANS" = "${TASKCODES[$I]}" || "$ANS" = "$I" || "$ANS" = "${TASKCODESUC[$I]}" ]];then
eval "${TASKS[$I]}" # Run associated task
selectNext
fi
let "I=$I+1"
done
# If you're here, you're probably wanting the expert menu, or you typed a
# wrong key
if [[ "$ANS" = "x" || "$ANS" = "X" ]]; then
expertMenu
else
echo "Ooops! Looks like you typed the wrong key."
selectNext
fi
else # Let user quit, if they want to
quitScript
fi
}
function shutDown() {
loggit -n "Shutting down `hostname`"
countDown
loggit "$GOODBYE"
loggit ""
(shutdown -h now &)
}
function uninstall() {
if getUserApproval "You are about to uninstall AppleJack. Are you sure?";then
loggit "*********************** Uninstalling AppleJack ***********************"
mountem
# get script name, from expanded alias, just in case it's been modified.
# thisAJ=`echo $0 | sed -e 's#^\.##'`
local thisAJ="$0" # (string) location of this script
local defaultAJ='/private/var/root/Library/Scripts/applejack.sh' # (string) the default location of this script
# add current working directory
local rootProfile='/private/var/root/.profile' # (string) location of the root profile
local manPage='/usr/share/man/man8/applejack.8' # (string) default location of the AppleJack man page
local tmp='/private/tmp/applejack.install' # location of temp file for uninstalling AppleJack
# Set exit code to ok, to begin with
local uEC=$EX_OK # eventual exit code to be returned
if [ -e "$thisAJ" ];then
loggit -n "found AppleJack script. Removing: "
$RM -fv "$thisAJ" | tee -a "$LOGFILE"
echo ""
else
loggit "could not find the invoked AppleJack script ${bPar}$thisAJ${ePar}."
loggit "will try default location ${bPar}$defaultAJ${ePar}..."
if [ -e "$defaultAJ" ];then
loggit -n "found! Removing: "
$RM -fv "$defaultAJ" | tee -a "$LOGFILE"
else
loggit "Not found! AppleJack script could not be removed."
uEC=$EX_USAGE # user error
fi
fi
loggit "Removing AppleJack Documentation:"
if [ -d "/Library/Documentation/AppleJack" ];then
cd "/Library/Documentation"
loggit -n "-removing documentation from /Library/Documentation: "
$RM -Rfv "AppleJack" | tee -a "$LOGFILE"
cd "$SANDBOX"
fi
loggit -n "-removing AppleJack man caches: "
find /usr/share/man -name 'applejack.*.gz' -exec $RM -v {} \; | tee -a "$LOGFILE"
loggit ""
loggit -n "-removing AppleJack man pages: "
find /usr -type f \( -name 'applejack.8' -o -name 'applejack.1' \) -exec $RM -fv {} \; | tee -a "$LOGFILE"
loggit ""
loggit "Done."
if [ -d "/Library/Receipts/AppleJack.pkg" ];then
loggit -n "Deleting the installer receipt: "
cd "/Library/Receipts"
$RM -Rfv "AppleJack.pkg" | tee -a "$LOGFILE"
cd $SANDBOX
loggit "Done."
fi
if [ -f "$rootProfile" ];then
loggit -n "Restoring the root profile: "
$SED -e '/[aA]pple[jJ]ack/d' "$rootProfile" > "$tmp"
$RM -fv "$rootProfile"
$MV -v "$tmp" "$rootProfile" | tee -a "$LOGFILE"
loggit "Done."
fi
if [ "$uEC" -ne "$EX_OK" ]; then
loggit "AppleJack was not completely uninstalled. The AppleJack program could"
loggit "not be found. Sorry."
else
loggit "AppleJack is uninstalled."
fi
loggit "$GOODBYE"
exit $uEC
else
loggit "Uninstall aborted. AppleJack will now quit."
exit $EX_OK;
fi
}
########################################################################
# EXPERT FUNCTIONS UNDER DEVELOPMENT #################################
########################################################################
function xBlessDrive() {
mountem # Make sure disks are available for r/w
loggit "Bless Drive"
# countDown 5
# sets global variable $blessthis
if ! _xPickSystemFolder; then
return $EX_ERR
fi
loggit "Blessing $blessthis"
if getUserApproval "Would you also like to start up from $blessthis on restart?";then
# Set current startup disk to <disk>.
# systemsetup -setstartupdisk <disk>
local sb='--setBoot'
local fb="bless and startup from"
local fbs="blessed and can be used at restart."
else
local sb=''
local fb="bless"
local fbs="blessed"
fi
# local out=`$BLESS -folder9 "$os9sf" "$use9" 2>&1`
# local out=`$BLESS --folder "$blessthis" --bootinfo --bootefi $sb --verbose`
local out=`$BLESS --folder "$blessthis" $sb`
local res=$?
if [ "$res" -ne "0" ];then
loggit "Could not $fb $blessthis because: "
loggit "$out"
else
loggit "System folder $fbs. $out"
fi
loggit "Done."
}
function _xPickSystemFolder() {
local folderToBless # (string) The path of the folder we want to bless
local device
local nom
local index=0
local ANS
local a
local chosen
mountem
startServices
echo "- Loading information for attached disk(s)..."
# get the device names with Apple_HFS partitions, and remember them
if [ -z "$devices" ] || [ -z "$device_names" ];then
for device in `diskutil list | awk '/Apple_HFS/ { print $NF }'`;do
let "index=$index+1"
# grab the volume name in an accurate way, even if spaces or odd chars
nom=`diskutil info $device | grep 'Volume Name:' | cut -f 2 -d ':' | sed 's/^ *//'`;
device_names[$index]=$nom
devices[$index]=$device
done
fi
for ((a=1; a <= $index; a++));do
echo -e "[${bK}$a${eK}] ${device_names[$a]} on ${devices[$a]}"
done
while [ -z "$chosen" ];do
echo -en "Enter the number of the disk you want to bless: ${bK}"
read ANS
echo -en "${eK}"
if [ -z "$ANS" ];then
echo "Looks like you have changed your mind. Giving up."
return $EX_USAGE
fi
for ((a=1; a <= $index; a++));do
if [ "$ANS" = "$a" ];then
chosen=$ANS
break
fi
done
if [ -z "$chosen" ];then
echo "$ANS is not in the list of disks. Try again."
fi
done
echo "You chose ${device_names[$chosen]} on ${devices[$chosen]}"
if ! mount | $GREP "${devices[$chosen]} on" >/dev/null;then
echo -n "/dev/${devices[$chosen]} is not mounted. Mounting..."
# mount the device
diskutil mount /dev/${devices[$chosen]}
echo " Done."
fi
# we can't be sure there was no whitespace in the disk name beginning or end, so
folderToBless=`$LS -d /Volumes/*/System/Library/CoreServices | grep "${device_names[$chosen]}"`
if [ ! -d "$folderToBless" ]; then
# if [ -d "/Volumes/${device_names[$chosen]}/System/Library/CoreServices" ];then
echo ""
echo "There is no system folder on ${device_names[$chosen]}. Please choose another device."
echo ""
if ! _xPickSystemFolder;then
return $EX_USAGE
fi
fi
blessthis="$folderToBless" # blessthis is the global variable to set
return $EX_OK
# (
# IFS=$'\n'
# index=0
# sysfolders=()
# echo "Searching for available system folders..."
# sys=($(find / -maxdepth 3 -type d -name "System" 2>/dev/null ))
# sysindex=${#sys[@]}
# for (( i=0; i<${sysindex};i++)); do
# if [ -d "${sys[$i]}/Library/CoreServices" ];then
# let "index=index+1"
# echo "[${bK}${index}${eK}] ${sys[$i]}/Library/CoreServices"
# sysfolders[$index]="${sys[$i]}/Library/CoreServices"
# fi
# done
# echo ">${sysfolders[0]}"
# echo ">${sysfolders[1]}"
# )
}
# This will remove cached items at the machine and system level, with the option
# to do the same at the user level
function xDeepCacheClean() {
echo 'Choosing this option allows you to perform a deep cache clean mode
without having to run AppleJack in auto mode. Deep cache clean differs
from the normal cache clean in that it also removes the user icon
caches and the launch services database in /Library/Caches.
'
if getUserApproval 'Proceed with deep cache cleaning?';then
loggit "Deep cache clean. ${bPar}Also available in AUTO mode.${ePar}"
DEEP=1 # global variable
cacheCleanup
fi
}
function xDisableAutoLogin() {
echo 'Disabling auto login forces all users to log in before accessing their
accounts. This can be useful if the computer crashes or hangs during
an automatic login to a user account.'
if getUserApproval 'Are you sure you want to disable auto login?';then
mountem # Make sure disks are available for r/w
loggit -n "Disabling auto login"
countDown 5
local wasEnabled=0
local xDAL_SUCCESS=1 # Assume error
local eMsg=''
local xDAL_steps
case "$ANIMAL" in
[4-6]* ) xDAL_steps=0
if defaults read /Library/Preferences/com.apple.loginwindow autoLoginUser &> /dev/null;then
xDAL_steps=1
wasEnabled=1
if defaults delete /Library/Preferences/com.apple.loginwindow autoLoginUser >>"$LOGFILE" 2>&1;then
let "xDAL_steps=($xDAL_steps-1)"
fi
fi
if defaults read /Library/Preferences/com.apple.loginwindow autoLoginUserUID &> /dev/null;then
wasEnabled=1
let "xDAL_steps=($xDAL_steps+1)"
if defaults delete /Library/Preferences/com.apple.loginwindow autoLoginUserUID >>"$LOGFILE" 2>&1;then
let "xDAL_steps=($xDAL_steps-1)"
fi
fi
if [ $xDAL_steps -eq 0 ];then
xDAL_SUCCESS=$EX_OK
else
eMsg='Could not delete the autoLoginUser and autoLoginUserUID keys from com.apple.loginwindow.plist'
fi;;
* ) echo 'uh oh. Unsupported OS version.';;
esac
if [ $wasEnabled -eq 0 ];then
loggit "Auto login is not enabled. No need to disable it."
else
if [ $xDAL_SUCCESS -eq $EX_OK ];then
loggit "Auto login was successfully disabled."
else
loggit "Error while disabling auto login: $eMsg"
fi
loggit "Done -$xDAL_SUCCESS-"
fi
else
loggit "Disabling of auto login aborted."
xDAL_SUCCESS=$EX_OK # There is no error here, just an exit code
fi
}
function xDisableNFSMounts() {
echo 'Sometimes a recalcitrant NFS mount in NetInfo will keep Mac OS X from
booting. If you have NFS mounts in NetInfo, and you suspect they might
be causing your computer to not boot, this option might fix that by
moving the /mounts directory to /disabled/mounts.
(If there is enough interest, I might expand this function to also include
NFS mounts in /etc/fstab.)
'
if getUserApproval 'Do you wish to continue?';then
mountem root # Make sure disks are available for r/w
loggit -n "Disabling NFS mounts"
countDown 5
local xDNFSM_result # exit status of this function
$DSCL -raw "$DSDB" create disabled
$DSCL -raw "$DSDB" move mounts disabled
local ES=$?
if [ $ES -eq 0 ];then
xDNFSM_result=$EX_OK
loggit "Success! The netinfo path /mounts has been moved to /disabled/mounts."
else
xDNFSM_result=$EX_SOFTWARE
loggit "Failed! Could not modify move the netinfo /mounts path to /disabled."
fi
loggit "Done -$xDNFSM_result-"
fi
}
function xDisableSysConfigFiles() {
echo '
Sometimes, the system configuration files will keep the computer from
booting. Generally, this will be the case if one or more of them are
corrupted, but it can also happen if something has been misconfigured.
Generally, AppleJack recommends you first do a preference file check
before running this routine, which will move only corrupted preference
files out of the way. This function is more blunt. It will simply rename
the various system configuration files and directories by appending
".old" to the file name or directory name, thus effectively silencing
them without destroying them. You can restore the files by moving them
back to their original name.
'
if getUserApproval 'Would you like to disable your current system configuration files?';then
mountem root
loggit -n "Disabling system configuration files"
countDown 5
case "$ANIMAL" in
4)
loggit "Moving system configuration files from /Library/Preferences/SystemConfiguration to /Library/Preferences/SystemConfiguration.old";
$MV -v /Library/Preferences/SystemConfiguration /Library/Preferences/SystemConfiguration.old | tee -a "$LOGFILE";
$MKDIR -v -m 0755 /Library/Preferences/SystemConfiguration | tee -a "$LOGFILE"
;;
5)
loggit "Moving system configuration files from /Library/Preferences/SystemConfiguration to /Library/Preferences/SystemConfiguration.old";
$MV -v /Library/Preferences/SystemConfiguration /Library/Preferences/SystemConfiguration.old | tee -a "$LOGFILE";
$MKDIR -v -m 0755 /Library/Preferences/SystemConfiguration | tee -a "$LOGFILE"
;;
6)
loggit "Moving system configuration files from /Library/Preferences/SystemConfiguration to /Library/Preferences/SystemConfiguration.old";
$MV -v /Library/Preferences/SystemConfiguration /Library/Preferences/SystemConfiguration.old | tee -a "$LOGFILE";
$MKDIR -v -m 0755 /Library/Preferences/SystemConfiguration | tee -a "$LOGFILE"
;;
*)
echo "oh oh. unsupported operating system version."
;;
esac
loggit 'Done.'
fi
}
function xDisableUserLoginItems() {
echo '
If you suspect the login items in a user account is causing the computer
to hang or crash, you can use this function to disable the login items.
This function will first make a backup of the
~/Library/Preferences/loginwindow.plist file, and then delete the
AutoLaunchedApplicationDictionary key from the original.
'
if getUserApproval 'Do you wish to disable the login items for a user account?';then
local xDULI_success # the exit status of this function
mountem root
loggit -n "Disable login items for which user?"
echo ""
chooseUserDirectory # This function sets the value for UD, user directory
countDown 5
loggit "Making backup file at $UD/Library/Preferences/loginwindow.plist.old"
$CP -vp "$UD/Library/Preferences/loginwindow.plist" "$UD/Library/Preferences/loginwindow.plist.old"
loggit "Disabling login items in $UD"
# First, find out the user id of the file
# old panther/jaguar version of finding the uid of the file (no stat command)
# local funame=`ls -l "$UD/Library/Preferences/loginwindow.plist" | awk '{ print $3 }'`
#local fuid=`id -u "$funame"`
fuid=`$STAT -f "%u" "$UD/Library/Preferences/loginwindow.plist"`
# do the deed, which happens to set the file ownership to root
if defaults delete "$UD/Library/Preferences/loginwindow" "AutoLaunchedApplicationDictionary" >>"$LOGFILE" 2>&1;then
loggit -n 'Succesfully deleted the login items.'
xDULI_success=$EX_OK
else
loggit -n 'There was an error deleting the login items.'
xDULI_success=$EX_SOFTWARE
fi
# restore owner
$CHOWN $fuid "$UD/Library/Preferences/loginwindow.plist"
loggit " Done -$xDULI_success-"
fi
}
function xEnableNewSetup() {
echo '
Enabling a new system setup will remove all user account information
from NetInfo, and prepare the computer to run the initial setup program
that runs when you first buy a Mac. When you restart your computer
after running this function, you will be greeted by the Apple User Setup
program which will allow to configure your computer from scratch.
Please note: User home folders are NOT removed. You will need to remove
them manually later.
'
if getUserApproval 'Do you wish to prepare your computer for new setup?';then
mountem root # Make sure disks are available for r/w
loggit -n "Enabling new system setup"
countDown 5
if [ -d "$DSDB" ];then
loggit -n "Moving the old netinfo database out of the way: "
$MV -v "$DSDB" "${DSDB}.old" | tee -a "$LOGFILE"
else
loggit "The netinfo database could not be found."
fi
if [ -f /private/var/db/.AppleSetupDone ]; then
loggit -n "Moving the Apple setup file: "
$MV -v /private/var/db/.AppleSetupDone /private/var/db/.AppleSetupDone.old | tee -a "$LOGFILE"
if [ -f /private/var/db/.RunLanguageChooserToo ]; then
loggit -n "Moving the language chooser flag file: "
$MV -v /private/var/db/.RunLanguageChooserToo /private/var/db/.RunLanguageChooserToo.old | tee -a "$LOGFILE"
fi
else
loggit "The Apple setup file could not be found."
fi
if [ -f /Library/Preferences/SystemConfiguration/preferences.plist ]; then
loggit -n "Moving the system preferences file: "
$MV -v /Library/Preferences/SystemConfiguration/preferences.plist /Library/Preferences/SystemConfiguration/preferences.plist.old | tee -a "$LOGFILE"
else
loggit "The system preferences file could not be found."
fi
loggit "Done. Your computer will initiate the setup process on restart."
fi
}
# TODO: Shouldn't this be done before all services are started up?
# you could do this like this: modify /etc/rc.local to search /tmp/applejack/
# for any scripts. If any are found, they would be executed and then deleted.
# For example, you could write something like this:
# /usr/local/sbin/memtest all 1 -L
# and put it in /tmp/memtest.ajscript
# Then, in rc.local or /var/root/.profile, you would do this:
# for script in /tmp/*.ajscript;do
# sh $script;done
# rm /tmp/*.ajscript
# done
function xMemTest() {
local PATH="$PATH:/usr/local/sbin:/usr/local/bin:/Applications/memtest"
echo '
To run the memory test, you must either have opted to install the
memtest os x program during the AppleJack installation, or already
have it installed in one of these locations:'
echo "$PATH"
echo '
AppleJack will run all available tests three times, testing all
available memory. If you want to maximize the amount of memory tested,
run this test before you do anything else in AppleJack. (Yeah, I suppose
it is a little late for that now, but at least now you know.)
'
if getUserApproval 'Do you want to proceed with the memory test?';then
# Add common install locations for memtest to the path
local mt=`which memtest | $GREP -e '^/'`;
local exit_status # exit status of this function
if [ -z "$mt" ];then
loggit "Could not find the required memtest program in any of these locations:"
loggit "$PATH"
echo ""
echo "Are you sure it is installed?"
exit_status=$EX_USAGE
else
if [ -x "$mt" ];then
# Find out if we have a version capable of logging. Minimum version is 4.14
local mtvers=`"$mt" -v 2>/dev/null | grep 'Memtest version' | awk '{ print $3 }'`
local logable=`echo "$mtvers 4.14" | awk '{if ($1 >= $2) print 1; else print 0}'`
local logopt=''
if [ "$logable" -eq "1" ];then
if [ $WRITEABLEROOT -ne 1 ] && getUserApproval 'If you want a log of test results, we need to mount the root drive for writing. Do you want to do this?';then
mountem
fi
if [ $WRITEABLEROOT -eq 1 ];then
logopt='-L'
fi
fi
# if [ $WRITEABLEROOT -eq 1 ] && [ "$logable" -eq "1" ];then logopt='-L';else logopt='';fi
loggit "Running memory test using $mt all 3 $logopt"
countDown 5
"$mt" all 3 "$logopt"
exit_status=$?
if [ ! -z "$logopt" ]; then cat "memtest.log" >> "$LOGFILE";$RM memtest.log;fi
if [ "$exit_status" = "0" ];then
loggit "Memory test was successfull. Your RAM is probably fine."
else
loggit "Memory test gave an error."
fi
else
loggit "Found the memtest program at $mt, but it is not executable."
exit_status=$EX_USAGE
fi
fi
loggit "Done. -${exit_status}-"
fi
}
# See Apple's documentation: http://docs.info.apple.com/article.html?artnum=107210
function xRestoreNetinfoFromBackup() {
echo "This function is not implemented yet."
return
local exit_status # exit status of this function
mountem root # Make sure disk is available for r/w
if [ -f "$DSDBdump" ];then
# move old database out of the way
# load backup db
echo "loading backup db"
else
echo "get user approval"
# if getUserApproval 'The old netinfo database could not be found. Would you like to create a new one?';then
# fi
fi
if [ -d "$DSDB" ];then
loggit -n "Moving the old netinfo database out of the way: "
$MV -v "$DSDB" "${DSDB}.bad" | tee -a "$LOGFILE"
else
echo "something else"
fi
if [ ! -f "$DSDBdump" ];then
loggit "The directory services database backup ${bPar}${DSDBdump}${ePar} could not be found!"
loggit "AppleJack cannot continue with this procedure."
else
/usr/libexec/create_nidb
case "$ANIMAL" in
4)
cd /var/db/netinfo
/usr/sbin/netinfod -s local
# sh /etc/rc
;;
esac
/usr/bin/niload -v -d -r -t / localhost/local < "$DSDBdump" | tee -a "$LOGFILE"
exit_status=$?
cd "$SANDBOX" # Get back to the sandbox if we moved away from it
loggit "Done -${exit_status}-"
fi
}
function xSmartCheck() {
local index
local status
local disks
# Set a warning flag to 0 - everything OK
local ret=$EX_OK
# First get the list of devices
disks=($(diskutil list | $GREP ^\/))
# now loop through each device and test to see if it's SMART enabled,
# and verified
for ((index=0 ; index < ${#disks[*]} ; index++ )); do
echo
echo "Results for ${disks[index]}:"
df -h | grep ${disks[index]} | sed -e 's/ */ /' -e 's/^/ /'
status=$(diskutil info ${disks[index]} | $AWK '/SMART/ { print $3$4 }')
case "$status" in
'Verified')
loggit " SMART Status: $status"
;;
'NotSupported')
loggit ' SMART not supported on this device '
echo ' (generally only supported on IDE hard drives)'
;;
*)
# set the warning for the exit status
loggit -e "${bW}***A SMART problem was reported with ${disks[index]}***${eW}"
echo
ret=$EX_ERR
;;
esac
done
echo
if [ $ret -eq 0 ];then
loggit "No problems were found. -$ret-"
else
loggit "Problems were found. Backup your data on the problematic disk(s). -$ret-"
fi
return $ret
}
# Experimental development for advanced features.
function expertMenu() {
ANS="NOT_NULL" # We set this to keep it from being null at the outset
echo ""
echo -e "\033[4m AppleJack EXPERT Menu \033[0m"
echo "These functions are experimental at this stage. If you experience "
echo "any problems, please file a bug report at http://sf.net/projects/applejack"
echo ""
echo "Choose the next task..."
echo ""
I=0
let "xTASKLIST=${#xTASKS[@]}-1"
while [ "$I" -lt "$xTASKLIST" ];do
echo -e "[${bK}$I${eK}] ${xTASKNAMES[$I]}";
let "I=$I+1"
done
echo ""
echo ""
echo -en "Your choice ${bPar}Just hit return to quit${ePar}: ${bK}"
read ANS
echo -en "${eK}"
if [ ! -z "$ANS" ];then # If user doesn't want to quit, continue
I=0
let "xTASKCOUNT=${#xTASKNAMES[@]}"
while [ "$I" -lt "$xTASKCOUNT" ];do
if [[ "$ANS" = "${xTASKCODES[$I]}" || "$ANS" = "$I" || "$ANS" = "${xTASKCODESUC[$I]}" ]];then
eval "${xTASKS[$I]}" # Run associated task
expertMenu # Return to menu
fi
let "I=$I+1"
done
# If you're here, you're probably typed a wrong key
echo "Ooops! Looks like you typed the wrong key."
expertMenu
else # Let user quit, if they want to
quitScript
fi
}
########################################################################
# END: EXPERT FUNCTIONS UNDER DEVELOPMENT ############################
########################################################################
########################################################################
# START OF APPLEJACK RUNTIME #########################################
########################################################################
# Before doing anything, let's move our working directory to the safe sandbox.
# by running the script from the SANDBOX directory, we hope to minimize damage from any bugs.
if [ -d "/tmp" ];then
SANDBOX="/tmp"
elif [ -d "/private/var/tmp" ]; then
SANDBOX="/private/var/tmp"
else # In a pinch, we use this
SANDBOX="/Library/Caches"
fi
cd "$SANDBOX"
# Okay, first let's prepare for logging...
# touching the log file helps us see if file system is writable and
# therefore what mode of logging to start with.
if touch "$LOGFILE" &>/dev/null; then
# on success [exit status 0]
WRITEABLEROOT=1
else
WRITEABLEROOT=0
TEMPLOG=
fi
# How was script invoked?
# Check for invocation of script with command line parameter for running
# it automatically, for example, from another script.
showusage=0
optFeedback=
badopt=
POSTSCRIPT=
if [ ! -z "$1" ];then
case "$1" in
"auto" | "AUTO" )
optFeedback="Running in automatic mode"
AUTO=1
if [ "$1" = "AUTO" ];then
DEEP=1
optFeedback="$optFeedback, deep clean on"
fi
if [ ! -z "$2" ];then
# Let's test for reboot as well as restart, for compatibility reasons
if [ "$2" = "restart" ] || [ "$2" = "reboot" ];then
optFeedback="$optFeedback, with automatic restart"
POSTSCRIPT="restart"
elif [ "$2" = "shutdown" ];then
optFeedback="$optFeedback, with automatic shutdown"
POSTSCRIPT="shutdown"
else
# incorrect options specified
badopt="$2"
showusage=1
fi
else
POSTSCRIPT="manual"
optFeedback="$optFeedback, with manual exit"
fi;;
"uninstall" )
if identityCheck;then
uninstall
else
echo -e "${bE}You are not authorized as the root user. To uninstall, use sudo ${eE}"
echo -e "${bE}or run this script from single user mode. ${eE}"
exit $EX_USAGE
fi;;
"version" | "--version" | "-v" ) # @todo: document this option
echo "This is AppleJack $VERSION, $REVISION"
echo ""
exit $EX_OK;;
* )
# incorrect options specified
showusage=1
badopt="$1";;
esac
fi
if [ $showusage -eq 1 ];then
echo ''
echo "AppleJack $VERSION, $REVISION: Invalid option '$badopt'"
echo 'USAGE: applejack [auto|AUTO [restart|shutdown]]'
echo 'type `man applejack` for more details'
echo ''
exit $EX_USAGE
fi
loggit "**********************************************************************"
loggit "* AppleJack $VERSION, $REVISION *"
loggit "* Copyright (c) 2002-10 Kristofer Widholm, The Apotek *"
echo "* - AppleJack comes with ABSOLUTELY NO WARRANTY *"
echo "* - This is free software, and you are welcome to redistribute it *"
echo "* under certain conditions, as specified in the GPL LICENSE you *"
echo "* read during installation of this product: www.opensource.org *"
echo "* *"
echo "* USAGE ${bPar}interactive${ePar}: Just run through the tasks in the menu below, *"
echo "* in ascending order, and let AppleJack fix your machine. *"
echo "* USAGE ${bPar}automatic mode${ePar}: To start AppleJack in auto mode, type: *"
echo "* 'applejack auto' *"
echo "* 'applejack auto restart' (restarts computer when done) *"
echo "* 'applejack auto shutdown' (shuts down the computer when done) *"
echo "* To do a deep clean of the system, use AUTO instead of auto. *"
echo "* Please see the man page for details: \`man applejack\` *"
echo "* *"
echo "* Donations gratefully accepted at http://applejack.sourceforge.net *"
echo "**********************************************************************"
echo ""
D=`date`
loggit "$D. Gathering information..."
loggit "$optFeedback"
# Make sure the script is being run by root
if identityCheck;then
loggit -n "- User ID: ${IDENTITY[0]}, NAME: ${IDENTITY[1]}"
else
loggit "- User ID: ${IDENTITY[0]}, NAME: ${IDENTITY[1]}"
echo -e "${bE}You are not authorized as the root user. AppleJack must quit. ${eE}"
exit $EX_USAGE
fi
# Okay, are we in single user mode? In the past, before, Leopard, we could count on id to behave differently in single user mode vs loaded, but now the behavior is difficult to discern. So we'll use the rather crude method of counting lines of output from ps ax. If there's less than 20 lines, we're pretty sure we're in single user mode, even after having loaded our basic services
# SUM=`id | $GREP -E '[^0]\('` # In single user mode, SUM should be empty.
# if [ ! -z "$SUM" ];then
process_count=`ps ax | wc -l | sed 's/ //g'` # how many processes are running?
if [ $process_count -gt 25 ]; then
loggit ""
loggit "${bW}!!! WARNING: You are not running AppleJack in single user mode! !!${eW}"
loggit "${bW}!!! Certain tasks can cause your operating system to crash. !!${eW}"
loggit "${bW}!!! Proceed at your own risk. !!${eW}"
echo -e "(For Single User Mode: Press and hold ${bK}command${eK} and ${bK}s${eK} immediately after restart.)"
loggit ""
fi
# Let's find out Mac OS X version in order to start the right services.
OSV=`sw_vers 2>/dev/null | $AWK '/ProductVersion/ { print $2 }'`
loggit "- OS Version: $OSV"
# TODO: Look for directory services DB in standard location, and allow user to input their location if not found.
# Check for location of swapdir
swapdir=''
if [ -e /etc/rc ];then
vmsrc='/etc/rc'
swapdir=`$GREP -E "^[^#]*swapdir=[/\"']" /etc/rc | $SED -e 's/"//g' -e "s/'//g" | cut -f 2 -d =`
fi
if [ "$swapdir" = "" ] && [ -e '/System/Library/LaunchDaemons/com.apple.dynamic_pager.plist' ];then
vmsrc='dynamic_pager'
# Register the PA variable, for getting the VM location later
declare -arx PA=`defaults read /System/Library/LaunchDaemons/com.apple.dynamic_pager ProgramArguments`;
swapdir="${PA[2]}";
swapdir=`echo "$swapdir" | awk -F/ '{for (i=2; i<NF; i++) printf "/"$i}'`
fi
if [ ! "$swapdir" = "" ];then
loggit "- According to $vmsrc, virtual memory is located at $swapdir."
else
loggit "${bW}Unable to discover location of virtual memory directories${eW}"
echo 'Make sure $swapdir is declared in a standard way. As a temporary'
echo 'workaround, please enter the correct VM directory location at the '
echo 'prompt. Or just hit return to accept the default location '
echo -n "${bPar}/private/var/vm${ePar} instead. [enter directory]: "
read vmDir
if [ ! "$vmDir" = "" ];then
swapdir="$vmDir"
loggit "- Using $vmDir as the swap file location."
else
swapdir='/private/var/vm'
loggit '- Defaulting to /private/var/vm'
fi
fi
if [ ! -d "$swapdir" ];then
loggit "${bW}WARNING! Swap directory $swapdir does not appear to exist. "
echo -e "Proceed with caution${eW}"
fi
# Check if root file system is journaled
# (Issuing the mount command without parameters simply lists volumes)
if mount | $GREP 'on / (' | $GREP 'journal' 1>/dev/null;then
JOURNALED=1
loggit "- Local root filesystem is journaled"
else
JOURNALED=0
fi
# OS Specific Configuration
ANIMAL=`echo $OSV | cut -f 2 -d . `
case "$ANIMAL" in
4)
# Where is the netinfo database located?
DSDB='/private/var/db/netinfo/local.nidb'
# Where is the netinfo database backup located?
DSDBdump='/private/var/backups/local.nidump'
;;
5)
# Where is the netinfo database located?
DSDB="."
# Where is the netinfo database backup located?
# @todo: convert this to dscl info
DSDBdump="/private/var/backups/local.nidump"
;;
6)
# Where is the netinfo database located?
DSDB="."
# Where is the netinfo database backup located?
# @todo: convert this to dscl info
DSDBdump="/private/var/backups/local.nidump"
;;
*)
loggit "${bW}This version of AppleJack supports only Mac OS X versions 10.4.x and"
loggit "above. It's advisable to not use it unless you absolutely have to. For"
loggit "Mac OS X versions prior to 10.4.x, please use AppleJack version 1.4.3${eW}"
;;
esac
# TODO: Output warning for bad environment
if [ $BADENV -ne 0 ];then
loggit "${bW}WARNING: Some necessary commands could not be found in their standard locations.${eW}"
echo 'If you have installed custom binaries or otherwise modified the'
echo 'operating environment, you may experience problems.'
fi
########################################################################
# APPLEJACK READY; PRESENT MENU ######################################
########################################################################
selectNext
#Script should never reach this, but just in case...
bell
exit $EX_SOFTWARE
Be the first to comment
You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.