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

View File

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