#!/bin/sh
# /* 23.01.2005
# mapfiles.sh - map file permissions and user:group from one tree to another
# by xmb<@skilled.ch> - localhack
# V0.8 23.01.2005, 14.02.2005(minor changes/comments) */
# [ad] more funky pro awk and bash code @ [URL unfurl="true"]http://xmb.ath.cx/code[/URL]
# feel free to mail me about any comments, changes, or ideas
# about this, or anything else.. new weird scripting ideas welcome
# [/ad]
# print differencies in file system structures
# generate commands to fix those, printed to stdout and to log/other file
# was created cause someone called 'man' fucked up the user:group bits of his
# ibook running gentoo. this script 'mapped' the original ones from another
# system back, and all were happy
# such has had to be written once already anyway, by me at least
# temp file structure
# /tmp
# /tmp/mapfiles.log - log
# /tmp/mapfiles.log.USER - alternative logs
# /tmp/mapfiles.list - generated list
# /tmp/mapfiles.in.list - read this list when updating files
# /tmp/mapfiles.restore - generated restore commands from the last run
# /tmp/mapfiles.restore.RANDOM - backups of the generated commands - TODO
# Examples:
# $ ./mapfiles c /var /lib /usr # <creates /tmp/mapfiles.list>
# $ scp /tmp/mapfiles.list other_machine:/tmp/mapfiles.in.list
# on the other machine either
# $ ./mapfiles u /var /lib /usr # <creates /tmp/mapfiles.restore>
# or
# $ ./mapfiles u / # <creates /tmp/mapfiles.restore>
# second running longer, but both resulting the same, as only seen&&differ files
# get printed, along with a command to restore them
# for now, one command per line per changed file attribute
# show diffs on /lib only
# $ ./mapfiles u /lib
# common find(1) options, to be specified after specifying search paths or
# directly specified without any paths:
# -type f -type d -maxdepth <number> -group <group>
# -[i]name <name> -i[regex] <regex> -user <user>
### ## # -
#[[ $1 == [cudh]* ]] && do=$1 && shift
do=$1 ; shift
Path=( "$@" ) # paths being the scripts arguments
[ ! $list ] && list=/tmp/mapfiles.list
[ ! $in ] && in=/tmp/mapfiles.in.list
log=/tmp/mapfiles.log reflist=$list refin=$in
commands_out=/tmp/mapfiles.restore # where the to-fix/restore commands of the last
# script run will be stored
change=123 # 1 for chmod, 2 for user, 3 for group
# this is actually equivalent to the awk field gotten from _find output
commands="chmod chown chgrp" # representing the commands to be done for the fields
[ ! $awk ] && awk=awk
[ ! $Path ] && Path=( "$PWD" $search_paths $mapfiles_paths )
[ -s $list -a $list != $reflist ] && list=$list.$RANDOM # existing list already, dont overwrite
[ -f $log -a ! -w $log ] && log=/tmp/mapfiles.log.$USER # append $USER if the log cant be written
trap '_e aborting; _end 2' 2 # doesnt help when stopping the awk running command
_e() { echo -e "mapfiles: $@" | tee -a $log; }
_ee() { Xret=$1; shift; _e "$@"; _end $Xret; } # _e + _end wrapper, Xret name to not conflict
# with any vars, dunno if 'local' is portable
_find() { find "$@" -printf "%m %u %g\t%p\n"; }
_info() { _e "starting at.. $date1 - $date2"
_e "list = $list, log file = $log, paths =\e[1m ${Path[@]} \e[m"
[ $do_u ] && _e "restore commands log =\e[1m $commands_out \e[m"
_e
[ "$@" ] && _e "$@"
}
_err() { _e "$@"; _end 1; }
_end() { _zero $1 && x=success || x=failed; _e "$x; end: `date +%F//%T` - `date`"; exit $1; }
gimme_file() { for file; do [ -f "$file" ] && echo "$file" && return; done; return 1; }
_zero() { [ -z $1 -o $1 == 0 ] && return 0 || return $1; } # compatibility function to not use [[
### ## # -
date1=`date +%F//%T` date2=`date`
case $do in
-h*|h*|'') echo "Usage: mapfiles <c|create u|update d|diff h|help> [paths] [find args]"
echo " c) create index list"
echo " p) u) update the files depending on the list"
echo " d) show diffs with diff"
echo " D) better output than diff, TODO"
echo " l) show statistics about the last run, TODO"
echo " h) this help"
exit 1
;;
-c|c) _info "Creating files list"
_find "${Path[@]}" >$list
ret=$?
;;
-u|u|p) do_u=1 _info "Updating files from list"
[ ! -f $in ] && _e "No input list found ($in)" && _err "Copy one over or export "\
"the in variable pointing to one"
# the awk thing could be reused for other matchings, as
# $change kinda defines the fields
_find "${Path[@]}" |
$awk -v list="$in" -v cmds="$commands" -v c=$change -v out=$commands_out \
-v d1="$date1" -v d2="$date2" -v paths="`echo ${Path[@]}`" 'BEGIN {
split(c, C, ""); split(cmds, Cmd) # cmds = "chmod chown chgrp"
while ((getline < list) > 0) { file = getfile()
List[file] = 1; while (C[++x]) List[file, C[x]] = $C[x]; x = 0
} close(list) }
{ file = getfile(); if (! List[file]) next
while (C[++x]) if ($C[x] != List[file, C[x]]) p(cmd(C[x])); x = 0 }
function getfile() { match($0, FS $4); return substr($0, RSTART +1) }
function cmd(n) { if (! Z) { pre(); Z = 1 }
return Cmd[n] " " List[file, n] " \"" file "\" \t# " $n }
function p(str) { print str; print str >> out } # > or >>, same
function pre() { printf "" > out; p("# mapfiles.sh generated commands")
p("# " d1 " -- " d2 " -- paths run: " paths) }' |
tee -a $log
ret=$? ret=$PIPESTATUS
;;
-d|d) file=`gimme_file $diff $list $in $reflist $refin` || _err "Couldnt find any list"
_info "Showing differencies on $file"
_find "${Path[@]}" | diff - $file && _e "No diffs found"
;;
esac
### ## # -
[ -z $ret ] && _end 0
[ $ret == 130 ] && _ee $ret "probably killed awk"
[ $ret -gt 130 -o $ret -lt 130 ] && _ee $ret "unknown return code $ret"
_end $ret