tMerge pull request #18 from boyska/only_optparsing - tomb - the crypto undertaker
 (HTM) git clone git://parazyd.org/tomb.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 21be9e204e7eead1f67ce6cf454e71591b311954
 (DIR) parent 58e2b26694b1ed73d04e61722295653cc94957cb
 (HTM) Author: Jaromil <jaromil@dyne.org>
       Date:   Wed,  3 Aug 2011 23:58:14 -0700
       
       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.
       Diffstat:
         M src/tomb                            |     267 ++++++++++++++++++++-----------
         M src/tomb-open                       |      10 +++++-----
       
       2 files changed, 179 insertions(+), 98 deletions(-)
       ---
 (DIR) diff --git a/src/tomb b/src/tomb
       t@@ -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
       t@@ -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
       t@@ -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
       t@@ -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}`
       t@@ -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:"
       t@@ -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`
       t@@ -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"
       t@@ -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
       t@@ -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"
       t@@ -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
        }
       t@@ -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)
       t@@ -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  ;;
 (DIR) diff --git a/src/tomb-open b/src/tomb-open
       t@@ -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
        
       t@@ -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
       t@@ -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."
       t@@ -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
       t@@ -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