Advertisement
zefie

script I wrote to verify RAR files (testrars)

Apr 22nd, 2020
1,006
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 10.29 KB | None | 0 0
  1. #!/bin/bash
  2.  
  3. # (C) 2020 Zefie Networks
  4.  
  5. # This script scans for .rar files in a directory, and runs a test on them using the unrar command
  6. # This script requires the nonfree version of unrar and is only tested with v5
  7.  
  8. # The results of the scan will be saved in a file (defined by Z_TESTDAT below)
  9.  
  10. # The file is designed to be both human readable and readable by this script, so do not modify
  11. # the output file except to delete a line to force revalidation (DOS/Unix linefeeds shouldnt matter).
  12.  
  13. # This script will stop if it reaches a damaged RAR if Z_ABORT_ON_ERROR is defined.
  14.  
  15. # This script will only validate files that have either not been checked yet, modified since the last check,
  16. # or it has been Z_EXPIRE_TIME since the last validation.
  17.  
  18.  
  19. # CONFIG
  20.  
  21. # How long since the last successful validation before we validate the file again?
  22. Z_EXPIRE_TIME="250:0:0:0" # days:hours:minutes:seconds
  23.  
  24. # Filename for test metadata, will be placed in directory specified to scan
  25. # Layout is tab-delimited, as follows
  26. # *** Date Validated    Test Status Recovery Record Status  RAR Size    RAW Size    Filename
  27. Z_TESTDAT_FILENAME="rar_test_status.txt"
  28.  
  29. # You can change these if you want but be aware of tabbing and cosmetic issues
  30. Z_OKSTR="PASSED (OK)"
  31. Z_RECOVERY_RECORD_OKSTR="Has Recovery Record"
  32. Z_RECOVERY_RECORD_ERRSTR="NO RECOVERY RECORD"
  33. Z_DATESTR="%Y-%m-%d %H:%M:%S"
  34. Z_DATESTR_DAY="%A, %B %d %Y"
  35.  
  36. # Comment this out to continue scanning even if a rar fails to validate
  37. Z_ABORT_ON_ERROR=1
  38. # Uncomment this out to fail if a rar does not have a recovery record
  39. #Z_ABORT_ON_NRR=1
  40.  
  41. # END EZ CONFIG
  42.  
  43.  
  44.  
  45.  
  46.  
  47. Z_SCRIPTDIR="$(realpath "$(dirname "${0}")")"
  48. Z_ERRCNT=0
  49. Z_ERRRCNT=0
  50. Z_NRRCNT=0
  51. Z_MISSING=0
  52.  
  53. # shellcheck source=/home/zefie/bin/ansihelper.sh
  54. source "${Z_SCRIPTDIR}/ansihelper.sh"
  55.  
  56. if [ -z "${1}" ]; then
  57.     # script requires an arg to run
  58.     echo "Usage: ${0} <directory> [directory ...]"
  59.     exit 1;
  60. fi
  61.  
  62. if [ "${1}" == "-nf" ]; then
  63.     unset Z_ABORT_ON_ERROR;
  64.     shift;
  65. fi
  66.  
  67. for arg in "${@}"; do
  68.     if [ ! -d "${arg}" ]; then
  69.         echo "ERR: Not a directory: \"${1}\""
  70.     fi
  71. done
  72.  
  73. # Intercept ^C and finish up cleanly instead of getting stuck in the loop
  74. trap ctrl_c INT
  75. function ctrl_c() {
  76.        echo "** ^C pressed, aborting script, please wait..."
  77.     export Z_ABORT=1;
  78. }
  79.  
  80. function z_chk_if_single_or_part1_rar() {
  81.     unset Z_MULTIPART_RAR
  82.     # Check if a single part rar, or if part1 of a multipart, returns 1 if not.
  83.     if [ "$(echo "${1}" | grep -Eic '\.part[0-9]{0,3}\.rar$')" -gt 0 ]; then
  84.         if [ "$(echo "${1}" | grep -Eic '\.part[0]{0,2}1\.rar$')" -eq 0 ]; then
  85.             return 1;
  86.         fi
  87.         export Z_MULTIPART_RAR=1
  88.     fi
  89. }
  90.  
  91. function bytesToHuman() {
  92.    # Simple 'friendlyBytes' from stackoverflow
  93.    b=${1:-0}; d=''; s=0; S=(Bytes {K,M,G,T,P,E,Z,Y}iB)
  94.    while ((b > 1024)); do
  95.        d="$(printf ".%02d" $((b % 1024 * 100 / 1024)))"
  96.        b=$((b / 1024))
  97.        (( s++ ))
  98.    done
  99.    echo "$b$d ${S[$s]}"
  100. }
  101.  
  102. function z_strip_empty_array() {
  103.     # Usage: z_strip_empty_array $arrayname $thearray
  104.     # (Re)defines the array of $arrayname with the contents of $thearray, but without null entries (and re-sorted)
  105.     local Z_ARRY="${1}"
  106.     shift
  107.     eval "declare -a ${Z_ARRY}"
  108.     for i in "${@}"; do
  109.        if [ -z "$i" ]; then
  110.          continue
  111.        fi
  112.        eval "${Z_ARRY}+=(\"${i}\")"
  113.     done
  114. }
  115.  
  116. function z_strip_from_txt() {
  117.     # Rebuilds text file $2 without the lines containing $1
  118.     local Z_TMP
  119.     Z_TMP="$(mktemp)"
  120.     grep -Fv "${1}" "${2}" > "${Z_TMP}"
  121.     cat "${Z_TMP}" > "${2}"
  122.     rm "${Z_TMP}"
  123. }
  124.  
  125. function z_fileheader() {
  126.     # Rebuilds the text header (starting with ***) without destroying the file contents
  127.        # No args, replace $Z_TESTDAT with $1 to make into a arg type function
  128.     # You can add extra tabs for formatting (eg \t\t) without breaking the script, thanks to z_strip_empty_array
  129.     local Z_TMP Z_EXPIRE
  130.     Z_TMP="$(mktemp)"
  131.     Z_EXPIRE=$(("$(date -u +%s)" + "$(echo -n "${Z_EXPIRE_TIME}" | awk -F: '{ print ($1 * 86400) + ($2 * 3600) + ($3 * 60) + $4 }')"))
  132.  
  133.     if [ -f "${1}" ]; then
  134.         grep -Fv "***" "${1}" > "${Z_TMP}"
  135.     fi
  136.     echo "*** RAR Integrty Test Status for ${Z_DIR} (Times in UTC/GMT) ***" > "${1}"
  137.     {
  138.         echo "*** Last Updated: $(date -u "+${Z_DATESTR}") (Suggested Revalidation: $(date -u --date="@${Z_EXPIRE}" "+${Z_DATESTR_DAY}")) ***"
  139.         echo -e '*** Date Validated\tTest Status\tRecovery Record Status\tRAR Size\tRAW Size\tFile Last Modified\tFilename'
  140.         cat "${Z_TMP}"
  141.     } >> "${1}"
  142.     rm "${Z_TMP}"
  143. }
  144.  
  145. function z_chk_abort() {
  146.     # If the abort flag has been set prior, exit now
  147.     if [ ! -z "${Z_ABORT}" ]; then
  148.         exit "${Z_ABORT}";
  149.     fi
  150. }
  151.  
  152. function do_rar_test() {
  153.     z_chk_abort
  154.     local Z_STATUS="UNKNOWN ERR"
  155.     local Z_STATUS_C="yellow"
  156.     local Z_REC_C="yellow"
  157.     local Z_RRES Z_TMP Z_REC Z_COMPSIZE Z_RAWSIZE Z_LASTMOD Z_REC_REC Z_REC_RES;
  158.  
  159.     # Check for recovery record
  160.     Z_REC_REC="$(unrar l "${1}" | grep "recovery record" -c)"
  161.     Z_REC_RES=$?
  162.     if [ ${Z_REC_RES} -ne 0 ] || [ "${Z_REC_REC}" -lt 1 ]; then
  163.         Z_REC="${Z_RECOVERY_RECORD_ERRSTR}"
  164.         Z_NRRCNT=$((Z_ERRCNT + 1))
  165.         if [ ! -z "${Z_ABORT_ON_NRR}" ]; then
  166.             Z_ABORT=1
  167.         fi
  168.         Z_REC_C="red"
  169.     else
  170.         Z_REC="${Z_RECOVERY_RECORD_OKSTR}"
  171.         Z_REC_C="green"
  172.     fi
  173.  
  174.     # Test with unrar (7z/7za/p7zip will return errcode 0 on corrupt file and cannot be used)
  175.     unrar t "${1}"
  176.     Z_RRES=$?
  177.     if [ ${Z_RRES} -eq 0 ]; then
  178.         Z_STATUS="${Z_OKSTR}"
  179.         Z_STATUS_C="green"
  180.     else
  181.         case ${Z_RRES} in
  182.             255)
  183.                 # When user hits ^C
  184.                 Z_STATUS="FAILED (ABORT)"
  185.                 Z_ABORT=1
  186.                 ;;
  187.             10)
  188.                 Z_STATUS="FAILED (NOTRAR)"
  189.                 ;;
  190.  
  191.             3)
  192.                 if [ "${Z_REC}" == "${Z_RECOVERY_RECORD_OKSTR}" ]; then
  193.                     # corrupt and has recovery record
  194.                     Z_STATUS="NEEDS REPAIR"
  195.                     Z_ERRRCNT=$((Z_ERRRCNT))
  196.                 else
  197.                     # corrupt and does NOT have recovery record
  198.                     Z_STATUS="FAILED (CORRUPT)"
  199.                 fi
  200.                 ;;
  201.             *)
  202.                 # catchall for unknown error codes
  203.                 Z_STATUS="FAILED (${Z_RRES})"
  204.                 ;;
  205.         esac
  206.         if [ ! -z "${Z_ABORT_ON_ERROR}" ]; then
  207.             Z_ABORT=1
  208.         fi
  209.         Z_STATUS_C="red"
  210.         Z_ERRCNT=$((Z_ERRCNT + 1))
  211.     fi
  212.     z_strip_from_txt "${1}" "${2}"
  213.     z_fileheader "${2}"
  214.  
  215.         if [ -z "${Z_MULTIPART_RAR}" ]; then
  216.                 # Use stat to get filesize of a single file
  217.                 Z_COMPSIZE="$(bytesToHuman "$(stat -c %s "${1}")")"
  218.        else
  219.                # Use stat to get the size of all the rar parts, and add them together
  220.         Z_MULTIRAR_PREFIX="$(echo "${1}" | rev | cut -d'.' -f3- | rev).part"
  221.         Z_COMPSIZE=0
  222.         for fss in $(stat -c %s "${Z_MULTIRAR_PREFIX}"*); do
  223.             Z_COMPSIZE=$(( Z_COMPSIZE + fss ))
  224.         done
  225.         Z_COMPSIZE=$(bytesToHuman ${Z_COMPSIZE})
  226.        fi
  227.  
  228.     Z_RAWSIZE="$(bytesToHuman "$(unrar l "${1}" | tail -n2 | head -n1 | awk ' { print $1 } ')")"
  229.     Z_LASTMOD="$(date -u "+${Z_DATESTR}" --date="@$(stat -c "%Y" "${1}")")"
  230.     echo -e "$(date -u "+${Z_DATESTR}")\\t${Z_STATUS}\\t${Z_REC}\\t${Z_COMPSIZE}\\t${Z_RAWSIZE}\\t${Z_LASTMOD}\\t${1}" >> "${2}"
  231.     echo -e "$(str_color "${Z_STATUS_C}" "${Z_STATUS}")\\t$(str_color "${Z_REC_C}" "${Z_REC}")\\t${1}"
  232. }
  233.  
  234. function z_process_directory() {
  235.     local Z_DIR Z_TESTDAT Z_TS Z_EXPIRE Z_LASTMOD Z_RES Z_PWD Z_MKTEMP;
  236.  
  237.     Z_DIR="$(realpath "${1}")"
  238.     Z_PWD="${PWD}"
  239.     Z_TESTDAT="${Z_DIR}/${Z_TESTDAT_FILENAME}"
  240.     cd "${Z_DIR}" || (echo "Could not enter directory: ${Z_DIR}" && exit 1)
  241.  
  242.     while IFS=$'\n' read -r -d '' file
  243.     do
  244.         z_chk_abort
  245.         z_chk_if_single_or_part1_rar "${file}" || continue;
  246.  
  247.         # if the test result file doesn't exist
  248.         if [ ! -f "${Z_TESTDAT}" ]; then
  249.             # then create it and insert the header
  250.             touch "${Z_TESTDAT}" || exit 1;
  251.             z_fileheader "${Z_TESTDAT}"
  252.         fi
  253.  
  254.         # scan the test result file for this filename
  255.         IFS=$'\t' read -r -a Z_RES <<< "$(grep -F "${file}" "${Z_TESTDAT}")"
  256.         z_strip_empty_array Z_RES "${Z_RES[@]}";
  257.  
  258.         # if there was a match from the test result file
  259.         if [ ${#Z_RES[@]} -gt 2 ]; then
  260.             # get the stored information and ch eck it
  261.             Z_TS="$(date -u --date="${Z_RES[0]}" +%s 2>/dev/null || echo -n "${Z_RES[0]}")"
  262.             Z_EXPIRE=$(("${Z_TS}" + "$(echo -n "${Z_EXPIRE_TIME}" | awk -F: '{ print ($1 * 86400) + ($2 * 3600) + ($3 * 60) + $4 }')"))
  263.             Z_LASTMOD="$(date -u "+${Z_DATESTR}" --date="@$(stat -c "%Y" "${file}")")"
  264.  
  265.             # if the expire time elapsed,                  the last test wasn't ok,            or the modified time changed
  266.             if [ "$(date -u +%s)" -gt "${Z_EXPIRE}" ] || [ "${Z_RES[1]}" != "${Z_OKSTR}" ] || [ "${Z_LASTMOD}" != "${Z_RES[5]}" ]; then
  267.                 # then validate the file
  268.                 do_rar_test "${file}" "${Z_TESTDAT}"
  269.             else
  270.                 # scan status file for any rars that don't have recovery records and count them
  271.                 if [ "${Z_RES[2]}" != "${Z_RECOVERY_RECORD_OKSTR}" ]; then
  272.                     Z_NRRCNT=$((Z_NRRCNT + 1))
  273.                 fi
  274.             fi
  275.         else
  276.             # otherwise validate the file
  277.             do_rar_test "${file}" "${Z_TESTDAT}"
  278.         fi
  279.     done <   <(find . -name '*.rar' -print0);
  280.  
  281.     Z_MKTEMP=$(mktemp)
  282.     while IFS=$'\n' read -r line
  283.     do
  284.         z_chk_abort
  285.         if [ "$(echo "${line}" | grep -F "***" -c)" -gt 0 ]; then
  286.             echo "${line}" >> "${Z_MKTEMP}"
  287.             continue;
  288.         fi
  289.         IFS=$'\t' read -r -a Z_TRES <<< "${line}"
  290.         if [ ! -z "${Z_TRES[6]}" ]; then
  291.             if [ ! -f "${Z_TRES[6]}" ]; then
  292.                 Z_MISSING=$((Z_MISSING + 1))
  293.             else
  294.                 echo "${line}" >> "${Z_MKTEMP}"
  295.             fi
  296.         fi
  297.     done < "${Z_TESTDAT}";
  298.  
  299.     if [ "${Z_MISSING}" -gt 0 ]; then
  300.         cat "${Z_MKTEMP}" > "${Z_TESTDAT}"
  301.     fi
  302.     rm "${Z_MKTEMP}"
  303.  
  304.     cd "${Z_PWD}" || (echo "Could not return to original directory: ${Z_PWD}" && exit 1)
  305.     if [ "${Z_ERRCNT}" -eq 0 ] && [ "${Z_NRRCNT}" -eq 0 ]; then
  306.         # all good
  307.         echo "All $(str_color green "${Z_OKSTR}") for ${Z_DIR}"
  308.     else
  309.         echo "$(str_color red "Error report") for ${Z_DIR}:"
  310.         local Z_ERRRCNT_STR;
  311.         if [ "${Z_ERRRCNT}" -eq "${Z_ERRCNT}" ]; then
  312.             Z_ERRRCNT_STR="$(str_color green all)";
  313.         elif [ "${Z_ERRRCNT}" -gt 0 ]; then
  314.             Z_ERRRCNT_STR="$(str_color yellow "${Z_ERRRCNT}")"
  315.         else
  316.             Z_ERRRCNT_STR="$(str_color red none)"
  317.         fi
  318.         if [ "${Z_ERRCNT}" -gt 0 ]; then
  319.             echo "$(str_color red "${Z_ERRCNT} file(s) failed") to validate (of those, ${Z_ERRRCNT_STR} of them can be repaired)."
  320.         fi
  321.         if [ "${Z_NRRCNT}" -gt 0 ]; then
  322.             echo "$(str_color yellow "${Z_NRRCNT} file(s)") do not have recovery records."
  323.         fi
  324.     fi
  325.     if [ "${Z_MISSING}" -gt 0 ]; then
  326.         echo "$(str_color yellow "Removed ${Z_MISSING} missing file(s)") from $(str_color cyan "${Z_TESTDAT}")"
  327.     fi
  328. }
  329.  
  330. for dir in "${@}"; do
  331.     z_chk_abort
  332.     z_process_directory "${dir}"
  333. done
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement