Merge pull request #18 from boyska/only_optparsing

New CLI option parsing supports contextual options following commands.

ZSH functions are used to parse options. Further testing will follow.
This commit is contained in:
Jaromil 2011-08-03 23:58:14 -07:00
commit 21be9e204e
2 changed files with 179 additions and 98 deletions

267
src/tomb
View File

@ -24,20 +24,28 @@ VERSION=1.1
DATE=May/2011 DATE=May/2011
TOMBEXEC=$0 TOMBEXEC=$0
TOMBOPENEXEC="tomb-open" TOMBOPENEXEC="tomb-open"
typeset -a OLDARGS
for arg in ${argv}; do OLDARGS+=($arg); done
STEGHIDE=1 STEGHIDE=1
MOUNTOPTS="rw,noatime,nodev" MOUNTOPTS="rw,noatime,nodev"
#declare global variables
QUIET=0
DEBUG=0
typeset -A global_opts
typeset -A opts
# PATH=/usr/bin:/usr/sbin:/bin:/sbin # PATH=/usr/bin:/usr/sbin:/bin:/sbin
autoload colors; colors autoload colors; colors
# standard output message routines # standard output message routines
# it's always useful to wrap them, in case we change behaviour later # it's always useful to wrap them, in case we change behaviour later
notice() { if ! [ $QUIET ]; then print "$fg_bold[green][*]$fg_no_bold[white] $1" >&2; fi } notice() { if [[ $QUIET == 0 ]]; then print "$fg_bold[green][*]$fg_no_bold[white] $1" >&2; fi }
error() { if ! [ $QUIET ]; then print "$fg[red][!]$fg[white] $1" >&2; fi } error() { if [[ $QUIET == 0 ]]; then print "$fg[red][!]$fg[white] $1" >&2; fi }
func() { if [ $DEBUG ]; then print "$fg[blue][D]$fg[white] $1" >&2; fi } func() { if [[ $DEBUG == 1 ]]; then print "$fg[blue][D]$fg[white] $1" >&2; fi }
act() { act() {
if ! [ $QUIET ]; then if [[ $QUIET == 0 ]]; then
if [ "$1" = "-n" ]; then if [ "$1" = "-n" ]; then
print -n "$fg_bold[white] . $fg_no_bold[white] $2" >&2; print -n "$fg_bold[white] . $fg_no_bold[white] $2" >&2;
else else
@ -156,7 +164,7 @@ exec_as_user() {
# escalate privileges # escalate privileges
check_priv() { check_priv() {
if [ $UID != 0 ]; then if [ $UID != 0 ]; then
func "Using sudo for root execution of 'tomb ${(f)ARGS}'" func "Using sudo for root execution of 'tomb ${(f)OLDARGS}'"
# check if sudo has a timestamp active # check if sudo has a timestamp active
sudok=false sudok=false
sudo -n ${TOMBEXEC} 2> /dev/null sudo -n ${TOMBEXEC} 2> /dev/null
@ -165,12 +173,12 @@ check_priv() {
OPTION ttyname=$TTY OPTION ttyname=$TTY
OPTION lc-ctype=$LANG OPTION lc-ctype=$LANG
SETTITLE Super user privileges required SETTITLE Super user privileges required
SETDESC Sudo execution of Tomb ${ARGS[@]} SETDESC Sudo execution of Tomb ${OLDARGS[@]}
SETPROMPT Insert your USER password: SETPROMPT Insert your USER password:
GETPIN GETPIN
EOF EOF
fi fi
sudo "${TOMBEXEC}" ${(s: :)ARGS} sudo "${TOMBEXEC}" "${(@)OLDARGS}"
exit $? exit $?
fi # are we root already fi # are we root already
return 0 return 0
@ -312,8 +320,8 @@ EOF
create_tomb() { create_tomb() {
if ! [ ${CMD2} ]; then if ! [ ${CMD2} ]; then
error "no tomb name specified for creation" error "no tomb name specified for creation"
return 1 return 1
fi fi
tombfile=`basename ${CMD2}` tombfile=`basename ${CMD2}`
@ -321,6 +329,7 @@ create_tomb() {
# make sure the file has a .tomb extension # make sure the file has a .tomb extension
tombname=${tombfile%%\.*} tombname=${tombfile%%\.*}
tombfile=${tombname}.tomb tombfile=${tombname}.tomb
tombsize=$opts[-s]
if [ -e ${tombdir}/${tombfile} ]; then if [ -e ${tombdir}/${tombfile} ]; then
error "tomb exists already. I'm not digging here:" error "tomb exists already. I'm not digging here:"
@ -336,17 +345,11 @@ create_tomb() {
notice "Creating a new tomb in ${tombdir}/${tombfile}" notice "Creating a new tomb in ${tombdir}/${tombfile}"
if [ -z $SIZE ]; then if [ -z $tombsize ]; then
if [ $CMD3 ]; then act "No size specified, summoning the Tomb Undertaker to guide us in the creation."
tombsize=${CMD3} "$TOMBOPENEXEC" &
else wait $!
act "No size specified, summoning the Tomb Undertaker to guide us in the creation." return 0
"$TOMBOPENEXEC" &
wait $!
return 0
fi
else
tombsize=${SIZE}
fi fi
tombsize_4k=`expr $tombsize \* 1024 / 4` tombsize_4k=`expr $tombsize \* 1024 / 4`
@ -480,38 +483,45 @@ create_tomb() {
mount_tomb() { mount_tomb() {
notice "Commanded to open tomb $CMD2" notice "Commanded to open tomb $CMD2"
get_arg_tomb $CMD2 get_arg_tomb $CMD2
local tombkey
if option_is_set -k ; then
tombkey=`option_value -k`
else
tombkey="${PARAM[1]}.key"
fi
echo the key used is $tombkey
if [ $? != 0 ]; then if [ $? != 0 ]; then
error "operation aborted." error "operation aborted."
return 1 return 1
fi fi
if ! [ $CMD3 ]; then if ! [ $CMD3 ]; then
tombmount=/media/${tombfile} tombmount=/media/${tombfile}
act "mountpoint not specified, using default: $tombmount" act "mountpoint not specified, using default: $tombmount"
elif ! [ -x $CMD3 ]; then elif ! [ -x $CMD3 ]; then
error "mountpoint $CMD3 doesn't exist, operation aborted." error "mountpoint $CMD3 doesn't exist, operation aborted."
if [ -n "$usbkey_mount" ]; then if [ -n "$usbkey_mount" ]; then
umount $usbkey_mount umount $usbkey_mount
rmdir $usbkey_mount rmdir $usbkey_mount
unset usbkey_mount unset usbkey_mount
fi fi
return 1 return 1
else else
tombmount=$CMD3 tombmount=$CMD3
fi fi
# check if its already open # check if its already open
mount -l | grep "${tombname}.tomb.*\[$tombname\]$" 2>&1 > /dev/null mount -l | grep "${tombname}.tomb.*\[$tombname\]$" 2>&1 > /dev/null
if [ $? = 0 ]; then if [ $? = 0 ]; then
error "$tombname is already mounted on $tombmount" error "$tombname is already mounted on $tombmount"
act "tomb list - show all tombs currently open" act "tomb list - show all tombs currently open"
if [ -n "$usbkey_mount" ]; then if [ -n "$usbkey_mount" ]; then
umount $usbkey_mount umount $usbkey_mount
rmdir $usbkey_mount rmdir $usbkey_mount
unset usbkey_mount unset usbkey_mount
fi fi
error "operation aborted." error "operation aborted."
return 1 return 1
fi fi
notice "mounting $tombfile on mountpoint $tombmount" notice "mounting $tombfile on mountpoint $tombmount"
@ -522,18 +532,18 @@ mount_tomb() {
nstloop=`losetup -f` nstloop=`losetup -f`
if [ $? = 255 ]; then if [ $? = 255 ]; then
error "too many tomb opened. Please close any of them to open another tomb" error "too many tomb opened. Please close any of them to open another tomb"
exit 1 exit 1
fi fi
losetup -f ${tombdir}/${tombfile} losetup -f ${tombdir}/${tombfile}
act "check for a valid LUKS encrypted device" act "check for a valid LUKS encrypted device"
cryptsetup isLuks ${nstloop} cryptsetup isLuks ${nstloop}
if [ $? != 0 ]; then if [ $? != 0 ]; then
# is it a LUKS encrypted nest? see cryptsetup(1) # is it a LUKS encrypted nest? see cryptsetup(1)
error "$tombfile is not a valid Luks encrypted storage file" error "$tombfile is not a valid Luks encrypted storage file"
$norm || rmdir $tombmount 2>/dev/null $norm || rmdir $tombmount 2>/dev/null
return 1 return 1
fi fi
# save date of mount in minutes since 1970 # save date of mount in minutes since 1970
@ -562,10 +572,10 @@ mount_tomb() {
done done
if ! [ -r /dev/mapper/${mapper} ]; then if ! [ -r /dev/mapper/${mapper} ]; then
error "failure mounting the encrypted file" error "failure mounting the encrypted file"
losetup -d ${nstloop} losetup -d ${nstloop}
$norm || rmdir ${tombmount} 2>/dev/null $norm || rmdir ${tombmount} 2>/dev/null
return 1 return 1
fi fi
act "encrypted storage filesystem check" act "encrypted storage filesystem check"
@ -582,8 +592,8 @@ mount_tomb() {
notice "encrypted storage $tombfile succesfully mounted on $tombmount" notice "encrypted storage $tombfile succesfully mounted on $tombmount"
if ! [ $NOBIND ]; then if ! [ $NOBIND ]; then
exec_safe_bind_hooks ${tombmount} exec_safe_bind_hooks ${tombmount}
exec_safe_post_hooks ${tombmount} open exec_safe_post_hooks ${tombmount} open
fi fi
return 0 return 0
} }
@ -689,6 +699,9 @@ print "-----END PGP MESSAGE-----"
} }
exec_safe_bind_hooks() { exec_safe_bind_hooks() {
if [[ -n ${(k)opts[-o]} ]]; then
MOUNTOPTS=${opts[-o]}
fi
local MOUNTPOINT="${1}" local MOUNTPOINT="${1}"
local ME=${SUDO_USER:-$(whoami)} local ME=${SUDO_USER:-$(whoami)}
local HOME=$(grep $ME /etc/passwd | sed "s/^${ME}:.*:.*:.*:.*:\([\/a-z]*\):.*$/\1/" 2>/dev/null) local HOME=$(grep $ME /etc/passwd | sed "s/^${ME}:.*:.*:.*:.*:\([\/a-z]*\):.*$/\1/" 2>/dev/null)
@ -1061,53 +1074,121 @@ EOF
act "Tomb is now installed." act "Tomb is now installed."
} }
main () { option_is_set() {
echo $@ | grep '\-D' 2>&1 > /dev/null #First argument, the option (something like "-s")
# ????? #Second (optional) argument: if it's "out", command will print it out 'set'/'unset'
if [ $? = 0 ]; then # This is useful for if conditions
#Return 0 if is set, 1 otherwise
[[ -n ${(k)opts[$1]} ]];
r=$?
if [[ $2 == out ]]; then
if [[ $r == 0 ]]; then
echo 'set'
else
echo 'unset'
fi
fi fi
return $r;
}
option_value() {
#First argument, the option (something like "-s")
echo ${opts[$1]}
}
ARGS=$@[@] main() {
local -A subcommands_opts
OPTS=`getopt -o hvqDs:k:no: -n 'tomb' -- "$@"` ### Options configuration
while true; do #Hi, dear developer! Are you trying to add a new subcommand, or to add some options?
case "$1" in #Well, keep in mind that:
-h) # 1. An option CAN'T have differente meanings/behaviour in different subcommands.
usage # For example, "-s" means "size" and accept an argument. If you are tempted to add
exit 0 ;; # an option "-s" (that means, for example "silent", and doesn't accept an argument)
-v) # DON'T DO IT!
notice "Tomb - simple commandline tool for encrypted storage" # There are two reasons for that:
act "version $VERSION ($DATE) by Jaromil @ dyne.org" # I. usability; user expect that "-s" is "size
# print out the GPL license in this file # II. Option parsing WILL EXPLODE if you do this kind of bad things
act "" # (it will say "option defined more than once, and he's right)
cat ${TOMBEXEC} | awk 'BEGIN { license=0 } /^# This source/ { license=1 } { if(license==1) print " " $0 } main_opts=(q -quiet=q D -debug=D h -help=h v -verbose=v)
/MA 02139, USA.$/ { license=0 }' subcommands_opts[open]="n -nohook=n k: -key=k o: -mount-options=o"
act "" subcommands_opts[mount]=${subcommands_opts[open]}
exit 0 ;; subcommands_opts[create]="s: -size=s"
-q) QUIET=1; shift 1 ;; subcommands_opts[close]=""
-D) subcommands_opts[help]=""
print "[D] Tomb invoked with args \"${(f)@}\" " subcommands_opts[slam]=""
print "[D] running on `date`" subcommands_opts[list]=""
DEBUG=1; shift 1 ;; subcommands_opts[help]=""
-s) SIZE=$2; shift 2 ;; subcommands_opts[bury]=""
-k) KEY=$2; shift 2 ;; subcommands_opts[exhume]=""
-n) NOBIND=1; shift 1 ;; subcommands_opts[decompose]=""
-o) MOUNTOPTS=$2; shift 2;; subcommands_opts[recompose]=""
--) shift; break ;; subcommands_opts[install]=""
*) CMD=$1; subcommands_opts[askpass]=""
FILE=$2; MOUNT=$3; # compat with old args subcommands_opts[mktemp]=""
CMD2=${2}; CMD3=${3}; break ;; ### Detect subcommand
esac local -aU every_opts #every_opts behave like a set; that is, an array with unique elements
for optspec in $subcommands_opts$main_opts; do
for opt in ${=optspec}; do
every_opts+=${opt}
done
done done
local -a oldstar
if ! [ $CMD ]; then oldstar=($argv)
error "first argument missing, use -h for help" zparseopts -M -E -D -Adiscardme ${every_opts}
exit 0 unset discardme
subcommand=$1
if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then #there's no such subcommand
error "Subcommand '$subcommand' doesn't exist"
exit 127
fi fi
argv=(${oldstar})
unset oldstar
### Parsing global + command-specific options
# zsh magic: ${=string} will split to multiple arguments when spaces occur
set -A cmd_opts ${main_opts} ${=subcommands_opts[$subcommand]}
if [[ -n $cmd_opts ]]; then #if there is no option, we don't need parsing
zparseopts -M -E -D -Aopts ${cmd_opts}
if [[ $? != 0 ]]; then
error "Some error occurred during option processing. See \"tomb help\" for more info"
exit 127
fi
fi
#build PARAM (array of arguments) and check if there are unrecognized options
ok=0
PARAM=()
for arg in $*; do
if [[ $arg == '--' || $arg == '-' ]]; then
ok=1
continue #it shouldnt be appended to PARAM
elif [[ $arg[1] == '-' ]]; then
if [[ $ok == 0 ]]; then
error "unrecognized option $arg"
exit 127
fi
fi
PARAM+=$arg
done
#first parameter actually is the subcommand: delete it and shift
PARAM[1]=()
shift
### End parsing command-specific options
### Set global options (useless, but for code retro-compatibility)
for opt in ${(k)global_opts}; do
if [[ $opt == '-q' ]]; then
QUIET=1
elif [[ $opt == '-D' ]]; then
DEBUG=1
fi
done
CMD=$subcommand
CMD2=$PARAM[1]
CMD3=$PARAM[2]
func "Tomb command: $CMD $CMD2 $CMD3" func "Tomb command: $CMD $CMD2 $CMD3"
case "$CMD" in case "$subcommand" in
create) check_priv ; create_tomb ;; create) check_priv ; create_tomb ;;
mount) check_priv ; mount_tomb ;; mount) check_priv ; mount_tomb ;;
open) check_priv ; mount_tomb ;; open) check_priv ; mount_tomb ;;

View File

@ -211,7 +211,7 @@ if [ $1 ]; then # is it a file?
exit 1 exit 1
else else
"${TOMBEXEC}" -k ${tombkey} mount ${tombdir}/${tombfile} "${TOMBEXEC}" mount -k ${tombkey} ${tombdir}/${tombfile}
success=$? success=$?
fi fi
@ -255,7 +255,7 @@ if [ "$1" != "wizard" ]; then
if [ -z $DISPLAY ]; then if [ -z $DISPLAY ]; then
error "tomb-open is a wrapper for the command 'tomb'" error "tomb-open is a wrapper for the command 'tomb'"
error "[!] type 'tomb-open wizard' if you want to be guided" error "[!] type 'tomb-open wizard' if you want to be guided"
"${TOMBEXEC}" -h "${TOMBEXEC}" help
exit 1 exit 1
fi fi
fi fi
@ -338,7 +338,7 @@ cat <<EOF
EOF EOF
tombfile=${tombname}.tomb tombfile=${tombname}.tomb
"${TOMBEXEC}" -s $tombsize create ${tombfile} "${TOMBEXEC}" create -s $tombsize ${tombfile}
if [ $? != 0 ]; then if [ $? != 0 ]; then
error "An error occurred creating tomb, operation aborted." error "An error occurred creating tomb, operation aborted."
@ -368,7 +368,7 @@ if [ $? = 0 ]; then
notice "${tombname}.key succesfully saved on your USB" notice "${tombname}.key succesfully saved on your USB"
act "now we'll proceed opening your brand new tomb" act "now we'll proceed opening your brand new tomb"
"${TOMBEXEC}" -k ${tombfile}.key open ${tombfile} "${TOMBEXEC}" open -k ${tombfile}.key ${tombfile}
if [ $? = 0 ]; then if [ $? = 0 ]; then
launch_status ${tombname} launch_status ${tombname}
fi fi
@ -392,7 +392,7 @@ cat <<EOF
EOF EOF
"${TOMBEXEC}" -k ${tombname}.tomb.key open ${tombfile} "${TOMBEXEC}" open -k ${tombname}.tomb.key ${tombfile}
if [ $? = 0 ]; then if [ $? = 0 ]; then
launch_status ${tombname} launch_status ${tombname}
fi fi