#!/bin/sh # IMPORTANT NOTE: The "local" keyword is not defined in POSIX, so I've decided to work without it. # For this reason, I do "$X/$f" instead of assigning local variables. # Another thing to consider is that in recursive functions, backtracking logic # CANNOT depend on variables, which are changed somewhere in the body before # the recursive step. cd "${0%/*}" [ -z "$HOSTNAME" ] && HOSTNAME="$(hostname)" if [ -z "$HOSTNAME" ] then echo "Set HOSTNAME variable to the system's name!" >/dev/stderr exit 1 fi if [ ! -f "./SYSTEM/$HOSTNAME" ] then echo "No configuration in ./SYSTEM for '$HOSTNAME'!" >/dev/stderr exit 1 fi # __merge_link <.FILES_DIR> __merge_link() { while read -r f do # Output file doesn't exist -> Create link # This is also true on broken symlinks if [ ! -e "$2/$f" ] then if [ -h "$2/$f" ] then echo "Warn: '$2/$f' is a broken link to '$(readlink "$2/$f")', updating!" rm "$2/$f" fi ln -s "$1/$f" "$2/$f" # Output file is already linked to current -> Do nothing elif [ -h "$2/$f" -a "$(readlink $2/$f)" = "$1/$f" ] then : # Current file is a file (and output exists) -> Create link but also update current elif [ -f "$1/$f" ] then if [ -f "$2/$f" ] then # When output is NOT a link inside .files, # update current file with the contents of the output # Otherwise, # replace output file with (link to) current if ! cmp -s "$1/$f" "$2/$f" then if [ -h "$2/$f" ] then real="$(readlink "$2/$f")" if [ "${real#$PWD}" = "$real" ] then cp "$real" "$1/$f" 2>/dev/null echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr fi else cp "$2/$f" "$1/$f" echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr fi fi rm "$2/$f" ln -s "$1/$f" "$2/$f" else echo "Error: '$2/$f' already exists and is not a regular file!" fi # Current file is a directory (and output exists) -> Link current's files & dirs in output directory elif [ -d "$1/$f" ] then if [ -d "$2/$f" ] then # If output is a link to directory in .files, continuing with merge # will place links inside a directory in .files, which is bad. # Instead, we'll make the link to directory become a directory with # links to all inner files. if [ -h "$2/$f" ] then real="$(realpath "$2/$f")" if [ "${real#$PWD}" != "$real" ] then rm "$2/$f" mkdir "$2/$f" while read -r ff do ln -s "$real/$ff" "$2/$f/$ff" done </dev/stderr continue fi __merge_link "$PWD/$dir" "$HOME" echo "Linked $dir" done <"./SYSTEM/$HOSTNAME" } # __remove_link <.FILES_DIR> __remove_link() { while read -r f do if [ -h "$2/$f" ] then real="$(realpath "$2/$f")" # Output file exists and is a link to current -> Remove output if [ "$real" = "$1/$f" ] then rm "$2/$f" continue # Output file exists and is a link in .files -> Do nothing (do not remove from .files) # This is possible in the case when we have .files/X/A and .files/Y/A, and we linked # .files/X but are now unlinking .files/Y elif [ "${real#$PWD}" != "$real" ] then continue fi fi if [ -d "$1/$f" -a -d "$2/$f" ] then __remove_link "$1/$f" "$2/$f" fi done < Remove it # We're assuming this directory was created by the script and/or the user # will not care for empty dirs if [ -d "$2/$f" -a -z "$(ls -A "$2/$f" 2>/dev/null)" ] then rm -r "$2/$f" fi done </dev/stderr continue fi __remove_link "$PWD/$dir" "$HOME" echo "Unlinked $dir" done <"./SYSTEM/$HOSTNAME" } case "$1" in (unlink) unlink ;; (link|*) link ;; esac