corz.org text viewer..
[currently viewing: /linux/software/checksum/checksum.sh - raw]
#!/bin/bash
set -e
#
#    Checksum for Linux                    v0.7.5b                            License: GPLv2+
#
#    Welcome to Checksum for Linux - drop-dead simple hashing from your desktop!
#
#     This is the BASH script part of Checksum for Linux, a simple checksum-like hash creation
#     & verification package, aiming to recreate the basic point-and-[right-]click hashing
#     functionality of the original checksum, except in the KDE Desktop environment*.
#
#     Essentially, it's a handy right-click+GUI front-end for the ever-robust on-board *nix
#     hashing tools, with a good few shiny knobs on; Features..
#
#    Hash or verify files and folders, even entire volumes, with a click..
#
#        Select "Checksum" from any folder's services menu, and Checksum springs into action,
#        creating MD5 or SHA-* hashes for all files in the folder, and all the folders inside,
#        and all the folders inside that, and so on, aka. "recursively", through the entire
#        directory tree.
#
#         Select "Verfiy this folder", from your service menu, and Checksum searches the
#         *entire* directory tree for .md5, .sha*, and .hash files, and verifies them *all*.
#
#    Full Desktop GUI integration.
#
#        The terminal is not required. Create and verify hashes directly from your desktop,
#        using Dolphin/Konqueror/etc. service menus. For output, Checksum supports both
#        kdialog (KDE) and zenity (Gnome) dialogs and notifications. Or you can use the shell.
#
#    Configurable File Masks..
#
#        Checksum only music files, or whatever; with both single and multiple file masks.
#
#    MD5/SHA1/SHA2 hashing algorithms supported.
#
#        Okay, and SHA-384 and SHA-512, because it was so trivial to tag on.
#
#    Intellignet append (syncronization).
#
#        Added a few files to a folder? No problem. With intelligent append, you can simply
#        checksum again, and *only* the new hashes get added. It's all automatic!
#
#    Multihashing_(TM), with generic .hash file handling..
#
#      As well as full .md5 and .sha1 support, of course!
#
#        So if you upgrade algorithms later, or wish to include multiple hashing algorithms
#        within a single hash file; no problem; checksum can verify them all. As well as
#        inferring security benefits, this enables the use of a cute, generic ".hash"
#        extension for *all* your checksums! (the real reason) One file fits all, baby!
#
#         Checksum also makes it trivial to create such a file, with intelligent hash append;
#        auto-matically skipping any files already hashed with your current algorithm. Neat.
#
#    Read-only Fall-back
#
#        Checksum can automatically switch to a designated fall-back folder in the event
#        of encountering read-only conditions in the checksumming location.
#
#    Shell use (mostly) supported.
#
#        You want these groovy features in your shell, go for it!
#
#
# Usage:
#
#     In the shell..
#
#        To create hashes:
#
#            checksum <file|folder>
#
#        To verify hashes:
#
#            verify <checksum file|folder>
#
#     Of course, the whole point of this, is to have the power of these commands on your
#     desktop, in the form of some handy services. The accompanying .desktop files enable
#     you to do exactly this. These simply launch checksum with different switches, instructing
#    Chec ksum to post GUI dialogs, notifications and such in your desktop environment.
#
#     There is a choice of kdialog (KDE) and zenity (Gnome) GUI reporting, and checksum will
#     use whatever you specify on the command-line, or with symlinks. You can also force zenity
#     dialogson KDE, if you prefer. zenity's dialogs and notifications may be superior, but you
#     lose the ability to "Do not show this message again". FYI; The preferences for those
#    "kdialogs" are in ~/.kde/share/config/checksum.
#
#    Because both systems have advantages (kdialog also has a handy find command, for searching
#    the text dialogs. Pretty neat. But zenity text boxes scroll the output automatically, for
#    example), there is also a "hybrid" setting, where you can use the best bits from each program.
#
#
#    Command-Line Switches..
#
#     You can override Checksum's behaviour with the use of command line arguments
#     aka. "switches". These override *everything*, so, for example, if you run Checksum
#     via the "kchecksum-sha1" symlink, and add the "--md5" switch, you get MD5 hashes.
#     These also enable you to use checksum without *any* symlinks, if you prefer that.
#
#     Available switches..
#
#            --verify            force verification mode, regardless of how checksum was called.
#            --kde                use kdialogs (handy, when not using a k* symlink)
#             --zenity | --zen    force zenity dialogs
#            --hybrid            use hybrid kdialog + zenity GUI system (best of both)
#             --xl                extensionless hashing (when not default - why oh why!)
#             --append            set append to true when it is not (again, why?)
#             --noappend            set append to false
#             --algo                set hashext="algo", override generic .hash file creation.
#             --md5                 default algorithm = MD5
#             --sha | --sha1        default algorithm = SHA-1
#             --sha2                default algorithm = SHA-256
#             --sha3                default algorithm = SHA-384
#             --sha5                default algorithm = SHA-512
#            --mask=file.mask    use regular file globbing patters.
#                                e.g.. kchecksum --mask=*.avi /Archive/Movies
#                                Multiple masks are okay, in the form.. --mask=*.mp3,*.ogg,*.flac
#
#    NOTE: Switches are applied in the above order, later switches overriding earlier switches.
#
#     You can also run kchecksum, kverify, etc. (the GUI versions) from the terminal;
#     handy if you need to debug/troubleshoot some [k|g]checksum/[k|g]verify operation;
#     the normal output will appear in your terminal. Also see Logging, below.
#
#
#     Checksum for Linux also comes with a handy Windows Hash File Converter script you can
#     use to convert any hash files made with windows tools. It does nothing more clever
#     than switch the linebreaks from DOS to UNIX, but hey, you *can* convert an entire
#     volume of hash files with one click! See: convert-winhashes for more details.
#
#     If you have any questions, mail me, or better yet, post them on the Checksum page..
#
#     http://corz.org/linux/software/checksum/
#
#     Have fun!
#
#     ;o)
#     Cor < linux at corz d.t org >
#
#
#     NOTE:     Other than my own intense desktop usage, this has had *very* little testing;
#            so all comments, bug reports, etc., will be gratefully received.
#
#    ALSO:    Apologies for all the tabs and spaces in the kdialog titles, but I cannot
#            live with " - kDialog" slapped on there. If this interferes with any desktop
#            automations or somesuch you have running; edit them out. And let me know!
#
#     *
#     It should work fine with other Linux desktops, too, but the GUI & activation methods
#     would need to be tweaked some. Gnome (zenity) dialogs are already coded in, though I
#     currently have no Gnome desktop to figure out Nautilus' service menus. Real soon, now!
#
#     **
#     File systems have "directories", but on a desktop they are called "folders". ;o)


## NOTE{S}:
#

## KDE & Kdialog
#
#    The funky dbus stuff, this requires KDE 4 to function.
#

## Logging..
#
#    When you use checksum from inside your desktop, a logfile is created in
#    /tmp/checksum.log, which contains all the script output (stuff that you might see
#    if you used checksum from the shell). This file is (usually) deleted at boot time,
#    and can be handy meantime. For example, if you added debug echo statements into this
#    script, they could be viewed there. Or if nothing happened, that file may tell you why.
#
#    However, if you prefer, you can disable this logging altogether with a single
#    alteration to the checksum .desktop files - full details inside "checksum.desktop".
#
## ##

## SHA-* support..
#
#    Checksum's default operation is to create and verify MD5 hashes. Unless you have
#    specific security needs, MD5 is perfect for file verification purposes, and is
#    also faster, in use, than any of the SHA-* family of algorithms.
#
#    Having said that, Checksum fully supports both SHA-1 and SHA-256 hashing algorithms.***
#    There is a preference (below) where you can set your preferred default.
#
#    Also, if you use a symlink with a name ending 'sha1', checksum will magically switch
#    to use sha1sum for all its operations. Use "sha2" to get SHA-256; 'kchecksum-sha1',
#    'kchecksum-sha2', etc. Same story in a shell; 'checksum-sha1', etc. You can also use
#    the command-line switches described above. Or a combination of both. Your call.
#
#    Checksum comes with a pre-made selection of .desktop files facilitating easy hashing
#    with the SHA-* hashing algorithms, which most people probably won't need.
#
#    NOTE: If you set your default algorithm to SHA-something, you can override the other
#    way, too, by using a symlink ending in "md5", or by use of the '--md5' switch,
#
#    During *verification*, checksum automatically switches algorithms, depending on
#    the current hash file extension, md5 => MD5; sha1 => SHA-1; sha2 => SHA-256; etc.,
#    so you don't have to set anything special to verify them. One command verifies ALL!
#
#    If you are using a generic extension, e.g. ".hash", checksum will interrogate the
#    file on a line-by-line basis, interrogating the individual hashes, and using
#    whatever tool is most approprite for each individual hash.
#
#    FYI; if you ask checksum to verify a file of a *completely* unknown extension, it
#    will be checked with md5sum, just in case it really is an old checksum file. It
#    will *not* be treated as a generic .hash file. This is by design.
#
#    *** Checksum also supports SHA-384 ('sha3') and SHA-512 ('sha5') hashing.
#        But let's be serious - this is file verification!
#
## ##


## :BEGIN PREFS
#

# Default Hashing Algorithm..    [string]        [default: algorithm='md5']
#
# Choose from 'md5' (for MD5), 'sha1' (SHA-1), or 'sha2' (SHA-256).
#
algorithm='md5'
#
# NOTE: you can override this default algorithm at any time by accessing checksum
# via a symlink of the appropriate name; e.g. 'kchecksum-sha1' for SHA-1 hashing.
# See the notes above, for more details. There's also 'sha3' and 'sha5', for nutters!


# Hashfile extension.        [string {file extension}]        [default: hashext='hash']
#
# Your preferred hash extension (for creating hashes)..
# files of this extension will also be treated as checksum files during verification.
#
hashext='hash'
#
# Checksum fully supports MultiHashing, and will happily verify a mix of MD5 and SHA
# hashes, even mixed up in the same file, hence the ability to use a single, generic
# .hash extension for *all* your hashes, as well as some other goodies.
#
# If you use your /own/ generic extension, you will probably want to also add it to your
# system filetype associations, so it automatically runs with kverify %f, or whatever.
#
# (at the time of writing, checksum doesn't setup .hash files, either; this is planned)
#
# There is also the special value, 'algo', which will use the currrent algorithm as the
# hash extension; e.g. an MD5 checksum file might be called, 'foobar.md5'.  While
# clutterish and less flexible, if you tend to hash very large numbers of small files;
# note; this setting has performance benefits during verification, because there is no
# need to interrogate the hash file line-by-line, or switch hashing tools: the
# extension sets the algorithm. You can also set this at runtime, with switches, so it
# may still be a good idea to leave the generic extension, and specify --algo as needed.
#
# ** IMPORTANT **
#
# DO NOT set this to 'md5', or some other algorithm - use 'algo', and set your default
# algorithm, above, instead.
#


# Append hashes to existing hash files?            [bool {0|1}]    [default: append=1][true]
#
# Checksum can automatically append any hashes you create, onto existing hash files.
# As well as making it trivial to add new files to an existing folder hash, this enables
# you to easily make MultiHashes, adding SHA-1 hashes into an existing MD5 hash file, or
# some other combination of actions. The possibilities are vast. You could easily create
# a hash file that was *ordered*; movies, music, archives, everything else, for example.
#
# Otherwise, set this to 0, and Checksum will ask you if you wish to overwrite existing
# hash files as and when it discovers them. Boring!
#
append=1


# Extensionless Hashes?        [bool {0|1}]            [default: extensionless=1][true]
#
# When hashing individual files, should checksum create 'extensionless' hash files?
# e.g. MyMovie.avi >> 'MyMovie.md5', NOT 'MyMovie.avi.md5' .
#
extensionless=1
#
# As well as being rather elegant, it's rather efficient; a hash for 'foo.avi' can
# be stored alongside hashes for 'foo.mpg', 'foo.html', & 'foo.nfo', etc., in one
# file. During verification, it looks just like a regular folder checksum. wekl!!


# Force Zenity?                [bool {0|1}]                [default: force_zenity=0][false]
#
# If you prefer to use zenity's notifications and dialogs, even when kdialog is available
# on KDE, set this to 1.. NOTE: You can also force zenity by using any of the g* symlinks.
#
force_zenity=0
#
# Don't forget to install zenity!    (sudo aptitude install zenity)


# Hybrid GUI System
#
# This basically uses kdialog where it might be useful to have the "do not show this again"
# option, and zenity for everything else. The advantage of this is three-fold; firstly, you
# get the "Do not show this again" option for dialogs where such things might matter. Secondly,
# you get superior notifications which stay put throughout the length of the entire operation,
# and thirdly, you get a neat icon in your system tray, as opposed to the rather distressing
# kNotifications! Zenity also scrolls its text output to the end (where the errors are), which
# is nice. This was trivial to implement, and trivial for you to customize, if required. See below.
#
hybrid=1
#
# Obviously, for this to work as expected, you need both kdialog and zenity installed.


# Ignore certain file types        [array]
#                                 [default: ignore_extensions=('desktop' 'ini' 'lnk' 'directory')]
#
# When creating hashes, checksum will skip files with the following extensions..
# DO Leave a space between each quoted entry. No dots required (unless the extension has one)
#

ignore_extensions=('desktop' 'ini' 'lnk' 'directory' 'nfo' 'm3u' 'pls' 'url' 'sfv')
#
# NOTE:    The 'directory' entry is for '.directory' files, NOT directories!
# NOTE:    Checksum will also automatically skip any hash files.


# Fall-Back location        [folder path]            [default: fallback_dir="~/Hashes"]
#
# If you ask checksum to create hashes of some read-only file/directory/volume, it
# obviously cannot store the hashes in the usual location, and so will use a fall-back.
#
fallback_dir="$HOME/Hashes"
#
# You can use "$HOME" variable to refer to your actual home folder.
# NOTE: You must use double-quotes to expand $VARIABLES. ("$var" not '$var')


# Temp file                    [file path]            [default: tmpfile='/tmp/checksum.tmp']
#
# Temporary file for folder verification (usually inside /tmp/). Output from multiple
# checksum files is collected here and posted at the finish. This gets wiped on every run.
#
tmpfile='/tmp/checksum.tmp'
#
# NOTE: The command-line inside any .desktop files used to launch Checksum may also log.
# By default, a semi-permanent (deleted@boot) log will be created in /tmp/checksum.log

# Error Log                    [file path]        [default: errlog='/tmp/checksum-errors.log']
#
# Another temporary file, wiped at run-time. Any verification errors are collected
# here and its output is posted at the foot of your verification results.
#
errlog='/tmp/checksum-errors.log'


# Keep the error log?                [bool {0|1}]            [default: append=0][false]
#
# Set this to 1, and the error log (if there is one) will be time-stamped and copied over
# to the working directory at the end of the verification procedure. In actual fact, you
# get a log of the entire operation + the error log joined together.
#
keep_errlog=0


# Dialog Maximum Height
#
# If you have an especially large monitor, you may wish to hack a higher value here..
#
dh_max=600


# Show Times.
#
# If you are testing or something, you might want to enable this. Total time is reported.
# NOTE: this time is sent to stdout, so you will need to either examine checksum.log
# (or whatever it's called in your .desktop files), or else run things from the shell.
show_times=1

#
## :END PREFS


# Note to experienced BASH coders..
#
# I'm an amateur bash coder, but I'd like to be better.
# All suggestions for improvements to Checksum will be warmly received.


# init..
mode=
mask=
folder=0
filebad=0
notifypid=0
existing=()
# simple flag set if we use fall-back
fb=0

# this changes, depending somewhat on the dialog's content..
diag_height=600

# this may change, depending on the symlink used..
me="${0##*/}"

# use last parameter (we may add --switches, later)..
path="${!#}"
# that changes, so we'll make a copy, now..
opath="$path"

# I do hope we won't need this..
here="$(pwd)"

# setup..
rm -rf "$errlog"
rm -rf "$tmpfile"
# for testing..
start_time=$(date +%s)


# A few functions..
#

# SwitchAlgo    {string}
# Switch hashing algorithm by file extension..
#
# This sets the file extension and actual binary used to compute the hash(es)..
#
# If you wanted to hack the default algorithm used for *completely* unknown files
# (aka. fallback algorithm), this is where you could do it. Currently it's md5..
# Of course, it's better if you have the correct extensions on your hash files.
# And best of all to use a generic extension, e.g. "hash". ;o)
#
# Fall-back only comes into effect if you specifically ask checksum to verify a
# single, non-hash-extensioned file, e.g. "kverify foo.bar", where 'bar' is unknown.
#
# This function gets used a *lot*.
#
SwitchAlgo() {
    case "$1" in
        sha1)
            algo='sha1'
            tool='sha1sum'
            hashlen=40
            ;;
        sha2)
            algo='sha2'
            tool='sha256sum'
            hashlen=64
            ;;
        sha3)
            algo='sha3'
            tool='sha384sum'
            hashlen=96
            ;;
        sha5)
            algo='sha5'
            tool='sha512sum'
            hashlen=128
            ;;
        *)
            algo='md5'
            tool='md5sum'
            hashlen=32
            ;;
    esac
}


# CheckMultiHash    {file path}
#
# This function accepts the local path to a checksum file,
# interrogates it on a line-by-line basis, and checks each
# recorded hash against a freshly computed hash, reporting
# success or failure, depending.
#
CheckMultiHash() {

    cat "$1" | while read line; do

        xhash=${line%% *}
        file=${line#*\*}

        # if it's not hash-looking, don't bother..
        if [[ $xhash =~ ^[[:xdigit:]]+$ ]]; then

            if [[ ! -f "$file" ]]; then
                echo "$file$algo: MISSING"
                echo "$1$algo: MISSING: $file" >> "$errlog"
                continue
            fi

            # calculate a nice new hash,,
            case ${#xhash} in
                32)
                    SwitchAlgo 'md5'
                    nnh=$($tool "$file")
                    nnh=${nnh:0:32}
                    ;;
                40)
                    SwitchAlgo 'sha1'
                    nnh=$($tool "$file")
                    nnh=${nnh:0:40}
                    ;;
                64)
                    SwitchAlgo 'sha2'
                    nnh=$($tool "$file")
                    nnh=${nnh:0:64}
                    ;;
                96)
                    SwitchAlgo 'sha3'
                    nnh=$($tool "$file")
                    nnh=${nnh:0:96}
                    ;;
                128)
                    SwitchAlgo 'sha5'
                    nnh=$($tool "$file")
                    nnh=${nnh:0:128}
                    ;;
                *)
                    # some other hex digit!
                    continue
                    ;;
            esac

            if [[ "$nnh" = "$xhash" ]]; then
                echo "$file$algo: OK"
            else
                echo "$file$algo: FAILED"
                echo "$1$algo: FAILED: $file" >> "$errlog"
            fi
        fi
    done | tee -a "$tmpfile"
}


# read the number of lines in an output file, and *roughly*
# set the dialog height to match. ish. Did I mention this isn't precise?
SetDialogHeight() {
    fl=$(wc -l < "$1" )
    diag_height=$((100+(fl*=30))) # magic!
    if [[ $diag_height -gt $dh_max ]] || [[ $diag_height -lt 50 ]]; then diag_height=$dh_max; fi
}


# add a checksum file's hashes into the existing() array
grabhashes() {
    while read; do
        hash=${REPLY%% *}
        [[ ${#hash} -eq $hashlen ]] && existing+=("${REPLY#*\*}")
    done < "$1"
}

# pop-up notification message, usually..
notify_user() {

    case $dlg_mode in

        0)
            echo
            echo "$2 hashes in \"$1\" .."
            echo "** To abort, press Ctrl-C **"
            echo
            ;;
        1 | 3)
            zenity     --notification --text="checksum: $3 in progress (click to hide)" \
                                        --window-icon="$HOME/.local/share/icons/checksum.png" &
            notifypid="$!"
            ;;
        2)
            kdialog --title "Checksum: $3 in progress.." --passivepopup \
                                        "(click here to hide this notification)" 5
            ;;
    esac
}


log_out() {
    if [[ -s "$errlog" ]]; then
        echo "" >> "$tmpfile"
        echo "THERE WERE ERRORS!" >> "$tmpfile"
        echo "" >> "$tmpfile"
        cat "$errlog" >> "$tmpfile"
    else
        echo "" >> "$tmpfile"
        echo "All checksums 100% AOK!" >> "$tmpfile"
    fi
}


TestDirWrite() {
    tname="$1.$(date +%s%N).test"
    echo "write***test" > "$tname"
    wtest=$(cat "$tname")
    rm -rf "$tname"
    if [[ $wtest = "write***test" ]]; then
        rodir=0
    else
        rodir=1
    fi
    return 0
} > /dev/null 2>&1 || true


# Setup user preferences..
#


# Dialog mode..
case "$me" in
    k*) # kchecksum, kverify, etc.
        # we will use kdialog..
        dlg_mode=2
        ;;
    g*) # gchecksum, gverify, etc.
        # we will use zenity..
        dlg_mode=1
        ;;
    *) # checksum, verify, etc.
        # no dialogs. output to stdout..
        dlg_mode=0
        ;;
esac

##    hybrid mode => dlg_mode=3
## We simply add an " | 3" wherever we want to apply SuperFandangoHybridGUIElements.
## If you'd prefer *your* hybrid system to be different, simply switch those around.


# runtime [symlink]overrides..
#
case "$me" in
    # the first condition is for users who have sha* hashing set as the default, but wish
    # to override by the use of an MD5-specific symlink, e.g. "kchecksum-md5 movie.avi"
     *md5) algorithm="md5" ;;
    *sha1) algorithm="sha1" ;;
    *sha2) algorithm="sha2" ;;
    *sha3) algorithm="sha3" ;;
    *sha5) algorithm="sha5" ;;
esac


# grab any command-line switches..
#
while [[ $# -gt 0 ]]; do
    case "$1" in
        --kde)                dlg_mode=2 ;;
        --zenity | --zen)    force_zenity=1 ;;
        --hybrid)            hybrid=1 ;;
        --xl)                extensionless=1 ;;
        --append)            append=1 ;;
        --noappend)            append=0 ;;
        --algo)                hashext="algo" ;;
        --md5)                 algorithm="md5" ;;
        --sha | --sha1)        algorithm="sha1" ;;
        --sha2)                algorithm="sha2" ;;
        --sha3)                algorithm="sha3" ;;
        --sha5)                algorithm="sha5" ;;
        --mask*)            mask="$1" ;;
    esac
    shift
done

## NOW we can set the user's default Hashing Algorithm....
SwitchAlgo "$algorithm"

# During folder/tree verification, we shall consider these types to be hash files..
# it's down here so that "average" users don't mess with it.
#
hash_types=([0]="md5" [1]="sha1" [2]="sha2" [3]="sha3" [4]="sha5")
#
# NOTE: if the number of entries in this list gets alterered, you must also alter the
# find command which uses it, below - simply search this script for "{hash_types[0]}".


# just in case they can't read..
for i in "${hash_types[@]}"; do
    if [[ $i = "$hashext" ]]; then
        # No! I will not set it to $algo! Yet..
        hashext="algo"
        break
    fi
done

# special extension setting:
# match checksum file extension to algorithm used (alternative to generic extension)..
[[ "$hashext" = "algo" ]] && hashext="$algo"


# Ignore types..
# An array of file types (by extension) to skip when creating hash files.
# we simply concatenate the hash types array with the user's ignore types array..
ignore_extensions=("$hashext" "${hash_types[@]}" "${ignore_extensions[@]}")



## GUI setup.
#

# basic checks for hybrid gui mode
# (but not if called from the shell with no k|g* symlink)..
if [[ $hybrid -eq 1 ]] && [[ $dlg_mode -ne 0 ]]; then
    dlg_mode=3;
    [[ ! -x /usr/bin/zenity ]] && dlg_mode=2;
    [[ ! -x /usr/bin/kdialog ]] && dlg_mode=1;
fi

# force zenity dialogs..
[[ $force_zenity -eq 1 ]] && [[ $dlg_mode -eq 2 ]] && dlg_mode=1

# kdialog is missing, fall-back to zenity..
[[ ! -x /usr/bin/kdialog ]] && [[ $dlg_mode -eq 2 ]] && dlg_mode=1

# zenity is missing, fall-back to kdialog. If kdialog is missing, fall-back to stdout..
if [[ ! -x /usr/bin/zenity ]] && [[ $dlg_mode -eq 1 ]]; then
    if [[ -x /usr/bin/kdialog ]]; then
        dlg_mode=2
    else
        dlg_mode=0
        echo "***    NOTE    ***"
        echo "Checksum can use zentiy to display dialogs in your desktop. "
        echo "To install zenity (on a debian-based system, like Ubuntu) do:"
        echo
        echo " sudo apt-get install zenity"
        echo
        echo "For other systems, check your package manager's man page, or"
        echo "compile from source. See: http://freshmeat.net/projects/zenity"
        echo
        #exit 7
    fi
fi


# are we creating or verifying?
case "$me" in
    checksum* | kchecksum* | gchecksum*)
        mode="create"
        usg_str=""
        ;;
    verify* | kverify* | gverify*)
        mode="verify"
        usg_str="Hash "
        ;;
esac

[[ "$mode" = '' ]] && exit 2

# more user error!..
if [[ "$path" = "$0" ]] || [[ ! -a "$path" ]]; then
    msg='*** NO PATH WAS SPECIFIED! ***'
    gmsg='Something bad happened. Please reinstall checksum!'
    if [[ "$path" = "$0" ]]; then
        msg='*** NO SUCH PATH! ***'
        gmsg='Checksum got sent a non-existant path!!!'
    fi
    case $dlg_mode in

        0)
            echo
            echo "$msg"
            echo
            echo "Usage: To $mode hashes.."
            echo
            echo $me' <'$usg_str'File|Directory>'
            echo
            ;;
        1 | 3)
            zenity    --error --title 'Error!!!' --text "$gmsg" \
                --width=280 --timeout=20 --window-icon="$HOME/.local/share/icons/checksum.png"
            ;;
        2)
            kdialog --error "$gmsg" -title 'Checksum Error!                                                                          '
            ;;
    esac
    exit 1
fi


# still here? Okay, let's look at that path..
filename=${path##*/}
fileext=${filename##*.}

# was it a folder?
[[ -d "$path" ]] && folder=1

# just in case..
[[ "$fileext" = "$filename" ]]&& fileext=""

# create a hash of a hash file!?!..
if [[ -f "$path" ]] && [[ "$mode" = "create" ]]; then
    # if it looks like a duck..
    #2do.. use the types array..  if (inarray(foo,bar)).. I wish!
    if [[ "$fileext" = "$algo" ]] || [[ "$fileext" = "$hashext" ]]; then
        hasherr='This is a checksum file. You can verify it.'
        case $dlg_mode in

            0)
                echo "$hasherr"
                ;;
            1 | 3)
                zenity    --info --title "Hashes Of Hashes!" --text="$hasherr" --timeout=10 \
                        --window-icon="$HOME/.local/share/icons/checksum.png"
                ;;
            2)
                # kDialog likes to post " - kDialog" on every window title *grrr*
                kdialog --msgbox "$hasherr"  --title "Checksum WHAT!?!                                                           "
                ;;
        esac
        exit 11
    fi
fi



## Let's CREATE!
#



if [[ "$mode" = "create" ]]; then

    if [[ $folder -eq 1 ]]; then
        if [[ "${path%/**/quot; == "$path" ]]; then    # "local" shell usage.. cd foo && checksum bar
            hashfile="
$filename.$hashext"
            # switch the "original" path, for any fall-back hashing..
            opath="
$here/$path"
        else
            hashfile="
$path/$filename.$hashext"
        fi
        cd "
$path"
    else
        if [[ "
${path%/**/quot; == "$path" ]]; then
            #2do.. read-only single hash file..
            path="./"
            #opath="$here/$path"
        fi
        if [[ $extensionless -eq 1 ]]; then
            hashfile="${path%/**/${filename%\.*}.$hashext"
        else
            hashfile="${path%/**/$filename.$hashext"
        fi
        cd "${path%/*}"
    fi


    # no hash file, cool.
    #
    if [[ ! -f "$hashfile" ]]; then
    
        # but is the dir writeable?..
        TestDirWrite $hashfile
        [[ $rodir -eq 1 ]] && fb="$hashfile"

    # Hash File EXISTS! ..
    #
    else
        # The hash file is not writeable..
        if [[ ! -w "$hashfile" ]]; then
            # they may be hashing IN the fallback location! So we use a flagoid.
            fb="$hashfile"
        else
            # the hash file is writeable,. 
            if [[ $append -eq 1 ]]; then
                # build an array ofexisting files + hashes..
                grabhashes "$hashfile"
            else
                # Do they wish to overwrite it?
                case $dlg_mode in

                    0)
                        until [[ "$answer" != "" ]]; do
                            echo
                            echo 'HASH FILE EXISTS!'
                            echo 'Do you wish to overwrite it?'
                            echo
                            select answer in yes no; do
                                [[ "$answer" != 'yes' ]] && exit 9
                                break
                            done
                        done
                        echo
                        ;;
                    1)
                        zanswer=$(zenity --question --title='Checksum File Exists!!!' --width=280 \
                            --text="A checksum file already exists.\nShall I overwrite it?" \
                                    --timeout=10 --window-icon="$HOME/.local/share/icons/checksum.png")
                        [[ "$zanswer" ]] && exit 9
                        ;;
                    2 | 3)
                        kdialog \
                            --warningyesno "  A checksum file already exists.       \n  Shall I overwrite it?      " \
                            --dontagain 'checksum:do_overwrite' \
                            --title 'Checksum File Exists!!!                              '
                        ;;
                esac
            fi
        fi
    fi


    # using the fall-back location..
    if [[ "$fb" != 0 ]]; then
        [[ ! -d "$fallback_dir" ]] && mkdir "$fallback_dir"
        hashfile="$fallback_dir/${hashfile##*/}"

        # add more hashes to our existing() array..
        [[ -f "$hashfile" ]] && grabhashes "$hashfile"
    fi


    if [[ ! -f "$hashfile" ]] || [[ $append -eq 0 ]]; then
        # okay, let's start a new hash file..
        echo '# Checksum for Linux..' > "$hashfile"
        echo '# http://corz.org/linux/software/checksum/' >> "$hashfile"
        [[ "$fb" != 0 ]] && echo "# hashes continued from read-only checksum file @ $fb" >> "$hashfile"
    fi


    # One last check..
    if [[ ! -w "$hashfile" ]]; then
        echo 'I am having trouble creating the checksum file.'
        echo "Check the permission on your fallback location: $fallback_dir"
        exit 9
    fi


    # okay, we're good to go..
    notify_user "$hashfile" 'Creating' 'Hashing'

    if [[ $folder -eq 1 ]]; then
        # Kinda hackish, but fun..
        if [[ "$mask" != '' ]]; then
            fmask=
            mask="${mask##*=}"    # remove "--mask=" part
            until [[ "$mask" = ',' ]]; do
                tmask=$(echo "$mask" | cut -d\, -f1)
                fmask=" -iname '$tmask'$fmask"
                mask="${mask#*,}"
                # on the final pass we add a comma so the previous line removes the final mask next time around. ;o)
                [[ "$(echo "$mask" | cut -s -d\, -f1)" = "" ]] && mask="$mask,"
                [[ "$mask" != ',' ]] && fmask=" -o $fmask"
            done
        fi
        [[ "$fmask" != '' ]] && fmask="  \( $fmask \) "

        #2do.. investigate: add name exclusions to find??
        eval "find . -type f ${fmask} -print" | while read i; do

            # check for ignored file types..
            thisname="${i##*/}"
            thisext="${thisname##*.}"
            for j in "${ignore_extensions[@]}"; do
                if [[ $j = "$thisext" ]]; then
                    [[ $dlg_mode -eq 0 ]] && echo "*** ** skipping IGNORED file ** *** ${i##*/}"
                    continue 2
                fi
            done


            # if appending - check against algo array, and skip..
            if [[ $append -eq 1 ]]; then
                for xfile in "${existing[@]}"; do
                    if [[ "$xfile" = "$i" ]]; then
                    [[ $dlg_mode -eq 0 ]] && echo "*** ** skipping checked file ** *** ${i##*/}"
                    continue 2
                fi
                done
            fi
            ## NOTE: if using full paths in fall-back checksum file, ($i = relative path) #2do

            # fall-back has been set, we are in a new location - rewrite the hash paths?
            if [[ "$fb" != 0 ]]; then

                # this method will write FULL absolute paths into the fallback hash file..
                $tool -b "${opath}/${i#*/}"  2>>"$errlog" | tee -a "$hashfile"

#                 #and/or we could employ some kind of user policy..
#                 case $fb_method in    ### ?
#                     0)
#                         ;;
#                     1)
#                         ;;
#                     *)
#                         ;;
#                 esac


            else
                # regular write-access..
                $tool -b "${i}"  2>>"$errlog" | tee -a "$hashfile"
            fi
        done

    else
        # hash a single file..

        if [[ "$fb" != 0 ]]; then
            echo "fallback hash foo"
            $tool -b "${i}"  2>>"$errlog" | tee -a "$hashfile"
        else
            $tool -b "$filename"  2>>"$errlog" | tee -a "$hashfile"
        fi

        SetDialogHeight "$hashfile"
        #diag_height=150
    fi

    # this may be zenity, or may be hybrid, so put it first..
    [[ "$notifypid" -ne 0 ]] && kill "$notifypid"

    case $dlg_mode in

        0)
            echo
            echo 'All done with hashing.'
            echo
            ;;
        1)
            zenity    --info --title 'All Done!' --text='Checksum operation complete.    ' --timeout=10 \
                    --window-icon="$HOME/.local/share/icons/checksum.png"
            ;;
        2 | 3)
            kdialog --msgbox ' Checksum operation complete!    ' \
                --title 'Checksum Complete!                                                                    ' \
                --dontagain 'checksum:notify_when_complete'
            ;;
    esac




## VERIFY hashes..
#

else

    # Verify a folder..
    #

    if [[ $folder -eq 1 ]]; then

        notify_user "${path%/*}" 'Verifying' 'verification'

        cd "$path"
        find . -type f  \( -name "*.$hashext" -o -name "*.${hash_types[0]}" \
                -o -name "*.${hash_types[1]}" -o -name "*.${hash_types[2]}" \
                -o -name "*.${hash_types[3]}" -o -name "*.${hash_types[4]}" \) -print | while read i; do

                chkerror=0
                hashfname="${i##*/}"

                thishext="${hashfname##*.}"

                cd "$path/${i%/**/quot;
                echo "
$hashfname" >> "$tmpfile"

                if [[ "
$thishext" = "$hashext" ]]; then
                    CheckMultiHash "
$hashfname"
                else
                    SwitchAlgo "
$thishext"
                    cout=$($tool --check -- "
$hashfname" 2>>"$errlog") || chkerror=1
                    echo "
$cout" | tee -a "$tmpfile"
                fi

                if [[ "
$chkerror" == 1 ]]; then
                    echo "
IN: $i" >> "$errlog"
                    echo "
" >> "$errlog"
                fi

                echo "
" >> "$tmpfile"
            done

        if [[ -s "
$tmpfile" ]]; then
            echo "
" >> "$tmpfile"
            log_out
            SetDialogHeight "
$tmpfile"
        fi

        [[ "
$notifypid" -ne 0 ]] && kill "$notifypid"

        case $dlg_mode in

            1 | 3)
                if [[ -s "
$tmpfile" ]]; then
                    zenity --text-info --editable --filename="
$tmpfile" --title='Checksum Results..' \
                             --width=600 --height=$diag_height --window-icon="
$HOME/.local/share/icons/checksum.png"
                else
                    zenity    --info --title 'Nothing done!' --text='No checksum files were found!    ' --timeout=10 \
                            --window-icon="
$HOME/.local/share/icons/checksum.png"
                fi
                ;;
            2)
                if [[ -s "
$tmpfile" ]]; then
                    kdialog --textbox "
$tmpfile" 600 $diag_height    \
                        --title 'Checksum Results..                                                                                '
                else
                    kdialog --sorry 'No checksum files were found!' \
                        --title 'Nothing done!                                                                                          '
                fi
                ;;
        esac



    # Verify a hash file..
    #
    # While technically feasible to have some reusable function, I like to keep this
    # separate and deal with single files (and their notifications) slightly differently..
    #

    else

        cd "
${path%/**/quot;
        hashfile="$path"
        notify_user "$filename" 'Verifying' 'verification'

        # a .hash file..
        if [[ "${hashfile##*.}" = "$hashext" ]]; then
            CheckMultiHash "$hashfile"
        else
            # oldschool .md5/.sha1 file...
            SwitchAlgo "${hashfile##*.}"
            if [[ "$dlg_mode" -eq 0 ]]; then
                $tool --check -- "$hashfile" 2>>"$errlog"
            else
                $tool --check -- "$hashfile" >> $tmpfile 2>>"$errlog" || echo "IN: $hashfile" >> "$errlog"

            fi
        fi

        log_out
        SetDialogHeight "$tmpfile"
        [[ "$notifypid" -ne 0 ]] && kill "$notifypid"

        case $dlg_mode in

            0)
                echo
                echo 'All done with verification!'
                echo
                ;;
            1 | 3)
                zenity    --text-info --title='Checksum Results' --filename="$tmpfile" --width=600 \
                        --height=$diag_height --window-icon="$HOME/.local/share/icons/checksum.png" \
                                --text="Verifying hashes in \"$filename\". This may take a moment.."
                ;;
            2)
                kdialog --textbox $tmpfile 600 $diag_height --title 'Checksum Results..         '
                ;;
        esac

    fi


    if [[ "$keep_errlog" -eq 1 ]] && [[ -s "$errlog" ]]; then
        cp "$tmpfile" "${path%/**/checksum_errors [$(date)].log"
        cat "$errlog" >> "${path%/**/checksum_errors [$(date)].log"
    fi

fi


if [[ "$show_times" = 1 ]]; then
    finish_time=$(date +%s)
    echo "All done in $((finish_time - start_time)) seconds."
fi
exit 0


##
#    Context/Service/Action/Right-Click Menus..
#
#    To get the right-click action, simply create some.desktop files inside..
#
#    /home/<USER NAME>/.kde/share/kde4/services/ServiceMenus
#

##    Example..
#
# You could roll them into one single "checksum" action, though I prefer
# to keep more than one, with an eye on future improvements.
#
## This one is for regular files..
#
##!/home/cor/.kde/share/kde4/services/ServiceMenus/checksum.desktop
#
# # Simple checksum-like right-click "action" for files
# # (c) cor <inquire at corz d.t org> 2009
# # License: GPL.v3
#
# [Desktop Entry]
# Actions=MD5Checksum
# Encoding=UTF-8
# # assuming the icon is located at /home/<USERNAME>/.local/share/icons/checksum.<image extension>
# Icon=checksum
# MimeType=application/octet-stream;*/**//span>
# ServiceTypes=KonqPopupMenu/Plugin,*/**/span>
# Type=Service
# # checksum does its own startup notifications, no need for bouncing hashes..
# X-KDE-StartupNotify=false
# # comment out the next two lines to put this service into the "Actions" submenu.
# X-KDE-Priority=TopLevel
# # comment out just this next line to have the checksum command in the *main* menu.
# X-KDE-Submenu=Checksum
#
# [Desktop Action MD5Checksum]
# Name=Checksum (MD5)
# Icon=checksum
# #Exec=/bin/sh -c "kchecksum '%f'"
# # remove the logging if you like. It's deleted at every bootup, and is handy.
# Exec=/bin/sh -c "kchecksum '%f' >> /tmp/checksum.log"






## 2do..
#

# switch to $algodeep tools where applicable (*should* be faster)

# folder hashing with *individual* hashes (e.g. all files get inividual .hash)

# Silent operation (dlg_mode = -1)

# Notify when failures *may* have been caused by encountering a Windows checksum file
# (is there a CRLF in the file?) & perhaps an option to automatically convert those files on-the-fly.

# gui notification on errors? Beep, maybe?

# beep when done - handy when completion dialog has been disabled.

## Gnome desktop activation. I must install Gnome and see how it works there!

# multihash-in-one-go - user could have an array of chosen algos, e.g. multi=( "md5" "sha1" )
# when not multihashing, simply set it to $algo

# when user attempts to hash a single hash file - rather than redundant message (literally!)
# post some random easter egg, like "The tops of old socks make excellent hair-bands." <OK>
# maybe put the info too, in brackets.

# per-folder checksum files? Hmmm. Maybe.

# switch to bring up mini-options - basically file masks - maybe a presets list, though-[

# --choose flag, to bring up a dialog to choose file masks.
#    [default to "*.*"s perhaps, for clarity]

# hmmm.. would it be possible to pause and continue an operation?
#    or even continue a crashed (volume) verify and pick up from where we left off
#    (if the log wasn't in /tmp maybe. hmm)


## BUGS & Foibles..
#

"&" in the file name will mess up verify results dialogs (needs to be converted to &amp;)
# I assume other entities will need to be treated the same way. When this happens, a generic
# message is posted instead, and checksum otherwise acts as normal, so it's no biggie.

Welcome to corz.org!

If something isn't working, I'm probably improving it, try again in a minute. If it's still not working, please mail me!