-
-
Save s1037989/f7e6a40841beef893549159f8f5e00ff to your computer and use it in GitHub Desktop.
file filter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
check_perm() { | |
local path="$1" | |
local min="$2" | |
local max="$3" | |
local owner="$4" | |
local group="$5" | |
local stat=( $(stat -L -c "0%#a %U %G" $path 2>/dev/null) ) | |
local fperm=${stat[0]} | |
local fowner=${stat[1]} | |
local fgroup=${stat[2]} | |
printf -v _min "%o" $((~0$fperm & 0${min:-0550})) | |
printf -v _max "%o" $((0$fperm & ~0${max:-0770})) | |
#printf "%s\n" "$path $min $max $owner $group | $fperm $fowner $fgroup | ~0$fperm & 0${min:-0550} = $_min | 0$fperm & ~0${max:-0770} = $_max" | |
[ $_min -eq 0 ] || return 11 | |
[ $_max -eq 0 ] || return 12 | |
[ "$(id -u $owner 2>/dev/null)" == $(id -u $fowner 2>/dev/null) ] || return 15 | |
[ "$(id -g $group 2>/dev/null)" == $(id -g $fgroup 2>/dev/null) ] || return 16 | |
return 0 | |
} | |
die() { | |
local msg=$1 | |
local errcode=$2 | |
local err | |
case "$errcode" in | |
11) err="min perm";; | |
12) err="max perm";; | |
13) err="running user";; | |
14) err="running group";; | |
15) err="file owner";; | |
16) err="file group";; | |
*) err="undefined error";; | |
esac | |
printf "%s: %s (%s)\n" "$msg" "$err" "$errcode" | |
exit | |
} | |
wait_file() { | |
is_file $1 || return 1 | |
test -f ${1%/*}/.#${1##*/} || ln $1 ${1%/*}/.#${1##*/} 2>/dev/null | |
while [[ -f $1 && -n $(lsof $1 2>/dev/null) ]]; do :; done | |
rm -f ${1%/*}/.#${1##*/} | |
} | |
ff() { | |
local file=$1 | |
local dst=$2 | |
file=${file## } | |
wait_file $file || return | |
local now=$(date +%s.%N) | |
local mod=$(stat -c%.Y $file 2>/dev/null) && ((${mod/./})) || return | |
printf "%s found %s: %s -> %s (%.9f)\n" ${FUNCNAME[2]} $file $mod $now $((${now/./}-${mod/./}))e-9 | |
pass_tests ${FF_FAIL} "$file" && move "$file" "$dst" || move "$file" "$quarantine" | |
} | |
incorrect_perm() { die "incorrect permissions or directory ownership on $1" $2; } | |
iwatch() { | |
if [[ -z $iwatch_fd ]] || ! kill -0 $iwatch_pid &>/dev/null; then | |
fifo=$(mktemp -u) | |
mkfifo $fifo | |
exec {iwatch_fd}<>$fifo | |
inotifywait -m -q -e create -e moved_to --format " %w%f" "$1" > $fifo & | |
iwatch_pid=$! | |
rm -f $fifo | |
echo "iwatch started ($iwatch_pid:$iwatch_fd)" | |
trap "kill $iwatch_pid $swatch_pid &>/dev/null; exit" 0 INT QUIT TERM | |
fi | |
read -n 256 -t .005 -u $iwatch_fd file | |
read_file "$file" "$2" | |
} | |
write_slowly() { | |
exec {_write_slowly}> "$1" | |
while read -n ${2:-2048} bytes; do | |
[[ -n $bytes ]] && echo -n $bytes 1>&${_write_slowly} || echo 1>&${_write_slowly} | |
sleep ${3:-.01} | |
done | |
eval "exec ${_write_slowly}>&-" | |
} | |
min_age() { | |
local min_age file=$2 now=${3:-$(date +%s.%N)} | |
local file_mod=$(stat -c%.Y "$file" 2>/dev/null || echo -1) | |
[[ $file_mod == -1 ]] && return 1 | |
printf -v min_age %.9f $1 | |
(((${now/./}-${file_mod/./}) > ${min_age/./})) | |
} | |
dir_empty() { | |
shopt -s nullglob | |
local remaining=($1/*) | |
((!${#remaining[@]})) | |
} | |
is_file() { | |
[[ -f $1 ]] || return 1 | |
[[ "${1##*/}" =~ ^\. ]] && return 1 | |
[[ -f ${1%/*}/.#${1##*/} ]] && return 1 | |
return 0 | |
} | |
swatch() { | |
if [[ -z $swatch_fd ]] || ! kill -0 $swatch_pid &>/dev/null; then | |
local fifo=$(mktemp -u) | |
mkfifo $fifo | |
exec {swatch_fd}<>$fifo | |
while sleep .1 && kill -0 $$ &>/dev/null; do | |
dir_empty $1 && continue | |
local now=$(date +%s.%N) | |
for file in $1/*; do | |
is_file $file && min_age 0 $file $now && echo $file | |
done | |
done > $fifo & | |
swatch_pid=$! | |
rm -f $fifo | |
echo "swatch started ($swatch_pid:$swatch_fd)" | |
trap "kill $swatch_pid $iwatch_pid &>/dev/null; exit" 0 INT QUIT TERM | |
fi | |
read -n 256 -t .1 -u $swatch_fd file | |
read_file "$file" "$2" | |
} | |
read_file() { | |
err=$? | |
[[ -n $1 && -n $2 ]] || return 1 | |
((err>=129)) && err=129 | |
case $err in | |
0) ff "$1" "$2" &;; | |
129) true;; | |
*) false;; | |
esac | |
} | |
move() { | |
mkdir -p "$2" || return 1 | |
test -d "$2" || return 1 | |
local hidden="$2/.#${1##*/}" | |
mv -f "$1" "$hidden" &>/dev/null || return | |
chown --reference="$2" "$hidden" &>/dev/null || return | |
chmod --reference="$2" "$hidden" &>/dev/null || return | |
chmod -x "$hidden" &>/dev/null || return | |
mv -f "$hidden" "$2/${1##*/}" &>/dev/null || return | |
printf "'%s' -> '%s' (%s)\n" "$1" "$2/${1##*/}" ${FUNCNAME[3]} | |
rm -f ${1%/*}/.#${1##*/} | |
} | |
pass_tests() { | |
local fail=$1; shift | |
local file=$1; shift | |
for test in "${tests[@]}"; do | |
type "$test" &>/dev/null || case "${fail:-safe}" in | |
lock) return 1;; | |
safe) continue;; | |
esac | |
test -f "$file" | |
echo "$test $file" | |
$test "$file" &>/dev/null || return 1 | |
done | |
return 0 | |
} | |
umask 0000 | |
tests=() | |
while getopts "d:o:q:s:t:" o; do | |
case "$o" in | |
d) | |
#path[:0550:0770[:owner:group]] | |
DST=(${OPTARG//:/ }) | |
test -n "${DST[0]}" -a -d "${DST[0]}" || die "dst path not a directory" | |
dst="${DST[0]}" | |
check_perm "${DST[@]}" || incorrect_perm ${DST[0]} $? | |
;; | |
o) | |
owner=(${OPTARG/:/ }) | |
[ "$(id -u 2>/dev/null)" == "$(id -u ${owner[0]} 2>/dev/null)" ] || die error 13 | |
[ "$(id -g 2>/dev/null)" == "$(id -g ${owner[1]} 2>/dev/null)" ] || die error 14 | |
;; | |
q) quarantine=$OPTARG;; | |
s) | |
#path[:0550:0770[:owner:group]] | |
SRC=(${OPTARG//:/ }) | |
test -n "${SRC[0]}" -a -d "${SRC[0]}" || die "src path not a directory" | |
src="${SRC[0]}" | |
check_perm "${SRC[@]}" || incorrect_perm ${SRC[@]} $? | |
;; | |
t) | |
unset -f "$OPTARG" | |
tests+=($OPTARG) | |
;; | |
esac | |
done | |
shift $(($OPTIND-1)) | |
[ -n "$dst" ] || die "dst path not set" | |
[ -n "$src" ] || die "src path not set" | |
: ${quarantine:=$dst-quarantine} | |
for conf in ${FF_CONF:-./ff.conf} /etc/ff.conf; do test -f $conf || continue; source $conf && echo "Loaded $conf" && break; done | |
for test in "${tests[@]}"; do declare -f "$test" &>/dev/null && echo "Found test: $test" || die "no func $test"; done | |
shopt -s nullglob | |
for file in $src/.#*; do | |
[[ -f $file ]] || continue | |
h=$(stat -c%h $file 2>/dev/null || echo 0) | |
(($h>1)) && rm -f $file | |
done | |
while :; do | |
iwatch "$src" "$dst" | |
swatch "$src" "$dst" | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment