You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
10 KiB
338 lines
10 KiB
#!/bin/sh |
|
## |
|
## lsync -- Access Layer Synchronization Tool |
|
## Copyright (c) 2000-2002 Ralf S. Engelschall <rse@engelschall.com> |
|
## |
|
## Permission to use, copy, modify, and distribute this software for |
|
## any purpose with or without fee is hereby granted, provided that |
|
## the above copyright notice and this permission notice appear in all |
|
## copies. |
|
## |
|
## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
|
## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
|
## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
|
## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR |
|
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
|
## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
|
## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
## SUCH DAMAGE. |
|
## |
|
|
|
## |
|
## filesystem hierarchy configuration |
|
## |
|
|
|
# program name and version/date |
|
progname="lsync" |
|
progvers="1.0.4" |
|
progdate="04-Aug-2001" |
|
|
|
# root directory |
|
# (if empty, .lsyncrc files provide default) |
|
root="" |
|
|
|
# subdirectory where packages are physically installed |
|
pkgdir="PKG" |
|
|
|
# subdirectories which are synchronized between physically |
|
# installed package areas and access layer |
|
subdirs="bin,sbin,man,info,include,lib" |
|
|
|
## |
|
## command line option parsing |
|
## |
|
|
|
# default run-time modes |
|
nop=0 |
|
quiet=0 |
|
trace=0 |
|
help='' |
|
init=0 |
|
uninstall=0 |
|
local=0 |
|
|
|
# be aware of .lsyncrc files |
|
cwd=`pwd` |
|
while [ 1 ]; do |
|
if [ -f "$cwd/.lsyncrc" ]; then |
|
set -- `cat $cwd/.lsyncrc` "$@" |
|
fi |
|
[ ".$cwd" = ./ ] && break |
|
cwd=`echo $cwd | sed -e 's;/[^/]*$;;' -e 's;^$;/;'` |
|
done |
|
if [ ".$HOME" != . -a -f "$HOME/.lsyncrc" ]; then |
|
set -- `cat $HOME/.lsyncrc` "$@" |
|
fi |
|
|
|
# iterate over argument line |
|
for opt |
|
do |
|
case $opt in |
|
-*=*) arg=`echo "$opt" | sed 's/^[-_a-zA-Z0-9]*=//'` ;; |
|
*) arg='' ;; |
|
esac |
|
case $opt in |
|
-n|--nop ) nop=1 ;; |
|
-q|--quiet ) quiet=1 ;; |
|
-t|--trace ) trace=1 ;; |
|
-v|--version ) version=1 ;; |
|
-h|--help ) help="Usage" ;; |
|
-i|--init ) init=1 ;; |
|
-u|--uninstall ) uninstall=1 ;; |
|
-l|--local ) local=1 ;; |
|
--root=* ) root=$arg ;; |
|
--pkgdir=* ) pkgdir=$arg ;; |
|
--subdirs=* ) subdirs=$arg ;; |
|
* ) help="Invalid option \`$opt'"; break ;; |
|
esac |
|
done |
|
|
|
# error or usage message |
|
if [ ".$help" != . ]; then |
|
if [ ".$help" != ".Usage" ]; then |
|
echo "$progname:ERROR: $help" 1>&2 |
|
fi |
|
cat 1>&2 <<EOT |
|
Usage: $progname [options] |
|
|
|
Global options: |
|
--version, -v display tool version information |
|
--help, -h display usage information |
|
--init, -i create an initial directory hierarchy |
|
|
|
Run-time options: |
|
--nop, -n perform no filesystem operations |
|
--quiet, -q display no verbose messages |
|
--trace, -t display performed filesystem operations |
|
--local, -l process a local package area only |
|
--uninstall, -u uninstall all files |
|
|
|
Filesystem options: |
|
--root=DIR override root directory |
|
--pkgdir=DIR override package sub-directory |
|
--subdirs=DIR override synchronized sub-directories |
|
|
|
Current configuration: |
|
root directory: $root |
|
package root subdir: $pkgdir |
|
synchronized subdirs: $subdirs |
|
EOT |
|
if [ ".$help" != ".Usage" ]; then |
|
exit 2 |
|
else |
|
exit 0 |
|
fi |
|
fi |
|
|
|
# version information |
|
if [ ".$version" = .1 ]; then |
|
echo "$progname $progvers ($progdate)" |
|
exit 0 |
|
fi |
|
|
|
# make sure a root directory was found or specified |
|
if [ ".$root" = . ]; then |
|
echo "$progname:ERROR: no root directory specified!" 1>&2 |
|
echo "$progname:HINT: use --root=DIR option explicitly on command line" 1>&2 |
|
echo "$progname:HINT: or implicitly inside an .lsyncrc file in your home" 1>&2 |
|
echo "$progname:HINT: directory or in any parent directory." 1>&2 |
|
exit 3 |
|
fi |
|
|
|
## |
|
## helper functions |
|
## |
|
|
|
display_hd () { |
|
if [ ".$headline" != . ]; then |
|
if [ ".$quiet" = .0 ]; then |
|
echo "$headline" |
|
fi |
|
headline='' |
|
fi |
|
} |
|
|
|
display_op () { |
|
if [ ".$quiet" = .0 ]; then |
|
echo " $@" |
|
fi |
|
} |
|
|
|
display_warning () { |
|
echo "$progname:WARNING: $*" 1>&2 |
|
} |
|
|
|
display_error () { |
|
echo "$progname:ERROR: $*" 1>&2 |
|
} |
|
|
|
perform_op () { |
|
if [ ".$trace" = .1 ]; then |
|
echo " \$ $@" |
|
fi |
|
if [ ".$nop" = .0 ]; then |
|
eval "$@" |
|
fi |
|
} |
|
|
|
## |
|
## main processing |
|
## |
|
|
|
# extend a "man" subdir to a complete list with subdirs |
|
# in order to avoid special cases in the loop processing |
|
manex='' |
|
if [ ".$init" = .1 ]; then |
|
manex='man' |
|
fi |
|
for i in 1 2 3 4 5 6 7 8; do |
|
manex="$manex,man/man$i" |
|
done |
|
manex=`echo $manex | sed -e 's;^,;;'` |
|
subdirs=`echo $subdirs | sed -e "s;man;$manex;"` |
|
|
|
# special processing: create initial hierarchy |
|
if [ ".$init" = .1 ]; then |
|
if [ ! -d $root ]; then |
|
echo "creating $root" |
|
perform_op "mkdir $root" || exit 1 |
|
fi |
|
for subdir in $pkgdir `IFS=,; echo $subdirs`; do |
|
if [ ! -d "$root/$subdir" ]; then |
|
echo "creating $root/$subdir" |
|
perform_op "mkdir $root/$subdir" || exit 1 |
|
fi |
|
done |
|
exit 0 |
|
fi |
|
|
|
# make sure the root directory actually exists |
|
if [ ! -d "$root" ]; then |
|
display_warning "root directory \`$root' does not exist" |
|
exit 3 |
|
fi |
|
|
|
# if processing is restricted to a local package area, pre-determine its name |
|
if [ ".$local" = .1 ]; then |
|
realroot=`cd $root; pwd` |
|
realthis=`pwd` |
|
pkgname=`expr "$realthis" : "^$realroot/$pkgdir/\\([^/]*\\).*"` |
|
if [ ".$pkgname" = . ]; then |
|
display_error "you are not staying under a local package sub-directory" |
|
exit 3 |
|
fi |
|
fi |
|
|
|
# now perform the synchronization for each sub-directory... |
|
for subdir in `IFS=,; echo $subdirs`; do |
|
headline="$root/$subdir:" |
|
|
|
# make sure the subdir actually exists in the access layer |
|
if [ ! -d "$root/$subdir" ]; then |
|
display_warning "access layer directory \`$root/$subdir' does not exist" |
|
continue |
|
fi |
|
|
|
# |
|
# PASS 1: remove dangling symbolic links in access layer |
|
# |
|
|
|
# iterate over all symlinks in the access layer subdir |
|
for link in . `ls "$root/$subdir/" | sed -e "s;^$root/$subdir/*;;g"`; do |
|
test ".$link" = ".." && continue |
|
|
|
# determine the target file of the symlink |
|
target=`ls -l "$root/$subdir/$link" 2>/dev/null | sed -e 's;.*-> *;;'` |
|
if [ ".$target" = . ]; then |
|
display_warning "$root/$subdir/$link seems to be not a symbolic link" |
|
continue |
|
fi |
|
|
|
# (optionally) make sure that link target points into local package area |
|
if [ ".$local" = .1 -a .`expr $target : "../$pkgdir/$pkgname/.*"` = .0 ]; then |
|
continue |
|
fi |
|
|
|
# check whether link is valid, i.e., points to |
|
# an existing target file or directory |
|
if [ ".$uninstall" = .1 ] ||\ |
|
[ ! -f "$root/$subdir/$target" -a \ |
|
! -d "$root/$subdir/$target" ]; then |
|
# target no longer exists, so remove dangling symlink |
|
display_hd |
|
display_op "remove: $link -> $target" |
|
perform_op "rm -f $root/$subdir/$link" |
|
fi |
|
done |
|
|
|
# if we are uninstalling only, our work is now done |
|
if [ ".$uninstall" = ".1" ]; then |
|
continue |
|
fi |
|
|
|
# |
|
# PASS 2: create new symbolic links in access layer |
|
# |
|
|
|
# calculate the corresponding reverse directory for the current subdir |
|
revdir=`echo $subdir | sed -e 's;[^/][^/]*;..;g'` |
|
|
|
# iterate over all package directories |
|
for dir in . `ls "$root/$pkgdir/" | sed -e "s;^$root/$pkgdir/*;;g"`; do |
|
test ".$dir" = ".." && continue |
|
|
|
# (optionally) make sure that we operate only for the local package area |
|
if [ ".$local" = .1 -a ".$dir" != ".$pkgname" ]; then |
|
continue |
|
fi |
|
|
|
# skip all directories with appended version numbers |
|
# in order to support manual versioning of packages |
|
case $dir in |
|
*-[0-9]* ) continue ;; |
|
esac |
|
|
|
# skip if package directory or package sub-directories has sticky bit set |
|
if [ ".`ls -l -d $root/$pkgdir/$dir 2>/dev/null | cut -c10`" = .t ] ||\ |
|
[ ".`ls -l -d $root/$pkgdir/$dir/$subdir 2>/dev/null | cut -c10`" = .t ]; then |
|
continue |
|
fi |
|
|
|
# check whether the processed subdir exists in package area |
|
if [ -d "$root/$pkgdir/$dir/$subdir" ]; then |
|
|
|
# iterate over all files/directories in package's subdir |
|
for file in . `ls "$root/$pkgdir/$dir/$subdir/" |\ |
|
sed -e "s;^$root/$pkgdir/$dir/$subdir/*;;g"`; do |
|
test ".$file" = ".." && continue |
|
|
|
# calculate the access layer symlink target |
|
target="$revdir/$pkgdir/$dir/$subdir/$file" |
|
|
|
# check whether a possibly conflicting symlink exists |
|
exlink=`ls -l $root/$subdir/$file 2>/dev/null` |
|
if [ ".$exlink" != . ]; then |
|
extarget=`echo $exlink | sed -e 's;.*-> *;;'` |
|
if [ ".$extarget" = . ]; then |
|
display_warning "$root/$subdir/$file exits, but seems to be not a symbolic link" |
|
elif [ ".$extarget" != ".$target" ]; then |
|
display_hd |
|
display_op "conflict: $file -> $extarget [existing]" |
|
display_op " $file -> $target [alternative]" |
|
fi |
|
continue |
|
fi |
|
|
|
# create new symlink in access layer |
|
display_hd |
|
display_op "create: $file -> $target" |
|
perform_op "cd $root/$subdir && ln -s $target $file" |
|
done |
|
fi |
|
done |
|
done |
|
|
|
|