# This program is under GPL v. 3 or later, see <http://www.gnu.org/licenses/>
_lnf_existing_link() {
- local target dest_file dest_dir
- target="$1"
- dest_file="$2"
- dest_dir="$3"
- if [[ -L $dest_file ]]; then
- if [[ $(readlink $dest_file) == "$target" ]]; then
- # Leave the link in place, but make sure it's
- # owner & group is as if we created it.
- # links all get 777 perms, so
- # we already know that is right.
-
- # test for setgid.
- if [[ $(stat -L -c%a "$dest_dir") == 2??? ]]; then
- grp=$(stat -L -c%g "$dest_dir") || return $?
- else
- grp=$(id -g) || return $?
- fi
- if [[ $EUID == 0 && $(stat -c%u "$dest_file") != 0 ]]; then
- chown -h 0:$grp "$dest_file" || return $?
- elif [[ $(stat -c%g "$dest_file") != "$grp" ]]; then
- chgrp -h $grp "$dest_file" || return $?
- fi
- do_exit=true
- return 0
- fi
- to_remove+=("$dest_file")
- elif [[ -e $dest_file ]]; then
- to_remove+=("$dest_file")
+ local target dest_file dest_dir
+ target="$1"
+ dest_file="$2"
+ dest_dir="$3"
+ if [[ -L $dest_file ]]; then
+ if [[ $(readlink $dest_file) == "$target" ]]; then
+ # Leave the link in place, but make sure it's
+ # owner & group is as if we created it.
+ # links all get 777 perms, so
+ # we already know that is right.
+
+ # test for setgid.
+ if [[ $(stat -L -c%a "$dest_dir") == 2??? ]]; then
+ grp=$(stat -L -c%g "$dest_dir") || return $?
+ else
+ grp=$(id -g) || return $?
+ fi
+ if [[ $EUID == 0 && $(stat -c%u "$dest_file") != 0 ]]; then
+ chown -h 0:$grp "$dest_file" || return $?
+ elif [[ $(stat -c%g "$dest_file") != "$grp" ]]; then
+ chgrp -h $grp "$dest_file" || return $?
+ fi
+ do_exit=true
+ return 0
fi
- to_link+=("$target")
+ to_remove+=("$dest_file")
+ elif [[ -e $dest_file ]]; then
+ to_remove+=("$dest_file")
+ fi
+ to_link+=("$target")
}
lnf() {
- local help="Usage:
+ local help="Usage:
lnf [OPTIONS] -T TARGET LINK_NAME (1st form)
lnf [OPTIONS] TARGET (2nd form)
lnf [OPTIONS] TARGET... DIRECTORY (3rd form)
"
- local temp nodir
- local verbose=false
- local dry_run=false
- local do_exit=false
- temp=$(getopt -l help,dry-run,verbose hnTv "$@") || usage 1
- eval set -- "$temp"
- while true; do
- case $1 in
- -n|--dry-run) dry_run=true; verbose=true; shift ;;
- -T) nodir=-T; shift ;;
- -v|--verbose) verbose=true; shift ;;
- -h|--help) echo "$help"; return 0 ;;
- --) shift; break ;;
- *) echo "$0: Internal error! unexpected args: $*" ; exit 1 ;;
- esac
- done
-
- if (( $# == 0 )); then
- echo "$help"
- return 1
- fi
-
- if [[ $nodir ]]; then
- if (( $# != 2 )); then
- echo "lnf: error: expected 2 arguments with -T flag. Got $#"
- return 1
- fi
+ local temp nodir
+ local verbose=false
+ local dry_run=false
+ local do_exit=false
+ temp=$(getopt -l help,dry-run,verbose hnTv "$@") || usage 1
+ eval set -- "$temp"
+ while true; do
+ case $1 in
+ -n|--dry-run) dry_run=true; verbose=true; shift ;;
+ -T) nodir=-T; shift ;;
+ -v|--verbose) verbose=true; shift ;;
+ -h|--help) echo "$help"; return 0 ;;
+ --) shift; break ;;
+ *) echo "$0: Internal error! unexpected args: $*" ; exit 1 ;;
+ esac
+ done
+
+ if (( $# == 0 )); then
+ echo "$help"
+ return 1
+ fi
+
+ if [[ $nodir ]]; then
+ if (( $# != 2 )); then
+ echo "lnf: error: expected 2 arguments with -T flag. Got $#"
+ return 1
fi
+ fi
- local reset_extglob=false
- ! shopt extglob >/dev/null && reset_extglob=true
- shopt -s extglob
-
-
- local -a to_remove to_link
- local x ret prefix dest_dir grp target
- local mkdir=false
-
- if [[ $nodir ]]; then
- dest_file="$2"
- dest_dir="$(dirname "$dest_file")"
- _lnf_existing_link "$1" "$dest_file" "$dest_dir" || return $?
- if $do_exit; then return 0; fi
- if [[ ! -d $dest_dir ]]; then
- mkdir=true
- if [[ -e $dest_dir || -L $dest_dir ]]; then
- to_remove+=("$dest_dir")
- fi
- fi
- to_link+=("$dest_file")
- elif (( $# >= 2 )); then
- dest_dir="${!#}"
- if [[ -d $dest_dir ]]; then
- prefix="$dest_dir" # last arg
- for target in "${@:1:$(( $# - 1 ))}"; do # all but last arg
- # Remove 1 or more trailing slashes, using.
- dest_file="${target%%+(/)}"
- # remove any leading directory components, add prefix
- dest_file="$prefix/${target##*/}"
- _lnf_existing_link "$target" "$dest_file" "$dest_dir"
- done
- else
- to_link+=("${@:1:$(( $# - 1 ))}")
- mkdir=true
- fi
- if (( ${#to_link[@]} == 0 )); then
- return 0
- fi
- to_link+=("$dest_dir")
- elif [[ $# -eq 1 ]]; then
- dest_file="${1##*/}"
- _lnf_existing_link "$1" "$dest_file" . || return $?
- if $do_exit; then return 0; fi
- fi
- if (( ${#to_remove[@]} >= 1 )); then
- if type -P trash-put >/dev/null; then
- if $verbose; then
- echo "lnf: trash-put -- ${to_remove[*]}"
- fi
- if ! $dry_run; then
- trash-put -- "${to_remove[@]}" || ret=$?
- fi
- # trash-put will fail to trash a link that goes across filesystems (72),
- # and for empty files (74)
- # so revert to rm -rf in that case
- if [[ $ret == 72 ]]; then
- echo "lnf: using rm -rf to overcome cross filesystem trash-put limitation"
- rm -rf -- "${to_remove[@]}" || return $?
- elif [[ $ret == 74 ]]; then
- echo "lnf: using rm -rf to overcome empty file & hardlink trash-put limitation"
- rm -rf -- "${to_remove[@]}"
- elif [[ $ret && $ret != 0 ]]; then
- return $x
- fi
- else
- if $verbose; then
- echo "lnf: rm -rf -- ${to_remove[*]}"
- fi
- if ! $dry_run; then
- rm -rf -- "${to_remove[@]}"
- fi
- fi
- fi
+ local reset_extglob=false
+ ! shopt extglob >/dev/null && reset_extglob=true
+ shopt -s extglob
- $reset_extglob && shopt -u extglob
- if $mkdir; then
- if $verbose; then
- echo "lnf: mkdir -p $dest_dir"
- fi
+ local -a to_remove to_link
+ local x ret prefix dest_dir grp target
+ local mkdir=false
- if ! $dry_run && ! mkdir -p "$dest_dir"; then
- echo "lnf error: failed to make directory $dest_dir"
- return 1
- fi
+ if [[ $nodir ]]; then
+ dest_file="$2"
+ dest_dir="$(dirname "$dest_file")"
+ _lnf_existing_link "$1" "$dest_file" "$dest_dir" || return $?
+ if $do_exit; then return 0; fi
+ if [[ ! -d $dest_dir ]]; then
+ mkdir=true
+ if [[ -e $dest_dir || -L $dest_dir ]]; then
+ to_remove+=("$dest_dir")
+ fi
fi
+ to_link+=("$dest_file")
+ elif (( $# >= 2 )); then
+ dest_dir="${!#}"
+ if [[ -d $dest_dir ]]; then
+ prefix="$dest_dir" # last arg
+ for target in "${@:1:$(( $# - 1 ))}"; do # all but last arg
+ # Remove 1 or more trailing slashes, using.
+ dest_file="${target%%+(/)}"
+ # remove any leading directory components, add prefix
+ dest_file="$prefix/${target##*/}"
+ _lnf_existing_link "$target" "$dest_file" "$dest_dir"
+ done
+ else
+ to_link+=("${@:1:$(( $# - 1 ))}")
+ mkdir=true
+ fi
+ if (( ${#to_link[@]} == 0 )); then
+ return 0
+ fi
+ to_link+=("$dest_dir")
+ elif [[ $# -eq 1 ]]; then
+ dest_file="${1##*/}"
+ _lnf_existing_link "$1" "$dest_file" . || return $?
+ if $do_exit; then return 0; fi
+ fi
+ if (( ${#to_remove[@]} >= 1 )); then
+ if type -P trash-put >/dev/null; then
+ if $verbose; then
+ echo "lnf: trash-put -- ${to_remove[*]}"
+ fi
+ if ! $dry_run; then
+ trash-put -- "${to_remove[@]}" || ret=$?
+ fi
+ # trash-put will fail to trash a link that goes across filesystems (72),
+ # and for empty files (74)
+ # so revert to rm -rf in that case
+ if [[ $ret == 72 ]]; then
+ echo "lnf: using rm -rf to overcome cross filesystem trash-put limitation"
+ rm -rf -- "${to_remove[@]}" || return $?
+ elif [[ $ret == 74 ]]; then
+ echo "lnf: using rm -rf to overcome empty file & hardlink trash-put limitation"
+ rm -rf -- "${to_remove[@]}"
+ elif [[ $ret && $ret != 0 ]]; then
+ return $x
+ fi
+ else
+ if $verbose; then
+ echo "lnf: rm -rf -- ${to_remove[*]}"
+ fi
+ if ! $dry_run; then
+ rm -rf -- "${to_remove[@]}"
+ fi
+ fi
+ fi
+ $reset_extglob && shopt -u extglob
+
+ if $mkdir; then
if $verbose; then
- echo "lnf: ln -s $nodir -- ${to_link[*]}"
+ echo "lnf: mkdir -p $dest_dir"
fi
- if ! $dry_run; then
- ln -s $nodir -- "${to_link[@]}"
+
+ if ! $dry_run && ! mkdir -p "$dest_dir"; then
+ echo "lnf error: failed to make directory $dest_dir"
+ return 1
fi
+ fi
+
+ if $verbose; then
+ echo "lnf: ln -s $nodir -- ${to_link[*]}"
+ fi
+ if ! $dry_run; then
+ ln -s $nodir -- "${to_link[@]}"
+ fi
}
lnf "$@"