From 9ca7a7ce7311148a4d0eb5e1e9d84072f74fca53 Mon Sep 17 00:00:00 2001 From: Ian Kelling Date: Mon, 13 Feb 2017 13:24:30 -0800 Subject: [PATCH] keep existing links --- lnf | 136 ++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 101 insertions(+), 35 deletions(-) diff --git a/lnf b/lnf index 1ab1cdf..3bc5152 100755 --- a/lnf +++ b/lnf @@ -2,112 +2,178 @@ # Copyright (C) 2014-2016 Ian Kelling # This program is under GPL v. 3 or later, see +_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 + # ownership is right. + # already exists. links all get 777 perms, so + # we dun have to mess with that. + if [[ $(stat -L -c%a "$dest_dir") == 2* ]]; then + grp=$(stat -L -c%g "$dest_dir") + else + grp=$(id -g) + fi + if [[ $EUID == 0 && $(stat -c%u "$dest_file") != 0 ]]; then + chown 0:$grp "$dest_file" + elif [[ $(stat -c%g "$dest_file") != "$grp" ]]; then + chgrp $grp "$dest_file" + fi + return 1 + fi + to_remove+=("$dest_file") + elif [[ -e $dest_file ]]; then + to_remove+=("$dest_file") + fi + to_link+=("$target") +} lnf() { local help="Usage: - lnf -T TARGET LINK_NAME (1st form) - lnf TARGET (2nd form) - lnf TARGET... DIRECTORY (3rd form) + lnf [OPTIONS] -T TARGET LINK_NAME (1st form) + lnf [OPTIONS] TARGET (2nd form) + lnf [OPTIONS] TARGET... DIRECTORY (3rd form) Create symlinks forcefully -Removes existing files using trash-put or rm -rf if it is not available, -or trash-put fails due to a limitation such as a cross-filesystem link. -Create directory if needed. Slightly more restrictive arguments than ln. +If the link already exists, make it's ownership be the same as if it was +newly created (only chown if we are root). Removes existing files using +trash-put or rm -rf if it is not available, or if trash-put fails due to a +limitation such as a cross-filesystem link. Create directory of link if +needed. Slightly more restrictive arguments than ln. In the 1st form, create a link to TARGET with the name LINK_NAME. In the 2nd form, create a link to TARGET in the current directory. In the 3rd form, create links to each TARGET in DIRECTORY. -Do export LNF_VERBOSE=true for verbose output +-n|--dry-run Do verbose dry run. +-v|--verbose Print commands which modify the filesystem. +-h|--help Print help and exit. " - if [[ $1 == --help || $1 == -h || $# -eq 0 ]]; then + + local temp + local verbose=false + local dry_run=false + local nodir + 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 0 + return 1 fi - local nodir - if [[ $1 == -T ]]; then - nodir=-T - shift + if [[ $nodir ]]; then if (( $# != 2 )); then - echo "lnf error: expected 2 arguments with -T flag. Got $#" + 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 x ret prefix dir to_remove + local -a to_remove to_link + local x ret prefix dest_dir grp target local mkdir=false - to_remove=() if [[ $nodir ]]; then dest_file="$2" dest_dir="$(dirname "$dest_file")" - if [[ -e $dest_file || -L $dest_file ]]; then - to_remove+=("$dest_file") - elif [[ ! -d $dest_dir ]]; then + _lnf_existing_link "$1" "$dest_file" "$dest_dir" || return 0 + if [[ ! -d $dest_dir ]]; then mkdir=true if [[ -e $dest_dir || -L $dest_dir ]]; then - to_remove+=("$dir") + 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 x in "${@:1:$(( $# - 1 ))}"; do # all but last arg + prefix="$dest_dir" # last arg + for target in "${@:1:$(( $# - 1 ))}"; do # all but last arg # Remove 1 or more trailing slashes, using. - x="${x%%+(/)}" + dest_file="${target%%+(/)}" # remove any leading directory components, add prefix - x="$prefix/${x##*/}" - [[ -e "$x" || -L "$x" ]] && to_remove+=("$x") + 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 - [[ -e "${1##*/}" || -L "${1##*/}" ]] && to_remove+=("${1##*/}") + dest_file="${1##*/}" + _lnf_existing_link "$1" "$dest_file" . || return 0 fi if (( ${#to_remove[@]} >= 1 )); then if type -P trash-put >/dev/null; then - if [[ $LNF_VERBOSE == true ]]; then + if $verbose; then echo "lnf: trash-put -- ${to_remove[*]}" fi - trash-put -- "${to_remove[@]}" || ret=$? + 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 "$0: using rm -rf to overcome cross filesystem trash-put limitation" + echo "lnf: using rm -rf to overcome cross filesystem trash-put limitation" rm -rf -- "${to_remove[@]}" elif [[ $ret == 74 ]]; then - echo "$0: using rm -rf to overcome empty file & hardlink trash-put limitation" + 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 [[ $LNF_VERBOSE == true ]]; then + if $verbose; then echo "lnf: rm -rf -- ${to_remove[*]}" fi - rm -rf -- "${to_remove[@]}" + if ! $dry_run; then + rm -rf -- "${to_remove[@]}" + fi fi fi $reset_extglob && shopt -u extglob if $mkdir; then - if ! mkdir -p "$dest_dir"; then + if $verbose; then + echo "lnf: mkdir -p $dest_dir" + fi + + if ! $dry_run && ! mkdir -p "$dest_dir"; then echo "lnf error: failed to make directory $dest_dir" return 1 fi fi - ln -s $nodir -- "$@" + if $verbose; then + echo "lnf: ln -s $nodir -- ${to_link[*]}" + fi + if ! $dry_run; then + ln -s $nodir -- "${to_link[@]}" + fi } lnf "$@" -- 2.30.2