#!/usr/local/bin/bash
# See http://wiki.zimbra.com/wiki/Hard_links
# Program name: hardlinks
# 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 3 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, see
##########################################################################
# Title : hardlinks - list hardlinks or rebuild from list
# Author : Simon Blandford
# Date : 2008-07-31
# Requires : awk
# Category : Administration
# Version : 1.0.0
# Copyright : Simon Blandford, Onepoint Consulting Limited
# License : GPLv3 (see above)
##########################################################################
# Description
# Store hardlink information in a file which can then be used to
# remove hardlinks or restore the hardlinks.
# This is intended to be used with backup and archiving systems that do
# not support hardlinks. Simply store the hardlink information as part of
# the archive so that the hardlinks can be regenerated when the archive is
# unpacked.
# the -s option is used to suppress the progress count when purging or
# restoring links.
# the -p option supresses errors when a file in the list isn't found
##########################################################################
function usage()
{
echo "Usage: `basename $0` scan rootdir linklistfile"
echo " `basename $0` [-sp] exclude rootdir linklistfile excludelistfile"
echo " `basename $0` [-sp] purge rootdir linklistfile"
echo " `basename $0` [-sp] restore rootdir linklistfile"
echo "Options: -s no counter display"
echo " -p no error on file in list not found"
exit 0
}
function act_on_list()
{
if [ ! -d "$rootDir" ]; then
echo "Error: Can't find root directory: $rootDir." >&2
exit 1
fi
if [ ! -f "$linkListFile" ]; then
echo "Error: Unable to open input link list file: $linkListFile." >&2
exit 1
fi
[ "$1" == "exclude" ] && > "$excludeListFile"
totFiles=`wc -l $linkListFile | awk '{ print $1 }'`
while read -r currentEntry; do
inum=$( echo $currentEntry | awk '{ print $1 }' )
#Change file space escapes (\ ) to *, extract the filename by deleting up to last space
#then change the * back to spaces.
fileName=$( echo "$currentEntry" | awk '{ for (i=2; i<=NF; i++) printf (" %s",$i) }' )
#Remove leading space and . and prepend path
fileName="$rootDir""${fileName:2}"
if [ ! -f "$fileName" ]; then
if [ "$suppressNoFileErr" != "yes" ]; then
echo "Error: File $fileName can not be found in $currentEntry." >&2
exitCode=1
continue
fi
fi
fileSize=`ls -l "$fileName" | awk '{ print $5 }'`
sizeUnLinkedTotal=$(( $sizeUnLinkedTotal + $fileSize ))
if [ "$inum" != "$lastInum" ]; then
sizeLinkedTotal=$(( $sizeLinkedTotal + $fileSize ))
baseFile="$fileName"
else
[ $1 == "exclude" ] && echo "$fileName" >> "$excludeListFile"
[ $1 == "purge" ] && rm -f "$fileName"
[ $1 == "restore" ] && ln "$baseFile" "$fileName"
fi
lastInum=$inum
if [ $(( counter++ % 100 )) -eq 99 ] && [ "$noiseLevel" != "silent" ]; then
echo "Processed $counter files of $totFiles"
fi
done < "$linkListFile"
if [ "$noiseLevel" != "silent" ]; then
echo "Processed $counter files of $totFiles"
fi
}
while getopts hps c
do
case $c in
h)
usage
;;
s)
noiseLevel="silent"
;;
p)
suppressNoFileErr="yes"
;;
?)
usage
;;
esac
done
shift $(($OPTIND - 1))
rootDir="$2"
linkListFile="$3"
excludeListFile="$4"
exitCode=0
case $1 in
scan)
if [ $# -ne 3 ]; then usage; exit 1; fi
# find...-printf is linux-centric
#echo 'cd '"$rootDir"'; find . -type f -links +1 -printf "%i %h/%f\n" | sort' | bash > "$linkListFile"
# find...-exec ls -i "{}" + is more unix general, though + may need to be \;
echo 'cd '"$rootDir"'; find . -type f -links +1 -exec ls -i "{}" + | sort' | bash > "$linkListFile"
;;
exclude)
if [ $# -ne 4 ]; then usage; exit 1; fi
act_on_list $1
;;
purge|restore)
if [ $# -ne 3 ]; then usage; exit 1; fi
act_on_list $1
;;
*)
usage
;;
esac
exit $exitCode