# vim: ft=zsh noet ts=4 sts=4 sw=4
#
# confz functions for file hierarchies and fileset miniformat
#
zmodload -F zsh/stat b:zstat
zmodload -m -F zsh/files b:zf_\*
typeset -g -A fileset_stat_cache fileset_stat_cur fileset_ftypes fileset_typenames
typeset -g fileset_stat_cur fileset_stat_next_id fileset_stat_cur_type fileset_stat_cur_perm fileset_stat_cur_major fileset_stat_cur_minor
fileset_ftypes=( # convert hex type to a word
C S # unix socket
c S
A L # symlink, could also be 'h'
a L
8 f # plain file
6 b # block device file
4 d # directory
2 c # character device file
1 p # FIFO pipe
)
fileset_typenames=(
S "unix socket"
L "symbolic link"
f "plain file"
b "block device"
c "character device"
p "named pipe"
)
fileset_reset_cache(){
# omit unsetting used variables for now
# probably no particular gain in freeing them
fileset_stat_cache=()
fileset_stat_cur=()
fileset_stat_next_id=1
}
fileset_reset_cache
# runs zstat on $1 if not already in cache
# the result is available as $fileset_stat_cur associative array
fileset_stat() {
local id ret ftype fperm
id=${fileset_stat_cache[$1]:-missing}
if [[ $id == missing ]]; then
typeset -gA fileset_stat_$fileset_stat_next_id
zstat -L -H fileset_stat_$fileset_stat_next_id $1 &>/dev/null
ret=$?
if (($ret == 0)); then
fileset_stat_cache[$1]=$fileset_stat_next_id
fileset_stat_set_cur $fileset_stat_next_id
fileset_stat_next_id=$[ $fileset_stat_next_id + 1 ]
else
fileset_stat_cache[$1]=-$ret
fi
return $ret
elif (( $id >= 0 )); then
fileset_stat_set_cur $id
return 0
else
return $[ -($id) ]
fi
}
fileset_stat_set_cur(){
local param ftype
param=fileset_stat_$1
fileset_stat_cur=( "${(@kvP)param}" )
ftype=$[ [##16] $fileset_stat_cur[mode] >> 12 ]
fileset_stat_cur_type=$fileset_ftypes[$ftype]
fileset_stat_cur_perm=$[ [##8] $fileset_stat_cur[mode] & 4095 ]
fileset_stat_cur_major=$[ $fileset_stat_cur[rdev] >> 8 ]
fileset_stat_cur_minor=$[ $fileset_stat_cur[rdev] & 255 ]
}
fileset_resetcmd() {
fileset_reset_cache
"$@"
}
confz_fs_p_check() {
checkvars filename
local parent
parent=${${vars[filename]}:h}
do_command=( fileset_resetcmd mkdir -p $parent )
fileset_stat $parent && [[ $fileset_stat_cur_type == d ]]
}
confz_fs_type_or_missing_check() {
checkvars filename flags filetype
if fileset_stat $vars[filename]; then
if [[ $fileset_stat_cur_type == $vars[filetype] ]]; then
return 0
elif [[ $vars[flags] == *'!'* ]]; then
fail_reason="expected ${fileset_typenames[${vars[filetype]}]}, got ${fileset_typenames[$fileset_stat_cur_type]}: ${(qqq)vars[filename]}"
return 1
else
die "$0: expected ${fileset_typenames[${vars[filetype]}]}, got ${fileset_typenames[$fileset_stat_cur_type]}: ${(qqq)vars[filename]}"
fi
else
return 0
fi
}
confz_fs_type_or_missing_do() {
local is_dir
if fileset_stat $vars[filename] && [[ $fileset_stat_cur_type == d ]]; then
is_dir=1
else
is_dir=0
fi
fileset_reset_cache
if [[ $vars[flags] == *r* ]]; then
if [[ $vars[flags] == *f* ]]; then
zf_rm -rf $vars[filename]
else
zf_rm -r $vars[filename]
fi
elif (($is_dir)); then
zf_rmdir $vars[filename]
else
if [[ $vars[flags] == *f* ]]; then
zf_rm -f $vars[filename]
else
zf_rm $vars[filename]
fi
fi
}
confz_fs_l_check() {
checkvars filename destination
defvar flags ''
if [[ $vars[flags] == *p* ]]; then
require fs_p :filename
fi
require fs_type_or_missing :filename :flags filetype=L
fileset_stat $vars[filename] && \
[[ $fileset_stat_cur_type == L ]] && \
[[ $fileset_stat_cur[link] == $vars[destination] ]]
}
confz_fs_l_do() {
local missing
fileset_stat $vars[filename]
missing=$?
fileset_reset_cache
if ! (($missing)); then
rm $vars[filename] || return $?
fi
zf_ln -sh $vars[destination] $vars[filename]
}
confz_fs_device_check() {
checkvars filename device_type major minor
defvar flags ''
if [[ $vars[flags] == *p* ]]; then
require fs_p :filename
fi
case $vars[device_type] in
(c) require fs_type_or_missing :filename :flags filetype=c ;;
(b) require fs_type_or_missing :filename :flags filetype=b ;;
(*) die "Incorrect device type: ${(qqq)vars[device_type]}" ;;
esac
fileset_stat $vars[filename] && \
[[ $fileset_stat_cur_type == $vars[device_type] ]] && \
[[ $fileset_stat_cur_major == $vars[major] ]] && \
[[ $fileset_stat_cur_minor == $vars[minor] ]]
}
confz_fs_device_do() {
local missing
fileset_stat $vars[filename]
missing=$?
fileset_reset_cache
if ! (($missing)); then
rm $vars[filename] || return $?
fi
mknod $vars[filename] $vars[device_type] $vars[major] $vars[minor]
}
confz_fs_pipe_check() {
checkvars filename
defvar flags ''
if [[ $vars[flags] == *p* ]]; then
require fs_p :filename
fi
require fs_type_or_missing :filename :flags filetype=p
do_command=( fileset_resetcmd mkfifo $vars[filename] )
fileset_stat $vars[filename] && [[ $fileset_stat_cur_type == p ]]
}
confz_fs_r_check() {
checkvars filename
defvar flags ''
do_command=( confz_fs_type_or_missing_do )
! fileset_stat $vars[filename]
}
confz_fs_m_check() {
checkvars filename mode
do_command=( fileset_resetcmd chmod $vars[mode] $vars[filename] )
fileset_stat $vars[filename] && (( $fileset_stat_cur_perm == $vars[mode] ))
}
confz_fs_o_check() {
checkvars filename owner
do_command=( fileset_resetcmd zf_chown -h $vars[owner] $vars[filename] )
fileset_stat $vars[filename] || \
die "fs_o: could not access file ${(qqq)vars[filename]}"
if [[ $vars[owner] =~ '^[0-9]+:[0-9]+$' ]]; then
(( $fileset_stat_cur[uid] == ${${vars[owner]}%:*} && \
$fileset_stat_cur[gid] == ${${vars[owner]}#*:} ))
elif [[ $vars[owner] =~ '^[0-9]+$' ]]; then
(( $fileset_stat_cur[uid] == ${${vars[owner]}%:*} ))
else
die "fs_o does not support non-numeric user/group: ${(qqq)vars[owner]}"
fi
}
confz_fs_f_check() {
checkvars filename
defvar flags ''
if [[ $vars[flags] == *p* ]]; then
require fs_p :filename
fi
require fs_type_or_missing :filename :flags filetype=f
fileset_stat $vars[filename] && [[ $fileset_stat_cur_type == f ]]
}
confz_fs_f_do() {
fileset_reset_cache
printf '' >> $vars[filename]
}
confz_fs_d_check() {
checkvars filename
defvar flags ''
require fs_type_or_missing :filename :flags filetype=d
if [[ $vars[flags] == *p* ]]; then
do_command=( fileset_resetcmd mkdir -p $vars[filename] )
else
do_command=( fileset_resetcmd mkdir $vars[filename] )
fi
fileset_stat $vars[filename] && [[ $fileset_stat_cur_type == d ]]
}
confz_fs_c_check() {
checkvars filename content_call
defvar flags ''
require fs_f :filename :flags
"${(Q@)${(z)vars[content_call]}}" | cmp -s - $vars[filename]
}
confz_fs_c_do() {
"${(Q@)${(z)vars[content_call]}}" > $vars[filename]
}
confz_fs_contentnl_check() {
checkvars filename content
defvar flags ''
require fs_f :filename :flags
printf '%s\n' "$vars[content]" | cmp -s - $vars[filename]
}
confz_fs_contentnl_do() {
printf '%s\n' "$vars[content]" > $vars[filename]
}