From c71bd81af238fbb76269e940d8217c8839abed9f Mon Sep 17 00:00:00 2001 From: BoySka Date: Thu, 14 Jul 2011 15:47:20 +0200 Subject: [PATCH 1/6] New optparsing, with zparseopts * parameters can be added where you want * it's possible to use long options * you can have "synonim" options * it has potential for a recursive implementation --- src/tomb | 228 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 136 insertions(+), 92 deletions(-) diff --git a/src/tomb b/src/tomb index 05b33a4..727251f 100755 --- a/src/tomb +++ b/src/tomb @@ -24,20 +24,27 @@ VERSION=1.1 DATE=May/2011 TOMBEXEC=$0 TOMBOPENEXEC="tomb-open" +OLDARGS=$* 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 +163,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 +172,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}" ${(s: :)OLDARGS} exit $? fi # are we root already return 0 @@ -312,8 +319,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 +328,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 +344,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` @@ -481,37 +483,37 @@ mount_tomb() { notice "Commanded to open tomb $CMD2" get_arg_tomb $CMD2 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 +524,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 +564,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 +584,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 +691,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) @@ -1062,52 +1067,91 @@ EOF } main () { - echo $@ | grep '\-D' 2>&1 > /dev/null - # ????? - if [ $? = 0 ]; then + main_opts=(q -quiet=q D -debug=D h -help=h v -verbose=v) + typeset -A subcommands_opts + subcommands_opts[open]="n -nohook=n k: -key=k o: -mount-options=o" + subcommands_opts[create]="s: -size=s" + subcommands_opts[close]="" + subcommands_opts[help]="" + + ### Parsing global options (wherever they are) + zparseopts -M -E -D -Aglobal_opts $main_opts + if [[ -n ${(k)global_opts[-h]} ]]; then # -h is set + usage + exit 0 + fi + + if [[ $? != 0 ]]; then + error Some error occurred during option processing. See tomb help for more info + exit 127 + fi + typeset -a pars + set -A pars $* + typeset -g subcommand + subcommand='' + for arg in $*; do + if [[ $arg[1] != '-' ]]; then + subcommand=$arg + valid=0 + if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then #command not set + echo "Subcommand $subcommand is not valid; see tomb help" + exit 126 + fi + pars[${(k)*[(r)$arg]}]=() + break + fi + done + set -- $pars + if [[ -z $subcommand ]]; then + error You have to specify some subcommand + exit 126 fi + ### End global options - 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 - done + ### Parsing command-specific options + # zsh magic: ${=string} will split to multiple arguments when spaces occur + set -A cmd_opts ${=subcommands_opts[$subcommand]} + if [[ -n $cmd_opts ]]; then + 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 + echo ERROR: unrecognized option $arg >&2 + exit 127 + fi + fi + PARAM+=$arg + done + ### End parsing command-specific options - if ! [ $CMD ]; then - error "first argument missing, use -h for help" - exit 0 - fi + ### 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 ;; From 89d4b8d8d0770bb0a52cf14ff72661ff91280d03 Mon Sep 17 00:00:00 2001 From: boyska Date: Tue, 19 Jul 2011 15:42:49 +0200 Subject: [PATCH 2/6] Optparsing: add other commands Now all commands should have been ported --- src/tomb | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/tomb b/src/tomb index 727251f..4faedda 100755 --- a/src/tomb +++ b/src/tomb @@ -1067,13 +1067,24 @@ EOF } main () { - main_opts=(q -quiet=q D -debug=D h -help=h v -verbose=v) - typeset -A subcommands_opts - subcommands_opts[open]="n -nohook=n k: -key=k o: -mount-options=o" - subcommands_opts[create]="s: -size=s" - subcommands_opts[close]="" - subcommands_opts[help]="" - + typeset -A subcommands_opts + ### Options configuration + 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" + subcommand_opts[mount]=subcommand_opts[open] + subcommands_opts[create]="s: -size=s" + subcommands_opts[close]="" + subcommands_opts[help]="" + subcommand_opts[slam]="" + subcommand_opts[list]="" + subcommand_opts[help]="" + subcommand_opts[bury]="" + subcommand_opts[exhume]="" + subcommand_opts[decompose]="" + subcommand_opts[recompose]="" + subcommand_opts[install]="" + subcommand_opts[askpass]="" + subcommand_opts[mktemp]="" ### Parsing global options (wherever they are) zparseopts -M -E -D -Aglobal_opts $main_opts if [[ -n ${(k)global_opts[-h]} ]]; then # -h is set From d0e004412d31ae13b05b4ccbdccda7ee3b201840 Mon Sep 17 00:00:00 2001 From: boyska Date: Wed, 20 Jul 2011 01:15:31 +0200 Subject: [PATCH 3/6] Changing optparsing, tomb-open needs changes It was putting the option BEFORE the subcommand. However, the new method supports this only partially: * Boolean options can be put before the subcommand * Subcommand-specific options that take an argument can be put before the subcommand, only in the form -kvalue ; using -k value gives an error * Global options can always be put before the subcommand Therefore, we'll say that options MUST go after the subcommand (to stay sure) --- src/tomb | 2 +- src/tomb-open | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tomb b/src/tomb index 4faedda..d10ca96 100755 --- a/src/tomb +++ b/src/tomb @@ -1071,7 +1071,7 @@ main () { ### Options configuration 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" - subcommand_opts[mount]=subcommand_opts[open] + subcommands_opts[mount]=${subcommands_opts[open]} subcommands_opts[create]="s: -size=s" subcommands_opts[close]="" subcommands_opts[help]="" diff --git a/src/tomb-open b/src/tomb-open index d617b92..766de66 100755 --- a/src/tomb-open +++ b/src/tomb-open @@ -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 < Date: Mon, 25 Jul 2011 01:00:25 +0200 Subject: [PATCH 4/6] [Optparsing] Support options in whatever position However, it has a precondition that developers MUST respect: 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) This, however, make sense even from an usability point of view. Also, option with optional argument are prohibited. The technique is a "double scan": first, we scan options passing _every_ possible subcommand options to zparseopts. This way, we can distinguish between option argument and parameters. So, we recognize which is the subcommand. Then, parse again --- src/tomb | 92 ++++++++++++++++++++++++++------------------------------ 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/src/tomb b/src/tomb index d10ca96..a8c4cc8 100755 --- a/src/tomb +++ b/src/tomb @@ -1066,64 +1066,58 @@ EOF act "Tomb is now installed." } -main () { - typeset -A subcommands_opts +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]="" - subcommand_opts[slam]="" - subcommand_opts[list]="" - subcommand_opts[help]="" - subcommand_opts[bury]="" - subcommand_opts[exhume]="" - subcommand_opts[decompose]="" - subcommand_opts[recompose]="" - subcommand_opts[install]="" - subcommand_opts[askpass]="" - subcommand_opts[mktemp]="" - ### Parsing global options (wherever they are) - zparseopts -M -E -D -Aglobal_opts $main_opts - if [[ -n ${(k)global_opts[-h]} ]]; then # -h is set - usage - exit 0 - fi - - if [[ $? != 0 ]]; then - error Some error occurred during option processing. See tomb help for more info - exit 127 - fi - typeset -a pars - set -A pars $* - typeset -g subcommand - subcommand='' - for arg in $*; do - if [[ $arg[1] != '-' ]]; then - subcommand=$arg - valid=0 - if [[ -z ${(k)subcommands_opts[$subcommand]} ]]; then #command not set - echo "Subcommand $subcommand is not valid; see tomb help" - exit 126 - fi - pars[${(k)*[(r)$arg]}]=() - break - fi - done - set -- $pars - if [[ -z $subcommand ]]; then - error You have to specify some subcommand - exit 126 + 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 + 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 - ### End global options + argv=(${oldstar}) + unset oldstar - - ### Parsing command-specific options + ### Parsing global + command-specific options # zsh magic: ${=string} will split to multiple arguments when spaces occur - set -A cmd_opts ${=subcommands_opts[$subcommand]} - if [[ -n $cmd_opts ]]; then + 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" @@ -1139,7 +1133,7 @@ main () { continue #it shouldnt be appended to PARAM elif [[ $arg[1] == '-' ]]; then if [[ $ok == 0 ]]; then - echo ERROR: unrecognized option $arg >&2 + error "unrecognized option $arg" exit 127 fi fi From d5ca1885ca71dfb7d562ae7aeef97f08f090ce52 Mon Sep 17 00:00:00 2001 From: boyska Date: Wed, 27 Jul 2011 10:52:26 +0200 Subject: [PATCH 5/6] [Optparsing] do a shift to delete subcommand --- src/tomb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tomb b/src/tomb index a8c4cc8..ba17259 100755 --- a/src/tomb +++ b/src/tomb @@ -1139,6 +1139,9 @@ main() { 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) From a252f62fba4ca366f150952d8842d1c766201ff1 Mon Sep 17 00:00:00 2001 From: boyska Date: Tue, 2 Aug 2011 02:10:31 +0200 Subject: [PATCH 6/6] Fix #14: problems with spaces in option arguments --- src/tomb | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/tomb b/src/tomb index ba17259..b82a96d 100755 --- a/src/tomb +++ b/src/tomb @@ -24,7 +24,8 @@ VERSION=1.1 DATE=May/2011 TOMBEXEC=$0 TOMBOPENEXEC="tomb-open" -OLDARGS=$* +typeset -a OLDARGS +for arg in ${argv}; do OLDARGS+=($arg); done STEGHIDE=1 MOUNTOPTS="rw,noatime,nodev" @@ -177,7 +178,7 @@ SETPROMPT Insert your USER password: GETPIN EOF fi - sudo "${TOMBEXEC}" ${(s: :)OLDARGS} + sudo "${TOMBEXEC}" "${(@)OLDARGS}" exit $? fi # are we root already return 0 @@ -482,6 +483,13 @@ 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 @@ -1066,6 +1074,27 @@ EOF act "Tomb is now installed." } +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]} +} + main() { local -A subcommands_opts ### Options configuration