#!/bin/sh
#
# flexishacheck
# Version 1.0
#
# by Apollia
# http://astroblahhh.com/
#
# License: GNU Affero GPL 3.0
# http://www.gnu.org/licenses/
#
#
# Completed July 25, 2016 at 7:34 AM EDT.
#
#
# Definitely works in Lucid Puppy Linux 5.2.8 version 004 -
# http://www.murga-linux.com/puppy/viewtopic.php?t=70855 -
# and Lighthouse 64 Puppy Linux 6.02 Beta 2.
# http://lhpup.org/
#
# Might have to be modified for other platforms.
#
# Requires the command "shasum".
#
#
#
# This software is free (as in freedom and as in price), libre, and open source!
#
# http://astroblahhh.com/what-is-free-libre-open-source-software
#
#
# -------
# Summary
# -------
#
# Makes it possible to check SHA checksums more flexibly, without having to be
# concerned at all about differences between the path of the file whose
# checksum you want to check, and the path specified inside a checksum record
# file.
#
# (However, this script only works with the first checksum in a checksum record
# file.)
#
#
# ---------
# Arguments
# ---------
#
# -a, --algorithm     [NUMBER]
#     The checksum algorithm which the "shasum" command will be told to use.
#
#     Valid values for NUMBER are:
#
#     1, 224, 256, 384, or 512 (which is the default)
#
#
# -h, --help
#     Some help text.
#
#
# -f, --file     [PATH]
#     A path (full/absolute, relative, or simply a filename) of a file whose
#     checksum you want to check.
#
#
# -r, --record     [PATH]
#     A path (full/absolute, relative, or simply a filename) of a checksum 
#     record file containing a single checksum.
#
#
# The only required arguments are --file and --record, but you can use
# positional arguments instead of switches.
#
# Argument #1 is equivalent to --file.
#
# Argument #2 is equivalent to --record.
#
# Argument #3 is equivalent to --algorithm.
#
#
# --------------------------------------------
# The Problems That Necessitated flexishacheck
# --------------------------------------------
#
# Traditionally (I guess), checksum record files (text files which serve as a
# record of a file's checksum) contain the checksum, then 2 spaces, then the
# filename - or worse yet, a longer path - of the file the checksum belongs to.
#
# The filename/filepath part can result in various annoying problems.  If you
# rename the checksummed file, the "shasum" command becomes incapable of using
# checksum record files containing the old filename.
#
# And if the checksum record file contains a longer path to a file instead of
# just a filename - then, even just moving the checksummed file to another
# folder without renaming the checksummed file makes the checksum record file
# unuseable by "shasum".
#
# And if you relocate a checksummed file, but the checksum record file only
# contains a filename rather than a path, then, "shasum" can only be made to
# work by jumping through some annoying hoops.  You have to change your
# working directory to the new parent directory of the checksummed file before
# you do this:
#
#     shasum --check "/annoyingly/long/full filepath to/ChecksumRecord.sha512"
#
#
# -----------------------
# What flexishacheck Does
# -----------------------
#
# With flexishacheck, the filename or path stored inside the checksum record
# file is totally irrelevant and ignored.
#
# Consequently, flexishacheck can compare any file's checksum to any checksum
# stored in any checksum record file - as long as that record only contains 
# one checksum.
#
# When run, flexishacheck creates a temporary checksum record file containing:
#
#    * The checksum contained in the first line of the checksum record file
#      you specified in Argument #2.
#
#    * The filepath you specified in Argument #1, which belongs to a file
#      whose checksum you wish to compare to the record's checksum.
#
# Then, flexishacheck tells shasum to do a check using the temporary checksum
# record file, and the checksum algorithm you optionally specified in
# Argument #3.  (Or 512 by default.)
#
#
#------------------------------------------------------------------------------
#
#
# Under the GNU Affero General Public License v3.0.
#
# Copyright (C) 2016  Apollia
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
# Contact info:     http://astroblahhh.com/contact-apollia.shtml
#
#-----


# 2016-07-25 07:11:46.  No settings are required to be changed below.


Folder____ApWorkspaces="/tmp/ApWorkspaces"
Folder____Temp_Checksum_Record="$Folder____ApWorkspaces/flexishacheck"
	# 2016-07-24 12:47:17.  The "mkdir" and "install" commands' ability
	# to set file permissions only affects the folder at the end of the path,
	# and not any of the created parent directories.
	#
	# So, below, "install" gets run twice to hopefully create all the folders with the
	# correct permissions.
	#
	# But if you modify $Folder____ApWorkspaces or 
	# $Folder____Temp_Checksum_Record and put the flexishacheck folder
	# at a deeper level, then, some of the parent folders might end up having
	# the wrong permissions.

Filename____Temp_Checksum_Record="TempChecksumRecord.$$"

Filepath____Temp_Checksum_Record="$Folder____Temp_Checksum_Record/$Filename____Temp_Checksum_Record"



#-------------------------------------------------------------------------------

This_Script_Name="flexishacheck"
This_Script_Version="1.0"


This_Script_Desc="Makes it possible to check SHA checksums more flexibly, without having to be concerned at all about differences between the path of the file whose checksum you want to check, and the path specified inside a checksum record file.

(However, this script only works with the first checksum in a checksum record file.)"


This_Script_Usage="Usage: $This_Script_Name [OPTIONS] [File] [Checksum record text file]

-a, --algorithm [NUMBER]
The checksum algorithm which the \"shasum\" command will be told to use.

Valid values for NUMBER are: 1, 224, 256, 384, or 512 (which is the default)

-h, --help        Some help text.

-f, --file [PATH]
A path (full/absolute, relative, or simply a filename) of a file whose checksum you want to check.

-r, --record [PATH]
A path (full/absolute, relative, or simply a filename) of a checksum record file containing a single checksum.


Or, you can use positional arguments instead.

Arg #1 = --file     Arg #2 = --record     Arg #3 = --algorithm"



########
# Function
Help_Message()
{
	echo "$This_Script_Name $This_Script_Version - $This_Script_Desc


$This_Script_Usage"
}
#readonly -f Help_Message

# End of
# Function
########


#-------------------------------------------------------------------------------
#
# Slightly modified getopt code from:
# http://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash/29754866#29754866
#
# Thanks to the authors, especially for the tip about using eval to properly handle quoting!

PARSED_OPTIONS=$(getopt --alternative --options h,a:,r:,f: --longoptions help,algorithm:,record:,file: --name "$This_Script_Name" -- "$@" )

if [ $? != 0 ]
then
	# then getopt has complained about wrong arguments to stdout
	echo
	Help_Message
	
	
	#!!!!
	exit 1
fi

eval set -- "$PARSED_OPTIONS"
# Use eval with "$PARSED_OPTIONS" to properly handle the quoting.


for arg
do
	case "$arg" in
	
		-a|--algorithm) 
			Arg____Algorithm="$2"
			shift
			shift
			;;


		-f|--file)
			Arg____File="$2"
			shift 
			shift
			;;
		
		-h|--help)
			Help_Message
			exit 0
			;;
			
		-r|--record) 
			Arg____Record="$2"
			shift
			shift
			;;
		--)
			#echo "Dashes!"
			shift
			;;
	esac
done


# 2016-07-25 07:14:56.  Now, the positional args (if there are any):

ARG1=${@:$OPTIND:1}
ARG2=${@:$OPTIND+1:1}
ARG3=${@:$OPTIND+2:1}


# 11:30 07/24/2016.  The below are basically abbreviated if/then statements.
#
# If ARG1 is not empty, then Arg____File gets set to $ARG1.  Etc.

[ ! -z "$ARG1" ] && Arg____File="$ARG1"
[ ! -z "$ARG2" ] && Arg____Record="$ARG2"
[ ! -z "$ARG3" ] && Arg____Algorithm="$ARG3"


#-------------------------------------------------------------------------------


########
# Function
Do_Arg_Valicheck____Algorithm()
{
	local arg="$1"
	
	
	##############################

	
	case "$arg" in
	
		1|224|256|384|512)
			
			
			#!!!!
			return 0
			;;
			
		*)
			
			
			#!!!!
			return 1
			;;
	esac
}
#readonly -f Do_Arg_Valicheck____Algorithm

# End of
# Function
########



########
# Function
Abort_Because_Algorithm_Arg_Was_Invalid()
{
	local arg="$1"
	
	
	##############################
	
	echo "$This_Script_Name error: The optional --algorithm argument (or -a or Arg #3) was not one of these values:

1 224 256 384 512"


	#!!!!
	exit 1
}
#readonly -f Abort_Because_Algorithm_Arg_Was_Invalid

# End of
# Function
########



if [ ! -z "$Arg____Algorithm" ]
then 
	Do_Arg_Valicheck____Algorithm "$Arg____Algorithm"


	#!!!!
	[ "$?" != 0 ] && Abort_Because_Algorithm_Arg_Was_Invalid "$Arg____Algorithm"
else
	Arg____Algorithm=512
fi


#-------------------------------------------------------------------------------



########
# Function
Abort_Because_Arg_Must_Be_A_Path_Leading_To_A_File()
{
	local arg_type="$1"
	local arg="$2"
	
	
	##############################
	
	
	case "$arg_type" in
	
		"file") 
			echo "$This_Script_Name error: The --file argument (or -f or Arg #1) must be a path which leads to a file!"
			;;
			
		"record")
			echo "$This_Script_Name error: The --record argument (or -r or Arg #2) must be a path which leads to a file!"
			;;
	esac


	#!!!!
	exit 2
}
#readonly -f Abort_Because_Arg_Must_Be_A_Path_Leading_To_A_File

# End of
# Function
########


# 11:45 07/24/2016.  If (whatever) is not a file, then abort.


#!!!!
[ ! -f "$Arg____File" ] && Abort_Because_Arg_Must_Be_A_Path_Leading_To_A_File "file" "$Arg____File"


#!!!!
[ ! -f "$Arg____Record" ] && Abort_Because_Arg_Must_Be_A_Path_Leading_To_A_File "record" "$Arg____Record"



#-------------------------------------------------------------------------------


#echo "File: $Arg____File  Record: $Arg____Record  Algorithm: $Arg____Algorithm"


#-------------------------------------------------------------------------------



########
# Function
Find_Out_If_Checksum_is_Useable()
{
	local checksum="$1"
	local algorithm="$2"
	
	#@@@@
	local checksum_length=${#checksum}
	
	
	##############################
	

	case "$algorithm" in
		512)
			#!!!!
			[ "$checksum_length" = 128 ] && return 0			;;

		1)
			#!!!!
			[ "$checksum_length" = 40 ] && return 0			;;

		224)
			#!!!!
			[ "$checksum_length" = 56 ] && return 0			;;

		256)
			#!!!!
			[ "$checksum_length" = 64 ] && return 0			;;

		384)
			#!!!!
			[ "$checksum_length" = 96 ] && return 0			;;
	esac
	
	
	#!!!!
	return 1
}
#readonly -f Find_Out_If_Checksum_is_Useable

# End of
# Function
########



########
# Function
Abort_Because_Record_Contained_Unuseable_Checksum()
{
	local checksum="$1"
	local record="$2"
	local algorithm="$3"
	
	
	##############################
	
	echo "$This_Script_Name error: Didn't do check because the record '$record' contained this unuseable checksum which doesn't appear to use the SHA-$algorithm algorithm:
	
$checksum"
	
	
	#!!!!
	exit 3
}
#readonly -f Abort_Because_Record_Contained_Unuseable_Checksum

# End of
# Function
########


# 11:56 07/24/2016.  Now getting checksum from the 1st line of the record file.
checksum=$(cat "$Arg____Record" | head -1 | cut --delimiter " " --field=1)


Find_Out_If_Checksum_is_Useable "$checksum" "$Arg____Algorithm"


#!!!!
[ "$?" != 0 ] && Abort_Because_Record_Contained_Unuseable_Checksum "$checksum" "$Arg____Record" "$Arg____Algorithm"



#-------------------------------------------------------------------------------



########
# Function
Abort_Because_Could_Not_Create_Folder()
{
	local folder="$1"
	
	
	##############################
	
	
	echo
	echo "$This_Script_Name error: Somehow it wasn't possible to create this folder:

	$Folder____Temp_Checksum_Record"
	
	
	#!!!!
	exit 4
}
#readonly -f Abort_Because_Could_Not_Create_Folder

# End of
# Function
########



if [ ! -e "$Folder____Temp_Checksum_Record" ]
then
	# 2016-07-24 12:52:17.  Only Owner gets Read/Write/Execute permissions
	# for these folders.
	

	#!!!!
	install --mode 700 --directory "$Folder____ApWorkspaces"
	[ "$?" != 0 ] && Abort_Because_Could_Not_Create_Folder "$Folder____ApWorkspaces"


	#!!!!	
	install --mode 700 --directory "$Folder____Temp_Checksum_Record"
	[ "$?" != 0 ] && Abort_Because_Could_Not_Create_Folder "$Folder____Temp_Checksum_Record"
fi



#-------------------------------------------------------------------------------



########
# Function
Abort_Because_Could_Not_Create_Temp_Checksum_Record()
{
	local temp_record="$1"
	
	
	##############################
	
	echo
	echo "$This_Script_Name error: Somehow it wasn't possible to create this file:

	$Filepath____Temp_Checksum_Record"
	
	
	#!!!!
	exit 5
}
#readonly -f Abort_Because_Could_Not_Create_Temp_Checksum_Record

# End of
# Function
########



# 2016-07-24 12:40:01.  Creates an empty file with only Owner:Read/Write
# permissions.

install --mode 600 /dev/null "$Filepath____Temp_Checksum_Record"


#!!!!
[ "$?" != 0 ] && Abort_Because_Could_Not_Create_Temp_Checksum_Record "$Filepath____Temp_Checksum_Record"


# 2016-07-24 12:41:33.  Now the file gets data added to it.

echo "$checksum  $Arg____File" > "$Filepath____Temp_Checksum_Record"


#!!!!
[ "$?" != 0 ] && Abort_Because_Could_Not_Create_Temp_Checksum_Record "$Filepath____Temp_Checksum_Record"



#-------------------------------------------------------------------------------

shasum --check "$Filepath____Temp_Checksum_Record" --algorithm "$Arg____Algorithm"

result_code="$?"


rm "$Filepath____Temp_Checksum_Record"


#!!!!
exit "$result_code"