Skip to content

Instantly share code, notes, and snippets.

@s1037989
Last active February 1, 2021 04:05
Show Gist options
  • Save s1037989/f7e6a40841beef893549159f8f5e00ff to your computer and use it in GitHub Desktop.
Save s1037989/f7e6a40841beef893549159f8f5e00ff to your computer and use it in GitHub Desktop.
file filter
#!/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