Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- # (C) 2020 Zefie Networks
- # This script scans for .rar files in a directory, and runs a test on them using the unrar command
- # This script requires the nonfree version of unrar and is only tested with v5
- # The results of the scan will be saved in a file (defined by Z_TESTDAT below)
- # The file is designed to be both human readable and readable by this script, so do not modify
- # the output file except to delete a line to force revalidation (DOS/Unix linefeeds shouldnt matter).
- # This script will stop if it reaches a damaged RAR if Z_ABORT_ON_ERROR is defined.
- # This script will only validate files that have either not been checked yet, modified since the last check,
- # or it has been Z_EXPIRE_TIME since the last validation.
- # CONFIG
- # How long since the last successful validation before we validate the file again?
- Z_EXPIRE_TIME="250:0:0:0" # days:hours:minutes:seconds
- # Filename for test metadata, will be placed in directory specified to scan
- # Layout is tab-delimited, as follows
- # *** Date Validated Test Status Recovery Record Status RAR Size RAW Size Filename
- Z_TESTDAT_FILENAME="rar_test_status.txt"
- # You can change these if you want but be aware of tabbing and cosmetic issues
- Z_OKSTR="PASSED (OK)"
- Z_RECOVERY_RECORD_OKSTR="Has Recovery Record"
- Z_RECOVERY_RECORD_ERRSTR="NO RECOVERY RECORD"
- Z_DATESTR="%Y-%m-%d %H:%M:%S"
- Z_DATESTR_DAY="%A, %B %d %Y"
- # Comment this out to continue scanning even if a rar fails to validate
- Z_ABORT_ON_ERROR=1
- # Uncomment this out to fail if a rar does not have a recovery record
- #Z_ABORT_ON_NRR=1
- # END EZ CONFIG
- Z_SCRIPTDIR="$(realpath "$(dirname "${0}")")"
- Z_ERRCNT=0
- Z_ERRRCNT=0
- Z_NRRCNT=0
- Z_MISSING=0
- # shellcheck source=/home/zefie/bin/ansihelper.sh
- source "${Z_SCRIPTDIR}/ansihelper.sh"
- if [ -z "${1}" ]; then
- # script requires an arg to run
- echo "Usage: ${0} <directory> [directory ...]"
- exit 1;
- fi
- if [ "${1}" == "-nf" ]; then
- unset Z_ABORT_ON_ERROR;
- shift;
- fi
- for arg in "${@}"; do
- if [ ! -d "${arg}" ]; then
- echo "ERR: Not a directory: \"${1}\""
- fi
- done
- # Intercept ^C and finish up cleanly instead of getting stuck in the loop
- trap ctrl_c INT
- function ctrl_c() {
- echo "** ^C pressed, aborting script, please wait..."
- export Z_ABORT=1;
- }
- function z_chk_if_single_or_part1_rar() {
- unset Z_MULTIPART_RAR
- # Check if a single part rar, or if part1 of a multipart, returns 1 if not.
- if [ "$(echo "${1}" | grep -Eic '\.part[0-9]{0,3}\.rar$')" -gt 0 ]; then
- if [ "$(echo "${1}" | grep -Eic '\.part[0]{0,2}1\.rar$')" -eq 0 ]; then
- return 1;
- fi
- export Z_MULTIPART_RAR=1
- fi
- }
- function bytesToHuman() {
- # Simple 'friendlyBytes' from stackoverflow
- b=${1:-0}; d=''; s=0; S=(Bytes {K,M,G,T,P,E,Z,Y}iB)
- while ((b > 1024)); do
- d="$(printf ".%02d" $((b % 1024 * 100 / 1024)))"
- b=$((b / 1024))
- (( s++ ))
- done
- echo "$b$d ${S[$s]}"
- }
- function z_strip_empty_array() {
- # Usage: z_strip_empty_array $arrayname $thearray
- # (Re)defines the array of $arrayname with the contents of $thearray, but without null entries (and re-sorted)
- local Z_ARRY="${1}"
- shift
- eval "declare -a ${Z_ARRY}"
- for i in "${@}"; do
- if [ -z "$i" ]; then
- continue
- fi
- eval "${Z_ARRY}+=(\"${i}\")"
- done
- }
- function z_strip_from_txt() {
- # Rebuilds text file $2 without the lines containing $1
- local Z_TMP
- Z_TMP="$(mktemp)"
- grep -Fv "${1}" "${2}" > "${Z_TMP}"
- cat "${Z_TMP}" > "${2}"
- rm "${Z_TMP}"
- }
- function z_fileheader() {
- # Rebuilds the text header (starting with ***) without destroying the file contents
- # No args, replace $Z_TESTDAT with $1 to make into a arg type function
- # You can add extra tabs for formatting (eg \t\t) without breaking the script, thanks to z_strip_empty_array
- local Z_TMP Z_EXPIRE
- Z_TMP="$(mktemp)"
- Z_EXPIRE=$(("$(date -u +%s)" + "$(echo -n "${Z_EXPIRE_TIME}" | awk -F: '{ print ($1 * 86400) + ($2 * 3600) + ($3 * 60) + $4 }')"))
- if [ -f "${1}" ]; then
- grep -Fv "***" "${1}" > "${Z_TMP}"
- fi
- echo "*** RAR Integrty Test Status for ${Z_DIR} (Times in UTC/GMT) ***" > "${1}"
- {
- echo "*** Last Updated: $(date -u "+${Z_DATESTR}") (Suggested Revalidation: $(date -u --date="@${Z_EXPIRE}" "+${Z_DATESTR_DAY}")) ***"
- echo -e '*** Date Validated\tTest Status\tRecovery Record Status\tRAR Size\tRAW Size\tFile Last Modified\tFilename'
- cat "${Z_TMP}"
- } >> "${1}"
- rm "${Z_TMP}"
- }
- function z_chk_abort() {
- # If the abort flag has been set prior, exit now
- if [ ! -z "${Z_ABORT}" ]; then
- exit "${Z_ABORT}";
- fi
- }
- function do_rar_test() {
- z_chk_abort
- local Z_STATUS="UNKNOWN ERR"
- local Z_STATUS_C="yellow"
- local Z_REC_C="yellow"
- local Z_RRES Z_TMP Z_REC Z_COMPSIZE Z_RAWSIZE Z_LASTMOD Z_REC_REC Z_REC_RES;
- # Check for recovery record
- Z_REC_REC="$(unrar l "${1}" | grep "recovery record" -c)"
- Z_REC_RES=$?
- if [ ${Z_REC_RES} -ne 0 ] || [ "${Z_REC_REC}" -lt 1 ]; then
- Z_REC="${Z_RECOVERY_RECORD_ERRSTR}"
- Z_NRRCNT=$((Z_ERRCNT + 1))
- if [ ! -z "${Z_ABORT_ON_NRR}" ]; then
- Z_ABORT=1
- fi
- Z_REC_C="red"
- else
- Z_REC="${Z_RECOVERY_RECORD_OKSTR}"
- Z_REC_C="green"
- fi
- # Test with unrar (7z/7za/p7zip will return errcode 0 on corrupt file and cannot be used)
- unrar t "${1}"
- Z_RRES=$?
- if [ ${Z_RRES} -eq 0 ]; then
- Z_STATUS="${Z_OKSTR}"
- Z_STATUS_C="green"
- else
- case ${Z_RRES} in
- 255)
- # When user hits ^C
- Z_STATUS="FAILED (ABORT)"
- Z_ABORT=1
- ;;
- 10)
- Z_STATUS="FAILED (NOTRAR)"
- ;;
- 3)
- if [ "${Z_REC}" == "${Z_RECOVERY_RECORD_OKSTR}" ]; then
- # corrupt and has recovery record
- Z_STATUS="NEEDS REPAIR"
- Z_ERRRCNT=$((Z_ERRRCNT))
- else
- # corrupt and does NOT have recovery record
- Z_STATUS="FAILED (CORRUPT)"
- fi
- ;;
- *)
- # catchall for unknown error codes
- Z_STATUS="FAILED (${Z_RRES})"
- ;;
- esac
- if [ ! -z "${Z_ABORT_ON_ERROR}" ]; then
- Z_ABORT=1
- fi
- Z_STATUS_C="red"
- Z_ERRCNT=$((Z_ERRCNT + 1))
- fi
- z_strip_from_txt "${1}" "${2}"
- z_fileheader "${2}"
- if [ -z "${Z_MULTIPART_RAR}" ]; then
- # Use stat to get filesize of a single file
- Z_COMPSIZE="$(bytesToHuman "$(stat -c %s "${1}")")"
- else
- # Use stat to get the size of all the rar parts, and add them together
- Z_MULTIRAR_PREFIX="$(echo "${1}" | rev | cut -d'.' -f3- | rev).part"
- Z_COMPSIZE=0
- for fss in $(stat -c %s "${Z_MULTIRAR_PREFIX}"*); do
- Z_COMPSIZE=$(( Z_COMPSIZE + fss ))
- done
- Z_COMPSIZE=$(bytesToHuman ${Z_COMPSIZE})
- fi
- Z_RAWSIZE="$(bytesToHuman "$(unrar l "${1}" | tail -n2 | head -n1 | awk ' { print $1 } ')")"
- Z_LASTMOD="$(date -u "+${Z_DATESTR}" --date="@$(stat -c "%Y" "${1}")")"
- echo -e "$(date -u "+${Z_DATESTR}")\\t${Z_STATUS}\\t${Z_REC}\\t${Z_COMPSIZE}\\t${Z_RAWSIZE}\\t${Z_LASTMOD}\\t${1}" >> "${2}"
- echo -e "$(str_color "${Z_STATUS_C}" "${Z_STATUS}")\\t$(str_color "${Z_REC_C}" "${Z_REC}")\\t${1}"
- }
- function z_process_directory() {
- local Z_DIR Z_TESTDAT Z_TS Z_EXPIRE Z_LASTMOD Z_RES Z_PWD Z_MKTEMP;
- Z_DIR="$(realpath "${1}")"
- Z_PWD="${PWD}"
- Z_TESTDAT="${Z_DIR}/${Z_TESTDAT_FILENAME}"
- cd "${Z_DIR}" || (echo "Could not enter directory: ${Z_DIR}" && exit 1)
- while IFS=$'\n' read -r -d '' file
- do
- z_chk_abort
- z_chk_if_single_or_part1_rar "${file}" || continue;
- # if the test result file doesn't exist
- if [ ! -f "${Z_TESTDAT}" ]; then
- # then create it and insert the header
- touch "${Z_TESTDAT}" || exit 1;
- z_fileheader "${Z_TESTDAT}"
- fi
- # scan the test result file for this filename
- IFS=$'\t' read -r -a Z_RES <<< "$(grep -F "${file}" "${Z_TESTDAT}")"
- z_strip_empty_array Z_RES "${Z_RES[@]}";
- # if there was a match from the test result file
- if [ ${#Z_RES[@]} -gt 2 ]; then
- # get the stored information and ch eck it
- Z_TS="$(date -u --date="${Z_RES[0]}" +%s 2>/dev/null || echo -n "${Z_RES[0]}")"
- Z_EXPIRE=$(("${Z_TS}" + "$(echo -n "${Z_EXPIRE_TIME}" | awk -F: '{ print ($1 * 86400) + ($2 * 3600) + ($3 * 60) + $4 }')"))
- Z_LASTMOD="$(date -u "+${Z_DATESTR}" --date="@$(stat -c "%Y" "${file}")")"
- # if the expire time elapsed, the last test wasn't ok, or the modified time changed
- if [ "$(date -u +%s)" -gt "${Z_EXPIRE}" ] || [ "${Z_RES[1]}" != "${Z_OKSTR}" ] || [ "${Z_LASTMOD}" != "${Z_RES[5]}" ]; then
- # then validate the file
- do_rar_test "${file}" "${Z_TESTDAT}"
- else
- # scan status file for any rars that don't have recovery records and count them
- if [ "${Z_RES[2]}" != "${Z_RECOVERY_RECORD_OKSTR}" ]; then
- Z_NRRCNT=$((Z_NRRCNT + 1))
- fi
- fi
- else
- # otherwise validate the file
- do_rar_test "${file}" "${Z_TESTDAT}"
- fi
- done < <(find . -name '*.rar' -print0);
- Z_MKTEMP=$(mktemp)
- while IFS=$'\n' read -r line
- do
- z_chk_abort
- if [ "$(echo "${line}" | grep -F "***" -c)" -gt 0 ]; then
- echo "${line}" >> "${Z_MKTEMP}"
- continue;
- fi
- IFS=$'\t' read -r -a Z_TRES <<< "${line}"
- if [ ! -z "${Z_TRES[6]}" ]; then
- if [ ! -f "${Z_TRES[6]}" ]; then
- Z_MISSING=$((Z_MISSING + 1))
- else
- echo "${line}" >> "${Z_MKTEMP}"
- fi
- fi
- done < "${Z_TESTDAT}";
- if [ "${Z_MISSING}" -gt 0 ]; then
- cat "${Z_MKTEMP}" > "${Z_TESTDAT}"
- fi
- rm "${Z_MKTEMP}"
- cd "${Z_PWD}" || (echo "Could not return to original directory: ${Z_PWD}" && exit 1)
- if [ "${Z_ERRCNT}" -eq 0 ] && [ "${Z_NRRCNT}" -eq 0 ]; then
- # all good
- echo "All $(str_color green "${Z_OKSTR}") for ${Z_DIR}"
- else
- echo "$(str_color red "Error report") for ${Z_DIR}:"
- local Z_ERRRCNT_STR;
- if [ "${Z_ERRRCNT}" -eq "${Z_ERRCNT}" ]; then
- Z_ERRRCNT_STR="$(str_color green all)";
- elif [ "${Z_ERRRCNT}" -gt 0 ]; then
- Z_ERRRCNT_STR="$(str_color yellow "${Z_ERRRCNT}")"
- else
- Z_ERRRCNT_STR="$(str_color red none)"
- fi
- if [ "${Z_ERRCNT}" -gt 0 ]; then
- echo "$(str_color red "${Z_ERRCNT} file(s) failed") to validate (of those, ${Z_ERRRCNT_STR} of them can be repaired)."
- fi
- if [ "${Z_NRRCNT}" -gt 0 ]; then
- echo "$(str_color yellow "${Z_NRRCNT} file(s)") do not have recovery records."
- fi
- fi
- if [ "${Z_MISSING}" -gt 0 ]; then
- echo "$(str_color yellow "Removed ${Z_MISSING} missing file(s)") from $(str_color cyan "${Z_TESTDAT}")"
- fi
- }
- for dir in "${@}"; do
- z_chk_abort
- z_process_directory "${dir}"
- done
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement