#!/bin/bash #Copyright 2004 William Stearns #Released under the GPL #V0.2 #Despite this app's best efforts, it's still a poster child for race #conditions. It really should only be run on a system in single user #mode, or at least on files you _know_ won't be touched. requireutil () { while [ -n "$1" ]; do if ! type -path "$1" >/dev/null 2>/dev/null ; then echo Missing utility "$1". Please install it. >&2 return 1 #False, app is not available. fi shift done return 0 #True, app is there. } #End of requireutil TFile () { mktemp -q "$BaseFile.XXXXXX" if [ $? -ne 0 ]; then echo Unable to make temporary file, exiting. >&2 exit 1 fi } MaxTries=6 if [ $EUID -ne 0 ]; then requireutil sudo || exit 1 SUDO=`which sudo` fi requireutil awk chmod chown dd df diff filefrag grep lsof ls mktemp mv rm sed seq touch || exit 1 BaseFile="$1" if [ -z "$BaseFile" -o -n "$2" ]; then echo Usage: echo $0 File_to_defragment echo You can only specify a single file, and it must not be in use. exit 1 fi if LsofOut="`lsof \"$BaseFile\" 2>&1`" ; then echo "$BaseFile is being held open:" echo "$LsofOut" echo 'Unable to continue on this file, exiting.' exit 1 fi #echo 'File is currently closed, good, continuing.' FileMegs=`ls -s --block-size=1048576 "$BaseFile" | awk '{print $1}'` FreeSpace=`df -m "$BaseFile" | grep -v '^Filesystem' | awk '{print $4}'` if [ $[ $FileMegs * $MaxTries + 20 ] -gt $FreeSpace ]; then echo "$BaseFile is ${FileMegs}M large, $FreeSpace free on that filesystem" echo "Making $MaxTries copies of $BaseFile would not leave 20M free. Exiting." exit 1 fi ExtentInfo=`$SUDO filefrag "$BaseFile" | sed -e 's/^.*: \([0-9]*\) extents* found, perfection would be \([0-9]*\) extents*$/\1 \2/' -e 's/^.*: \([0-9]*\) extents* found$/\1 \1/'` CurrentExtents=${ExtentInfo%% *} PerfectExtents=${ExtentInfo##* } echo "Current $CurrentExtents Perfect $PerfectExtents" if [ $CurrentExtents -eq $PerfectExtents ]; then echo "$BaseFile is at a perfect level, good. Exiting." exit 0 elif [ $CurrentExtents -lt $PerfectExtents ]; then echo "Hmmm, $Basefile claimed to have fewer extents than perfection, something is wrong. Exiting." exit 1 else echo "$BaseFile is fragmented, trying to defrag." BestPass=0 BestExtents=$CurrentExtents declare -a Try TryExtents for Pass in `seq 1 $MaxTries` ; do Try[$Pass]=`TFile` dd if="$BaseFile" of="${Try[$Pass]}" 2>/dev/null TryExtents[$Pass]=`$SUDO filefrag ${Try[$Pass]} | sed -e 's/^.*: //' -e 's/ extent.*//'` echo -n "${TryExtents[$Pass]} " if [ ${TryExtents[$Pass]} -lt $BestExtents ]; then BestPass=$Pass BestExtents=${TryExtents[$Pass]} fi done echo #Remove all but the best for Pass in `seq 1 $MaxTries` ; do if [ $Pass -ne $BestPass ]; then rm -f "${Try[$Pass]}" fi done if [ $BestPass = 0 ]; then echo "Couldn't do better than the original file, exiting." rm -f "${Try[$BestPass]}" exit 0 fi echo "Best pass was pass number $BestPass with ${TryExtents[$BestPass]} extents." touch --reference="$BaseFile" "${Try[$BestPass]}" || echo touch failed chmod --reference="$BaseFile" "${Try[$BestPass]}" || echo chmod failed chown --reference="$BaseFile" "${Try[$BestPass]}" || echo chown failed if ! diff -q "$BaseFile" "${Try[$BestPass]}" >/dev/null ; then echo "Warning - $BaseFile and ${Try[$BestPass]} differ, exiting" rm -f "${Try[$BestPass]}" exit 1 fi if LsofOut="`lsof \"${Try[$BestPass]}\" 2>&1`" ; then echo "Someone opened ${Try[$BestPass]}:" echo "$LsofOut" echo 'Unable to continue on $BaseFile, exiting.' rm -f "${Try[$BestPass]}" exit 1 fi if LsofOut="`lsof \"$BaseFile\" 2>&1`" ; then echo "Someone opened $BaseFile:" echo "$LsofOut" echo 'Unable to continue on this file, exiting.' rm -f "${Try[$BestPass]}" exit 1 fi #To completely replace the original echo "Succeeded, renaming ${Try[$BestPass]} to $BaseFile with no backup." rm "$BaseFile" mv "${Try[$BestPass]}" "$BaseFile" #For manual replacement #echo "Succeeded, please rename ${Try[$BestPass]} to $BaseFile." #To keep the original as a backup (untested) #echo "Succeeded, renaming ${Try[$BestPass]} to $BaseFile and keeping a backup of the original." #mv "$BaseFile" "`TFile`" #mv "${Try[$BestPass]}" "$BaseFile" fi