corz.org text viewer..
[currently viewing: /public/machine/source/linux/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.
#
#        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!

Since switching hosts (I hope you are alright, Ed! Wherever you are …) quite a few things seems to be wonky.

Juggling two energetic boys (of very different ages) on Coronavirus lockdown, I'm unlikely to have them all fixed any time soon. Mail me! to prioritise!