diff --git a/forkrun.bash b/forkrun.bash index 7d7ce37..f6b662e 100644 --- a/forkrun.bash +++ b/forkrun.bash @@ -1,6 +1,11 @@ #!/bin/bash # shellcheck disable=SC2004,SC2015,SC2016,SC2028,SC2162 source=/dev/null +if shopt extglob | grep -qE 'off$'; then + forkrun_extglobState='-u' +else + forkrun_extglobState='-s' +fi shopt -s extglob forkrun() ( @@ -248,7 +253,7 @@ forkrun() ( [[ ${tmpDirRoot} ]] || { [[ ${TMPDIR} ]] && [[ -d "${TMPDIR}" ]] && tmpDirRoot="${TMPDIR}"; } || { [[ -d '/dev/shm' ]] && tmpDirRoot='/dev/shm'; } || { [[ -d '/tmp' ]] && tmpDirRoot='/tmp'; } || tmpDirRoot="$(pwd)" - tmpDir="$(mktemp -p "${tmpDirRoot}" -d .forkrun.XXXXXX)" + tmpDir="$(mktemp -p "${tmpDirRoot}/.forkrun" -d forkrun.XXXXXX)" fPath="${tmpDir}"/.stdin mkdir -p "${tmpDir}"/.run @@ -1880,121 +1885,23 @@ EOF } -_forkrun_loadable_setup() { - ## sets up a "loadable" bash builtin for x86_64 machines - local loadableArch cksumAlg cksumVal cksumAll loadablePre loadableDir loadableGetFlag loadableCurlFailedFlag forkrunRepo - - loadableGetFlag=false - loadableCurlFailedFlag=false - #forkrunRepo='main' - forkrunRepo='forkrun_testing_nSpawn_5' - - type curl &>/dev/null || { - if [[ -f "${BASH_LOADABLES_PATH%%:*}"/forkrun.so ]]; then - enable -f "${BASH_LOADABLES_PATH%%:*}"/forkrun.so lseek 2>/dev/null - enable -f "${BASH_LOADABLES_PATH%%:*}"/forkrun.so childusage 2>/dev/null - else - enable lseek 2>/dev/null - enable childusage 2>/dev/null - fi - return - } - - if type uname &>/dev/null; then - loadableArch="$(uname -m)" - elif [[ -f /proc/sys/kernel/arch ]] ; then - loadableArch="$(/dev/null && export -nf _forkrun_SETUP - { [[ "${loadableArch}" == 'x86_64' ]] || [[ "${loadableArch}" == 'aarch64' ]] || [[ "${loadableArch}" == 'riscv64' ]] || return 1; } +_forkrun_SETUP() { + local -a filePathA - if ! [[ $USER == 'root' ]] && [[ -f /dev/shm/.forkrun.loadable/forkrun.so ]]; then - loadablePre='/dev/shm/.forkrun.loadable/forkrun.so' - elif [[ -f /usr/local/lib/bash/forkrun.so ]]; then - loadablePre='/usr/local/lib/bash/forkrun.so' - fi - - [[ ${loadablePre} ]] && { - for cksumAlg in sha256sum sha512sum b2sum sha1sum md5sum cksum sum; do - type $cksumAlg &>/dev/null && break || cksumAlg='' - done - [[ ${cksumAlg} ]] && { - cksumVal="$($cksumAlg "$loadablePre")" - cksumVal="${cksumVal%% *}" - cksumAll="$(curl 'https://raw.githubusercontent.com/jkool702/forkrun/refs/heads/'"${forkrunRepo}"'/loadables/CHECKSUMS' 2>/dev/null)" - [[ "${cksumAll}" == *"${cksumVal}"* ]] || loadableGetFlag=true - } - } + local ARCH t tt k kk forkrun_git_branch outDir filePath fileCur downloadFlag localFlag gotLoadableFlag b b0 doneFlag extglobState supportedArchFlag b64 - if [[ ${loadablePre} ]] && ! ${loadableGetFlag}; then - enable -f "${loadablePre}" lseek 2>/dev/null || loadableGetFlag=true - enable -f "${loadablePre}" childusage 2>/dev/null || loadableGetFlag=true + if shopt extglob | grep -qE 'off$'; then + extglobState='-u' else - loadableGetFlag=true + extglobState='-s' fi + shopt -s extglob - if ${loadableGetFlag}; then - ${loadablePre} && \mv -f "${loadablePre}" "${loadablePre}".old - case "${USER}" in - root) loadableDir='/usr/local/lib/bash' ;; - *) loadableDir='/dev/shm/.forkrun.loadable' ;; - esac - - mkdir -p "${loadableDir}" - [[ "${BASH_LOADABLES_PATH}" == *"${loadableDir}"* ]] || export BASH_LOADABLES_PATH="${loadableDir}:${BASH_LOADABLES_PATH}" - curl -o "${loadableDir}"/forkrun.so 'https://raw.githubusercontent.com/jkool702/forkrun/'"${forkrunRepo}"'/loadables/bin/'"${loadableArch}"'/forkrun.so' || loadableCurlFailedFlag=true - - [[ ${loadablePre} ]] && { - if ${loadableCurlFailedFlag}; then - \mv "${loadablePre}".old "${loadablePre}" - else - enable -d lseek 2>/dev/null - enable -d childusage 2>/dev/null - fi - } - - enable -f "${loadableDir}"/forkrun.so lseek 2>/dev/null || return 1 - enable -f "${loadableDir}"/forkrun.so childusage 2>/dev/null || return 1 - else - enable -f "${loadableDir}"/forkrun.so lseek 2>/dev/null || return 1 - enable -f "${loadableDir}"/forkrun.so childusage 2>/dev/null || return 1 - fi - echo 'abc' >/dev/shm/.forkrun.lseek.test - { - read -r -u $fd -N 1 - lseek $fd -1 >/dev/null - read -r -u $fd -N 1 - exec {fd}>&- - } {fd}&2 - [[ ${loadablePre} ]] && ! ${loadableCurlFailedFlag} && { - \rm -f "${loadablePre}" - \mv "${loadablePre}".old "${loadablePre}" - } - return 1 - ;; - esac -} - -#_forkrun_loadable_setup -#enable -f forkrun_loadables.so evfd_init evfd_wait evfd_signal evfd_close evfd_copy lseek cpuusage childusage - -enable -f forkrun_loadables.so evfd_init evfd_wait evfd_signal evfd_close evfd_copy order_init order_get lseek cpuusage childusage - - export -fp _forkrun_getVal &>/dev/null && export -nf _forkrun_getVal - + _forkrun_getVal() { ## Expands IEC and SI prefixes to get the numeric value they represent # @@ -2030,53 +1937,477 @@ _forkrun_getVal() { local +n vOut } -_forkrun_file_to_base64() { - local ff nn k1 k2; - charmap=($(printf '%s ' {0..9} {a..z} {A..Z} '@' '_')) +export -f _forkrun_getVal - [[ -f "${1}" ]] || { - printf '\nERROR: "%s" not found. ABORTING.\n' "${1}"; >&2 - return 1 - } - - charmap=($(printf '%s ' {0..9} {a..z} {A..Z} '@' '_')) - - while read -r -N 3 nn; do - (( k1 = ( 16#${nn} >> 6 ) )); - (( k2 = ( 16#${nn} % 64 ) )); - printf '%s%s' "${charmap[$k1]}" "${charmap[$k2]}"; - done < <(hexdump -v -x < "${1}" | sed -E 's/^[^ ]*//; s/ //g' | sed -zE s/'\n'//g); -} +export -fp _forkrun_base64_to_file &>/dev/null && export -nf _forkrun_base64_to_file _forkrun_base64_to_file() { - local b fd0 fd1 + local b b0 b1 k kk fd0 fd1 out0 out outC outN outF outB outFile nnSum noVerifyFlag doneFlag IFS extglobState + local -a compressV compressI outA + local -x LC_ALL=C + + # parse options + if shopt extglob | grep -qE 'off$'; then + extglobState='-u' + else + extglobState='-s' + fi + shopt -s extglob [[ -t 0 ]] && { printf '\nERROR: pass the base64-encoded sequence on stdin. ABORTING.\n' >&2 return 1 } + # determine if we are outputting to stout or to a file exec {fd0}<&0 if (( $# > 0 )); then - exec {fd1}>"$1" + [[ -f "$1" ]] && { \rm -f "$1" || return 1; } + outFile="$1" + : >"${outFile}" + exec {fd1}>"${outFile}" else exec {fd1}>&1 fi - - { - printf "$(while read -r -u "${fd0}" -N 4 b; do - printf -v b '%0.6X' "$(( 64#${b} ))" - printf '\\x%s' "${b:0:2}" "${b:2:2}" "${b:4}"; - done)" >&"${fd1}" - } - + + # read dataheader and data + read -r -d $'\034' -u "${fd0}" out0 + read -r -d '' -u "${fd0}" out exec {fd0}>&- + + if [[ -z ${out} ]]; then + # if data header is mising then the base64 may have been made with standard base64 decoding. attempt to work with this. + out="${out0}" + grep -F '+' <<<"${out}" | grep -qF '/' && { base64 -d <<<"${out}" >&$fd1; return; } + noVerifyFlag=true + outN=0 + outF='' + nnSum=0 + else + # parse the data header to get various parameters + noVerifyFlag=false + { + read -r outN outB + read -r nnSum_md5 + read -r nnSum_sha256 + mapfile -t compressV + + } <<<"${out0}" + + # determine checksum to use. prefer sha256 + if type -p sha256sum &>/dev/null; then + nnSum="${nnSum_sha256}" + elif type -p md5sum &>/dev/null; then + nnSum="${nnSum_md5}" + else + noVerifyFlag=true + fi + + # restore full base64 sequence + (( ${#compressV[@]} > 0 )) && { + compressI=('~' '`' '!' '#' '$' '%' '^' '&' '*' '(' ')' '-' '+' '=' '{' '[' '}' ']' ':' ';' '<' ',' '>' '.' '?' '/' '|') + + for (( kk=${#compressV[@]}-1; kk>=0; kk-- )); do + out="${out//"${compressI[$kk]}"/"${compressV[$kk]}"}" + done + } + fi + + # recreate binary from base64 sequence + # this generates outF which is a string that contains the hex values formatted like: \x00\xFF\x9A\x...' + # printf '%b' then will write out the binary data using that string + while read -r -N 4 b0; do + b0="${b0%%*([^0-9a-zA-Z@_])$'\n'}" + + [[ ${b0} ]] || break + (( b1 = 64#0${b0})) + + if ((outN < 6 )); then + printf -v b '%0.'"${outN}"'X' "${b1}" + b="${b:0:${outN}}" + else + printf -v b '%0.6X' "${b1}" + fi + (( outN = outN - ${#b} )) + printf -v outC '\\x%s' ${b:0:2} ${b:2:2} ${b:4} + printf -v outC '%s' "${outC%%*(\\x)}" + outF+="${outC}" + ((outN <= 0 )) && break + done <<<"${out}" + + printf '%b' "${outF}" >&"${fd1}" exec {fd1}>&- + + # verify output file and make it executable + if [[ ${outFile} ]] && [[ -e "${outFile}" ]]; then + chmod +x "${outFile}" + (( outB > 0 )) && type -p truncate &>/dev/null && truncate --size="${outB}" "${outFile}" + ${noVerifyFlag} || [[ "${nnSum}" == '0' ]] || { + nnSumF="$("${nnSum%%\:*}" "${outFile}")"; + nnSumF="${nnSumF%% *}"; + grep -qF "${nnSum#*\:}" <<<"${nnSumF}" || { printf '\n\nWARNING FOR EXTRACTED LOADABLE:\n"%s"\n\nCHECKSUM DOES NOT MATCH EXPECTED VALUE!!!\nDO NOT CONTINUE UNLESS THIS WAS EXPECTED!!!\n\nEXPECTED: %s\nGOT: %s\n\nTHIS CODE WILL NOW REMOVE THE EXTRACTTED .SO FILE AND ABORT\nTO FORCE KEEPING THE [POTENTIALLY CORRUPT] .SO FILE, RE-RUN THIS CODE WITH THE "--force" FLAG' "${outFile:-\(STDOUT\)}" "${nnSum}" "${nnSumF}" >&2; read -r -u ${fd_sleep} -t 2; \rm -f "${outFile}"; return 1; }; + }; + elif ! { ${noVerifyFlag} || [[ "${nnSum}" == '0' ]]; }; then + nnSumF="$("${nnSum%%\:*}" <(printf '%b' "${outF}"))"; + nnSumF="${nnSumF%% *}"; + grep -qF "${nnSum#*\:}" <<<"${nnSumF}" || { printf '\n\nWARNING FOR EXTRACTED LOADABLE:\n"%s"\n\nCHECKSUM DOES NOT MATCH EXPECTED VALUE!!!\nDO NOT CONTINUE UNLESS THIS WAS EXPECTED!!!\n\nEXPECTED: %s\nGOT: %s\n\nTHIS CODE WILL NOW REMOVE THE EXTRACTTED .SO FILE AND ABORT\nTO FORCE KEEPING THE [POTENTIALLY CORRUPT] .SO FILE, RE-RUN THIS CODE WITH THE "--force" FLAG' "${outFile:-\(STDOUT\)}" "${nnSum}" "${nnSumF}" >&2; read -r -u ${fd_sleep} -t 2; \rm -f "${outFile}"; return 1; }; + fi + + shopt ${extglobState} extglob } + export -f _forkrun_base64_to_file + downloadFlag=false + localFlag=false + + forceFlag=false + outDir="/dev/shm/.forkrun/lib/${USER}-${EUID}" + + # parse inputs + while true; do + + case "${1}" in + + -?(-)d?(ownload)*) case "${1}" in + -?(-)d?(ownload)) downloadFlag=true; localFlag=true; forkrun_git_branch='main' ;; + -?(-)d?(ownload)?(=)local) downloadFlag=true; localFlag=true ;; + -?(-)d?(ownload)?(=)*local*) downloadFlag=true; localFlag=true; forkrun_git_branch="${1#-?(-)d?(ownload)?(=)}"; forkrun_git_branch="${forkrun_git_branch//?(\,)local?(\,)/}"; forkrun_git_branch="${forkrun_git_branch//[\"\']/}" ;; + -?(-)d?(ownload)?(=)*) downloadFlag=true; localFlag=false; forkrun_git_branch="${1#-?(-)d?(ownload)?(=)}" ;; + esac ;; + -?(-)o?(utput)?(=)*) outDir="${1#-?(-)o?(utput)?(=)}" ;; + -?(-)f?(orce)) forceFlag=true ;; + *) break ;; + esac + shift 1 + + done + + gotFlamegraphFlag=false + gotLoadableFlag=false + + # create required dirs + mkdir --mode=1777 -p "/dev/shm/.forkrun" + mkdir --mode=1777 -p "/dev/shm/.forkrun/lib" + mkdir --mode=700 -p "${outDir}" + + # add to PATH and BASH_LOADABLES_PATH + BASH_LOADABLES_PATH="${BASH_LOADABLES_PATH//\:${outDir}?(\/):/:}" + BASH_LOADABLES_PATH="${BASH_LOADABLES_PATH#${outDir}?(\/)?(:)}" + BASH_LOADABLES_PATH="${BASH_LOADABLES_PATH%?(\:)${outDir}?(\/)}" + BASH_LOADABLES_PATH="${BASH_LOADABLES_PATH}${BASH_LOADABLES_PATH:+:}${outDir}" + export BASH_LOADABLES_PATH="${BASH_LOADABLES_PATH}" + + PATH="${PATH//\:${outDir}?(\/):/:}" + PATH="${PATH#${outDir}?(\/)?(:)}" + PATH="${PATH%?(\:)${outDir}?(\/)}" + PATH="${PATH}${PATH:+:}${outDir}" + export PATH="${PATH}" + + ARCH="$(uname -m)" + + if ${localFlag}; then + # see if the files are available locallly + for fileCur in 'forkrun.so' 'forkrun_flamegraph.pl'; do + filePath='' + filePathA=() + if ${localFlag} && PATH="${PATH}:${outDir}:${PWD}$([[ -d "${PWD}/forkrun" ]] && printf ':%s/forkrun' "${PWD}")" type -p -a "${fileCur}" &>/dev/null; then + mapfile -t filePathA < <(PATH="${PATH}:${outDir}:${PWD}$([[ -d "${PWD}/forkrun" ]] && printf ':%s/forkrun' "${PWD}")" type -p -a "${fileCur}") + mapfile -t filePathA < <(printf '%s\n' "${filePathA[@]}" | grep -F "${outDir}"; printf '%s\n' "${filePathA[@]}" | grep -vF "${outDir}") + if (( ${#filePathA[@]} > 1 )) && type -p date &>/dev/null; then + t=$(date -r "${filePathA[0]}" '+%s') + k=0 + for (( kk=1; kk<${#filePathA[@]}; kk++ )); do + tt=$(date -r "${filePathA[$kk]}" '+%s') + (( tt > t )) && k=$kk + done + filePath="${filePathA[$k]}" + elif (( ${#filePathA[@]} > 0 )); then + filePath="${filePathA[0]}" + fi + [[ "${filePath}" ]] && { + chmod +x "${filePath}" + [[ "${filePath}" == "${outDir}/${fileCur}" ]] || { + \cp -f "${filePath}" "${outDir}/${fileCur}" + chmod +x "${outDir}/${fileCur}" + } + if [[ "${filePath}" == *'forkrun_flamegraph' ]]; then + gotFlamegraphFlag=true + elif [[ "${filePath}" == *'forkrun.so' ]]; then + enable -f "${outDir}/forkrun_loadables.so" getCPUtime && [[ $(getCPUtime) ]] && gotLoadableFlag=true + fi + } + fi + done + fi + + if ${downloadFlag} && ! ${gotLoadableFlag}; then + # try to download the files + : "${forkrun_git_branch:=main}" + + ${gotLoadableFlag} || { + type -p wget &>/dev/null && wget https://raw.githubusercontent.com/jkool702/forkrun/${forkrun_git_branch:-main}/loadables/bin/${ARCH}/forkrun_loadables.so -O "${outDir}/forkrun_loadables.so" &>/dev/null + type -p "${outDir}/forkrun_loadables.so" &>/dev/null || { + type -p curl &>/dev/null && curl https://raw.githubusercontent.com/jkool702/forkrun/${forkrun_git_branch:-main}/loadables/bin/${ARCH}/forkrun_loadables.so >"${outDir}/forkrun_loadables.so" 2>/dev/null + } + + if type -p "${outDir}/forkrun_loadables.so" &>/dev/null; then + chmod +x "${outDir}/forkrun_loadables.so" + enable -f "${outDir}/forkrun_loadables.so" getCPUtime && [[ $(getCPUtime) ]] && gotLoadableFlag=true + fi + } + fi + + if ${forceFlag} || ! ${gotLoadableFlag}; then + # use the versions built into theis time.bash file + + # note: this base64 binary blob is generatred by using _forkrun_base64_to_file on the arch-specific compiled shared .so file for the builtin. + # passing this blob to the stdin of _forkrun_base64_to_file will restore the original .so file (needed for the loadable builtin to get cpu time with getCPUtime) at . + # the .so file, source code and compile instructions are all available in the "forkrun" repo on github (https://github.com/jkool702/forkrun) at LOADABLES/SRC/forkrun.c. + # The compiled .so file that this binary blob re-creates is avaiilable in the repo at LIB/LOADABLES/BIN/$ARCH/forkrun.so. + # Note: these base64 blobs have been compressed. The information needed to decompress them is built into the start of the blob, as are the sha256 and md5 checksums for the original .so file + + supportedArchFlag=true + case "${ARCH}" in + x86_64) +b64=$'66030 33016\nmd5sum:712ef645e4be636676afbfb6901b6100\nsha256sum:b899f03c6a27a745444a24673f3ce00343783586b666e7e7cae961cc62f4a2df\n00000000000000000000000000000000000000000000000000\n0000000000000000000000000000000000000000\n000000000000000000000000000000000000000\n00000000000000000000000000000\n00000000000000000000000000\n0000000000000000000000000\n000000000000000000000000\n000000000000000000000\n00000000000000000000\n0000000000000000000\n000000000000000000\n0hQN9gAdvcyUObzk\n000000000000000\n00000000000000\n0000000000\n000000000\n00000000\n0000000\n000000\n00000\n0000\ncRj\n000\n0g\n00\n__\n0w?4g\034vQlchw81.{?c0fw01=1{7xV{>4?e?b04?7w0t?4<4#]_08]3Y0w[g[g2Ung]bxJ}K6Q]30.]c01}2{4<12E0w]aw2}G08}M[3{2[1gVnhA12wng]a1J}E6Q]1w0w]602[g[4<8;k>17jBk0.01M.<9{8?s04;g[4<5;c>17jBk0VMmDZFYoDsU_QZQ@iFOtPuUic0o~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`?fcf7LF8w@M8i8I5ElY?4y5M7g2_Z18wYg8MM:_PnanM?_OncnM?3NZ?fYBOBY?6w;Wu3//_9s9v?1E.>eDg//_OmWnM?q08>3FMf//YBIBY?6w33FAf//YBCBY?6w6eBM//_OmanM?q0w>3Fof//YBwBY?6w9eB0//_OlOnM?q0I>3Fcf//YBqBY?6wceAg//_OlqnM?q0U>3F0f//YBkBY?6wf3FQfX/_YBeBY?6wieCM_L/_OkGnM?q1g>3FEfX/_YB8BY?6wleC0_L/_OkinM?q1s>3FsfX/_YB2BY?6woeBg_L/_OnWnw?q1E>3FgfX/_YBYBU?6wr3F4fX/_YBSBU?6wueDM_v/_Onanw?q2<3FUfT/_YBMBU?6wxeD0_v/_OmOnw?q2c>3FIfT/_YBGBU?6wAeCg_v/_Omqnw?q2o>3FwfT/_YBABU?6wD3FkfT/_YBuBU?6wGeAM_v/_OlGnw?q2M>3F8fT/_YBoBU?6wJeA0_v/_Olinw?q2Y>3FYfP/_YBiBU?6wMi8n0t0z_U6of7Qg?ccf7U:YMYu@E0Z5mc>1RaRl8wPQqn<4y9Vngci8QZTBA?ewV//W6j//61uRy>1nscf7M333N@:fcf7LHFt//MYvw;11lER7_A5lglhlkQy1Xb<23@04fxPk1?29_kybvwx9yvmW2w>37Shj7AW1T@/@9MUfZ0M@48w4?4Odr2gwytZ1Lw>w1cyuXEHvX/Un0ti18yQgAm4y5M7UmKw?1018et183Qrgi3T/M40j0Z7YAydfpMZ?3EHvL/Qy5M7guKwEfg2Kww>1cyuXEHvL/@L93NY0W3fX/@be8f_17iWWcvZ/Z8zjRpfg?i8D6cs3E9LL/Xw1cs3E0LL/@LqioJZ4bEafxd<3EDLH/UIUioD5w_Y4tdadh@G3UeYfxgw1?18znMA6ezK@/_xs0fy4U1?2bv2gscs2@2.?ez6@L/xs1@4QNzY4AVXw@2lw4?4MVZkAfh@VczngA2eIX3N@4:2bv2got5ANOj7SgrA5yPTKmw?i8RQ90yW2W4TV/@5M0@8z_X/UJY91MNMbU81>W3nV/@5M0@evLX/QNzYeBL_L/3NZ4?11lQ5mgll1l5lji87IG1>8f_0w@4Bw4?46Z:@fcw8?ewl@v/i8RQ912///Q69Nezz@L/yse5M0@5yg4?bY2j8D_W6DU/ZcyvvEkvz/QyddjgW?1cyv_EILD/QC9N4y5M0@4c04?4OdduoX?18yst8yuENM4O9ZKz_@f/j8DDWcvT/Z5xuQfxfo>18wsiE4>ytxrnk5sglR1nA5vMSoK3N@4:18yTU8w3YJtj@0vM5NtjC0vM80tjd1Lg4>3FlLX/@zHZL/yPzExfD/Qydfl8V?18ysoNMezzZL/KM4>3HDwYvg018zjkleg?hj7JW6DU/@5M44fBcnF5fX/Sof7Qg?4ydfigV>NMeyGZL/WYkf7Ug:ewH@v/j8DSi8QZd3A?4y9Mz70W8vS/ZcyvvEj_v/@Kq3NZ4?18zjSZe>cs3EqLr/@K53N@4:2@3M>4OdfpcU?3FmfX/MYvw;18yuVcyvsNMezXZL/Wvz@/_E8vr/UIUWbHU/ZcyvV8zjTse>i8D2cs3E5Lr/@AK//A45lyvANM469_k5ki8QlNPw?4C9ZbV;lld8wuN81>i8DDWcnS/Z8zjlOe>i8DDW1rU/Z8xs0fx4o1?18zmMAg4y9MHU01>i8D3i8DLW4nT/Z8ytZ8xs0fx1Q1?3E9fr/XUFi8RU0ky9XKzrZ/_i8n03UjH2W2w>37SKMA>3Eafv/Q69h2g43NY0cvZ8yuXExLv/UfH0nnNi8n03UihNM5JtglN1nsfE1_n/Qy1N4w4?2U//_RJtglN1nscf7Q?kQ69@4y9Yj70Lw.?18zhkbdM?i87I41>4ydn2ggi8DvW2vR/Z8zjnkdw?i8DvW7zS/Z8xs1Qe4y9NQy9MP70ict490w;i8Rk90x8zjnJdw?W47Q/Z8yt_Eyvj/Qybh2g8i87441>5L3cs3HYMYvw;11l4yddnIS?18zjSYdw?lld8wuM?w?W1bS/Z8xs0fx8M>18yut8ysa@?8?4y9M@x6Zv/i8n0t0C1f2hzs7kwt20NXky9T@wuZf/i874?8?4y9W5JtglP33N@:4Od9mIS?18znMA14O9VKzkZv/i8D7i8n0tcgNXmoK3N@4:2W2w>37SW6jP/YN_QO9VAw1NuyDZv/i8D7i8n0tt_HBP7JWVJC3N@4:11lQ5mgll1l5lji87IC<4ydt2gYW4nR/Z8zjnWdg?j8Iwi8D3j8DDWa3Q/@5M0@4A04?4yddgES?1cyuvEyvj/UD5xs0fxds>18zjk8e>j8DDW73Q/@9Non03UjS0w?i8QRBPo?4O9V@xnZf/xs0fxaY3?18zjmadw?j8DDW43Q/@9Non03UgS0w?i8QRsPs?4O9V@wDZf/xs0fx1s5?18zjladM?j8DDW13Q/@5M0@4@0k?4yddp8S?1cyuvE@vf/UD5xs0fx1A4?18zjnyd>j8DDWe3P/@5M0@4Lwo?4yddrUS?1cyuvEOvf/Un03Umh1w?yTMAf4y9TKx5@v/ysnFxM>6of7Qg?8dY93M23Uhi0M0.rP//_3U_Y1>yMkLkM?Lw46q9v2hIi8RY961CykMArEB496zENvf/Un03Uyd1g?ZAgApw4fxqk2?3Sh2hK.@5Fgg?4y9T@xfYL/i874C<8DEmRR1n45tglV1nYcf7M2br2gYgoD5zknZw_w23UsZ0w?i8JX2bEacs:4C9NewsYL/gocY9298yggA3UgI1g?w_Q33UhG0M?j8JX64yddvkP?1cyv_EAvb/UD2xs0fx3g2?18zjnBcM?j8D_W7zO/@5M0@4Uwg?37.o0_0bE18n03UwT0M?j8RA962b3rtg?1cziQzcw?cs1cyuFcyuu@8yTMAf4y9TKykYL/ysnFpLT/UIZaR>4ydt2h0Kww>3Emf3/Qy3@0xRtkm5V0@8gLT/QyddiQO?14yusNMew8Yf/WiPZ/Z5cv_FWLT/QybuMyW2w>37SW8LM/Z1ysjFELP/QybuMyW2w>37SW7fM/Z9ysl1w_M33Ugh1w?3UVs1w?i8QZCiw?370W6bK/_FvvX/@x8XL/Lg4>2beezsYf/i8QZAz4?4y9Nz70W3LK/_FH_P/UdY93M23Ulk1w?i8I5D5g?4y5M0@4ZMo?bA1xs1Q4g@Sj2h0y4MAoqw23Uk52>wY01i8RQ960NQAyoNAg4o018yTI8W7_L/_F4_P/Qy9NHE1370cuTEbuX/@Dx@/_yPSCjw?xvZU1uypXL/yPSjjw?xvZU1uyaXL/i8QZ1P4?37JNMlTjw?//_Ys5skU?f//_EWeX/QydfuYM?3ETeX/@Cg@/_W0bJ/@beeyrX/_i8QZA3>4y9Nz70WfHI/_F5vT/Qydfh4M>NMezDXf/WgbZ/@bfhNe?18zngAgbE88IUW4bL/Z8zjQpc>i8D6cs3EEuP/@Al@/_i8JH881Z>fxec>18yPgAh8DTWabJ/Z8w_z_3UiK3F8fL/QybsN18zjT69g?cs3EP@L/@DG@/_goIY9exhXL/i8QZH2U?4y9Nz70Wb3H/_FO_L/Q6Z.>eDE@L/honJ3Ugn_L/cuTF2vH/QO9VAydfhUM>NMex_W/_WpHX/ZcoSgAf463_04fzxY4?15znMA_QkNXkC3X059oYt8ykgA24z1U098yst8ykgAaexSXf/i8D53NY0iof50rEahj7JW2PI/Z8NQgA8?1?18ykgA64ydh2hwi8B4910f7Q?i8IY9eznXf/i8n03UiD1dxuQfx1U1?16yMifi8J491wNQKIk3NY0i8f20ky3M2x9etkfx281?2bc4gVNDnEct9C3NZ4>Xt9k03Uj@4ydt2hwNM:ioD4W5jE/Z1yNgAxt8fxvQ>18yRgAo80W?@5XM>4yW////_TZ8et183Qv2i8B49414yu_FQfv/QkNOj7_grz//_Ki4>2W0M>bU8NMewFV/_WkjT/Z8zjSt8g?W1zD/_Fc_v/Q6_3M>4OdbkkF?3FR_T/@zIVL/yPzExuD/QydfgEH?18ysoNMezAVL/Wv_S/Z8zjR5aM?cs3EQur/Qy9X@ypV/_WujS/ZcyTgA26pCbwYvx]3NZ?4S5Zw@4PM>4Obj2gocvp5cs11yN5dysYf7U:cs3H5mpCbwYvx]A4y3M05cev1Q7PAkxTnOiof?kQVNnhViof7a46b5QS9@uLd3NZ4?11yR44cs3H2ky3M05cev1QhPIkxTnOiof60kO9h2ggiEQkJg<1cykMA24y9RAy952jE4ev/QObj2g8i8Ik9bU1exbWf/i8IZX4g?bU1Lw4>3Eauz/QybfoF4?2@.>ewoWf/i8IZAkg?bU1Lw4>3EZKv/QybflZ4?2@.>ezBV/_i8IZtAg?bU1Lw4>3EM@v/QybflN4?2@.>eyOV/_cs18wYg8MM>fcf7LF8w@M8i8f42cc~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#0M>3k<1<1w>98987dd201adg>3w80cI2>go0U08?1<2kApo6>50eE2>g<5mBF3g?103R0w?4c0_M8?1<1R6CA9>2?E3}E6Q}8[f0j}G6Q}8[b0j}I6Q}8[b1J}M74}8[,}O74}8{xb}Q74}8[,}S74}8[3xb}U74}8[7xb}W74}8[c1b[78}8[,}278}8[f1b}478}8[,}678}8[1xc}878}8[61c}g78}8[,}i78}8[1hl}k78}8[,}m78}8[91c}o78}8[cxc}w78}8[,}y78}8[2Jl}A78}8[,}C78}8{1d}M78}8[,}O78}8[3Rl}Q78}8[,}S78}8[3xd[7c}8[,}27c}8[6xd}47c}8[,}67c}8[a1d}87c}8[dxd}a7c}8{1e}g7c}8[,}i7c}8[31e}k7c}8[,}m7c}8[61e}o7c}8[9xe}q7c}8[4Zl}w7c}8[,}y7c}8[6Jl}A7c}8[,}C7c}8[d1e}E7c}8[11f}G7c}8[,}I7c}8[41f}M7c}8[,}O7c}8[8Bl}Q7c}8[,}S7c}8[8xf}U7c}8[d1f[7g}8[,}27g}8[11g}47g}8[,}67g}8[4xg}87g}8[,}a7g}8[81g}c7g}8[c1g}e7g}8{xh}g7g}8[,}i7g}8[41h}o7g}8[fFi}q7g}8[30t}u7g}8[c1N}w7g}8[6xh}E7g}8[eRk}G7g}8[30t}K7g}8{1O}M7g}8[9Fl}U7g}8[95k}W7g}8[30t}@7g}8[41O[7k}8[1Jl}87k}8[39l}a7k}8[30t}e7k}8[81O}g7k}8[39l}o7k}8[4hl}q7k}8[30t}u7k}8[c1O}w7k}8[4hl}E7k}8[49k}G7k}8[30t}K7k}8{1P}M7k}8[9xh}U7k}8[3xk}W7k}8[30t}@7k}8[41P[7o}8[cxh}87o}8[7Bj}a7o}8[30t}e7o}8[81P}g7o}8[bpl}o7o}8[91l}q7o}8[30t}u7o}8[c1P}w7o}8[91l}E7o}8[4Nj}G7o}8[30t}K7o}8{1Q}M7o}8[f1h}u6Y}613rSRMtnhB87hLt65I84dgli1QqmRB86pLsy1TrT9Hpn9P865Kp21Qq6lFsy1ApndzpmVAomVQsOU[gmhAsO1CqmVFsSxBp213k5kwt6BJpi0EpD9Lri0KoT1RtndxpSkwpCBIpncF82Iwr6BSpi0Ls79LoO1QqmRBsOU[jTlQs7lQsPEwf5hfl45cnQdgllZkikR5fy0YkRBjl4ldnQdgllZkikR5fw:lld1hQkW86dEqmNAtndxpSkwmO0Jsi1Y82QJsnlFpngwng]59BoSZOp21CqmVFsSxBp21zq6BIp79BrytP84dgli1QqmRB87hL82hXt6RMh6BOviYKoT1RtndxpSkLoT1RbzNgikg@bw>4BC82sJsiswrT8w9OQJsnlFpngD86BP86tFtClKb21Ptn1MsClPsO1LtnhMtngK045QrSRFoS5Ir7AwoDlJs21x87dEon9Bp21zrTlKt6lO865Kp21Pt6ZOpi1Ft21Fry0YlA5ifw?oncwoi0NdyRAqmtFt2MwuClOrORMomhApmgwtn1Mpn9zondB86xBu21Pt79FrCsK{19rCBQqm5IqnFB864wsSxxsClA83oQbm9Ft21zrTlKt6lO86pLsy1LsChBsBZDpngK[4dIrTdB869Lt6wwpnpBrDhCp7cwomVA87lKsSlQ87pxsCBxoCNBsOU{5ljgkt5ey1BtCpAnTdFpSVxr21rf7hBsCRvpCg@85IYqmVzsClJpmVQnSdLtmVQfBQwng:kSBDrC5I84lfhzEwtT9Ft6lP83NFrCdOpmRBrDhvoSZRrDg@87hL83NQpn9JnSpAfw[w820wh6lConlIt21Qpn9JnSpA86BP84lmhAhvl4lijg{820w84hBpC5Rr7gwqmVzsClJpmVQnSdLtmVQ86BP85l9jBgSd5Zdglww82Ywcw?lld1hQkW86lSpChvoSZMui0YrTlQs7lQnSpAfy1rf6BKs7lQnSpAfBQ[gSZKt6BKtmZRsSNV87dMr6Bzpi1CsCZJ87dQp6BK87hL86ZRt71Rt5ZCp21Fry1zq7lKqTcK?11pDhBsy1BomdE86dEtmVHb21PqmtKomMwpnpBrDhCp21QrO1TomJB879BomhBsDcK[5txqngwtmVQqmMwp65Qoi1FsO1xtC5Fr65yr6kwt6YwsClxp21Lsy1xry1BtClKt6pA86BP87dFpSVxr6lA?1yui1MrSNIqmVD869Lt6wwpnpCp5ZAonhx865Kp21BtCpAnThBsCQK{0YrCZQqmpVnSpAfzEwjT1QqmZKomMwhAgwt6YwtT9Ft6kw9P0D82xKrO1TomBQai1Lsy0Dciswa7txqnhBp2AK{1FrCBQqm5IqnFB87hTrO1BtClKt6pA9TcI87dQrT9B84p486VRrm9BsDcwqmUwhlp6h5Z4glh1865Kp215lAp4nRh5kAQK?1dtndQ869B86dxr6NBp21LrCdB869BpCZOpi1RsSBKpO1BtCpAnTtxqngwbO1BtCpAnTdFpSVxr2U[lld1hQkW86NPpmlH83N6h3Uwf4Z6hBd5l3UwmPNjhklbnRhpk4k@ni1rf5p1kzVt{1drTpB87hEpi1DqnpBry1CqmNB86hBsSdOqn1QrT8wf4p4fy1yui0YjQp6kQlkfy1yunhBsOU?2QwkQl5iRZkml1582xLs7hFrSVxr2AW85d5hkJvkQlkb21jhklbnQdlky0Ep6lConlIt2AI85d5hkJvhkV4>J85p1ky0ErT1QqmZKomMFey19py1DqnpBryMwsThLsCkwrClT86pFr6kwrSpCsSlQ86BK87pxsCBxoCNB85p1kyU}J84BC85p1ky1FsO1Brn1Qui0E9OsFb21BrC5yr6kwsnlFpngwrmZApi0ErCYwrTlQs7lQaiU?59Bt7lOrDcwrClT86ZCpDdBt21Lsy1Pt6ZOpncwqngK{1zs7lRsS5Dpi0YtSZOqSlOnT1Fp3UwmPNTrT9Hpn9vs6BAfy1rf2UKbzVtng<1BtCpAnTdFpSVxr21rf7hBsCRvpCg@85IYqmVzsClJpmVQnSdLtmVQfBQwng<1BtCpAnSdLs7Awf6ZRt71Rt5ZCp3UwmPNFrD1Rt5ZCp3Vt}r7dBpmIwf4p4fy0YjQp6kQlkfy1rf5d5hkJvl5BghjVt85IYlA5ifBQ0hAZiiR9ljBZ3i5leiM1BtCpAnSdLs7AW86pPt65Qa6BKpCgFey0BsM1BtCpAnSdLs7AW87dBrChCqmNBey0BsM1BtCpAnSdLs7AW871Fs6kW82lP06lSpChvoSZMujEwsT1IqmdB86ZRt3Ew9nc0pnpCp5ZzrT1Vey1Ps6NFoSkW82lP02ZQrn?biRNtmBBt01lsS5DpjEwoSxFr6hRsS5Dpi1r82RN85Q0pSlQsDlPomtBey0BsM1PundzrSVCa5ZjgRZ3j4Jvl4dbai1ComBIpmg0t6RMh6BO02lPbOVzs7lRsS5Dpg1JqShFsy0BsPEw9nc09ncLoT1RbylA07s0pCZMpmUw9ncW82lP02ZMsCZzbOlAbTdQong09mNItg0Ls79LoOZPt65Q06dMti?r7dBpmI0r7dBpmIW82lP05d5hkJvkQlk05d5hkJvhkV402lIr6g09mNIp0E0pnpCp5ZTomBQ06lSpChvtS5Ft3EwtT9LrCswon9DsM1BtCpAnTtxqngW871Lr6MW82lP06lSpChvtS5Ft3EwsClxp21AonhxnSlSpCgW82lP030a06lSpChvtS5Ft3EwsClxp21Qpn9JnSlSpCgW82lP06lSpChvqmVFt3Ewp65Qoi1BtClKt6pAey0BsM1BtCpAnSBKqngW87hBsCQwpnpBrDhCp3Ew9nc0hlp6h5Z4glh104lmhAhvl4lijg1BtCpAnSdLs7A0pnpCp5ZPqmtKomM0pnpCp5ZPqmtKomMW87tOqnhBey0BsM1LsChBsBZFrCBQey1TsCZKpO1xsCtP06ZOp6lOnSBKqngW86RJon0W82lP06ZOp6lOnStBt?Br7w0c34OcPgRdzsUek52gQh5hw1zs7lRsS5DpjEwrmBPsSBKpO1gikhP02ZMsCZz06dMtnlPomtBey0Ls79LoO1ComBI02lIr7kw9mNItgE0oSxFr6hRsS5Dpg1CszEwtmVHrCZTry1Ptm9zrSRJomVA82sBsOs0lld1hQkW86ZOp6lOnStBt20YlA5ifw1lkQ57hjEwrT9Apn9vqmVFt01lkQ57hjEwpnpCp5Zzr6ZPpg14pmpxtmNQ83NFrD1Rt5ZCp3UwqncwsThAqmU0lld1hQkW86lSpChvtS5Ft5IYrCZQqmpVnSpAfBQ0lld1hQkW86lSpChvqmVFt01zq6BIp7lPomtB85Iwbn4wv20Jbn5RqmlQ85Q0pnpCp5ZTomBQ85IYrCZQqmpVnSpAfBQ<16McXj;w>1gKL/q<32@/@g20Nf/a04?236/ZQ.?Ecr/Vw1?1wN/_P04?f3k/Ys0w]1g{nFi?5U404r30s8A04?2g48e48U2hgUozgd23y2c144ea8o5ggUMwMp73K010Pg12wUMggUEggUwgwUogwUggwU8gwI>1c48e48Y2gwUozwd23y2d148ea8M5ggUMxwp13zy31QseU243y04a3zx33z113yx23y123xx23x123wxb2M>4w>3s35/@T;48e48M2jMUoxwd13y2314seE.2g0Ee84ge644e448e24wbj<801?2cNv/zwQ>123x2f0A8e68U3gwUwzgh23yyc1k4ec8o6ggUUwMt73J010Ws12wUUgMUMggUEgwUwgwUogwUggwU8h0I101s0E74}4[101>101w0S7o}8[1U1>101s0M74}U[2I1>101s?78}M[3E1>101s.78}M[4w1>101s0w78}E[5s1>101s0M78}E[6o1>101s?7c}U[7o1>101s.7c}U[8g1>101s0w7c]1{981>101s0M7c}M[a01>101s?7g]1o[aE1>40f7_&bg1>101?15w^40f7_&c81>2?o0u2I)cw1>101c0I6Q)dk1>101g0K6Q)dU1:Y0Q5k)f41>101s0Q7o)fQ1>101o0W6Y)1c2>2?c?1(1A2>i%2M2>i%4w2>h01s087o}M[5A2>i%7o2>w%982>g%a02>i%bQ2>i%cU2>i%eA2>i%fI2>i$Q3>i%1Y3>h01s0U7k}M[303>i%4c3>h01s087k}M[5k3>i%6w3>i%7M3>i%8Y3>i%2Y4>i%a03>g%ao3>i%bI3>h01s0E7o}M[cw3>h01s0E7k}M[dI3>g%eg3>i$04>i%1g4>i%2o4>g%5o3>i%2U4>i%484>i%5c4>i%6w4>i%7A4>i%8I4>i%9U4>h01s0E7g}M[b04>i%cg4>g%dg4>w%ec4>i%co4>g%fk4>h01s0U7g}M{o5>i%245>h01s0o7g}M[345>i%4k5>h01s0o7o}M[5o5>h01s0o7k}M[6w5>i%7Q5>i?k0M2E]2R[9Q5>i%aU5>g%c05>i%d85>i%ew5>i%fI5>i$Y6>i%246>w%3I6>i%506>i%646>y%7M6>g%8w6>i$1zsDhypmtFrBcKrM1Apn9BpSBPt6lOnThJnSdIrSVBsM1vnShLnStIrS9xr5ZAt6ZOsRZxtnw0oSZJs6NBt6lAbz?nRZArRZDr6ZyomNvp7hLsDdvonlUnSpFrCBvon9OonBvpmVQsDA0pD9xrmlvp7lJrnA0nRZCsC5JplZAtmRJulZFrCBQnS5OsC5VnSlKt79V06lSpChvoSZMulZJomBK06lSpChvp65Qog1zq6BIp7lPomtBnSRxqmU0sClxp5ZMsCZznTdQong0sClxp5ZCqmVFsSxBp5ZQqmRBnSpLsBZMqmg0sClxp5Zxr6NvoT1RnThFrmk0pD9voDlFr7hFrw1BtCpAnThBsCQ0rT9Apn9voSZRrDhBsw1zs7lRsS5DplZArSc0oSxFr6hRsS5DplZArSc0rT9Apn9vpSlQnShLoM1LsChBsBZFrCBQnShLoM1BtCpAnSdIrTdBnShLoM1BtCpAnTdFpSVxr5ZArSc0pnpCp5ZzrT1VnShLoM1BtCpAnTtxqnhvp6Zz06lSpChvqmVFt5ZArSc0r7dBpmJvp6Zz06dOt6lKp5cKrM1vnQpigkR5nQleh5Zv05ZCqmVF05Zvp7dLnSxxrChIpg1vh5BegkR9gM1vnQtellZ5i5Z6kA5dhlZ8h580nRZkjkdvhkV4nRY0nQtcjQ91j5ZfhApjhlhvl452j4lv05ZFrCBQ06tBt6lKtA17j4B2gRYObz8Kdg1vnSBPrScOcRZPt79QrTlIg4tcik93nP8KcPw0pnpCp5ZTomBQnTdQsDlzt01vnSlOsCVLnSNLoS5QqmZKg4tcik93nP8KcyUR05Z9l4Rvp6lOpmtFsThBsBhdgSNLrClkom9Ipg1ytmBIt6BKnSlOsCZO05ZvqndLoP8PnTdQsDhLtmNIg4tcik93nP8KcPw0sT1IqmdBg4tcik93nP8Kdg1vnSBPrScOcRZCsSdxrCp0hQN9gAdvcyUPe01JqShFsA17j4B2gRYObz8Kdg1CoSVQr417j4B2gRYObz8Kdg1TsCBQpk17j4B2gRYObz8Kdg1BtCpAnSdLs7BvsThOtmdQ06tBt71Fp417j4B2gRYObz8Kdg1LsChBsBZFrCBQnTdQsDlzt01CoSNLsSl-0rT1BrChFsA17j4B2gRYObz8Kdg1Pt79IpmV-0rmRxs417j4B2gRYObz8Kdg1UpD9Bpg1PrD1OqmVQpA17j4B2gRYObz8Kdg1IsSlBqRZPt79RoTg0pnpCp5ZPqmtKomNvsThOtmdQ07xOpm5Ir6Zz05ZvqndLoP8PnTdQsDhLr6N0hQN9gAdvcyUPe01Pt79OoSxOg4tcik93nP8KcyUR06NPpmlHg4tcik93nP8KcyUR07xJomNIrSc0p71OqmVQpA17j4B2gRYObz8Kdg1Mqn1Bg4tcik93nP8KcyUR06dIrTdBp6BOg4tcik93nP8KcyUR079Bomh-0pCtBt7d-0sThOoSRMg4tcik93nP8KcyUR06dEqmNAtndxpSlvsThOtmdQ06pMsCBKt6p-0tmVyqmVAnTpxsCBxoCNB05ZvpSRLrBZPt65Ot5Zv06RBrmdMuk17j4B2gRYObz4Q06ZOp6lOnStBt5ZPt79RoTg0nRZFsSZzczdvsThOt6ZIg4tcik93nP8KcPw0oT1RtndxpSlvsThOtmdQ079BomhAqn9-0pnpCp5ZFrCBQnTdQsDlzt01BtCpAnSdIrTdBnTdQsDlzt01PpmVApCBIpk17j4B2gRYObz8Kdg1PpnhRs5ZytmBIt6BKnSpLsCJOtmVvr6Zxp65yr6lP071Lr6N-0rm5HplZytmBIt6BKnS5OpTo0pCZMpmV-0pSlQsDlPomtBg4tcik93nP8KcyUR07dQsDhLqQ17j4B2gRYObz8Kdg1PundzrSVCg4tcik93nP8KcyUR06lSpmVQpCh0hQN9gAdvcyUT05Z9l4RvsClDqndQpn9kjkdIrSVBl65yr6k0sThOpn9OrT9-0pDdQonh0hQN9gAdvcyUPcM1vnSdUolZCqmVxr6BWpk17j4B2gRYObz8Kdg1xp6hvoDlFr7hFrw1vnSdQun1BnS9vr6Zzg4tcik93nP8KcM?bDdVrnhxow0KsThOt65y02VPq7dQsDhxow0KrCZQpiVDrDkKs79Ls6lOt7A0bCVLt6kKpSVRbC9RqmNAbmBA02VFrCBQ02VQpnxQ02VCqmVF02VDrDkKq65Pq?Kp7BKsTBJ02VAumVPt780bCtKtiVSpn9PqmZK02VDrDkKtClOsSBLrBZO02VOpmNxbChVrw0KsClIoiVMr7g0bD9Lp65Qog0KpmxvpD9xrmlvq6hO02VBq5ZCsC5Jpg0KqmVFt5ZxsD9xug0KpCBKqlZxsD9xug0Kp65QoiVOpmMKsCY0bChVrC5Jqmc0bCtLt?KpSZQbD1It?Kp65Qog0KoDdP02VzrSRJpmVQ02VDrDkKoDlFr6gKonhQsCBytnhBsM~`>r<1M<8[G08]2E0w]3&8*bwfX/SY2[ewV}W3A]2{0A<1<2*8A<4;w}1Eew]6wW}O0E}8{w[6[2j<1<48[c4k}Mhg]9w4}2<1o<8[1w[Dg<4g>3*3gpw]2U*1{4[3g4>s=e2m[6s}w.)1&4<2%21E}A0A}s<9M&2 + return 1 + } + + # define char mapping array that convero 0-63 --> [0-9][a-z][A-Z]@_ (bash 64# chars) + charmap=($(printf '%s ' {0..9} {a..z} {A..Z} '@' '_')) + doneFlag=false + outN=0 + outA=() + + # to dump the binary as ascii hexidecimals , we need od or hexdump + if type -p od &>/dev/null; then + hexProg='od -x' + + elif type -p hexdump &>/dev/null; then + hexProg='hexdump' + else + return 1 + fi + + # map each 12-bit segment (3x ascii hex chars, each representing 4 bits of data) into 2 base64 ascii chars (each representing 6 bits of data) + while read -r -N 3 nn; do + nn="${nn%$'\n'}" + (( outN = outN + ${#nn} )) + until (( ${#nn} == 3 )); do + nn="${nn}"'0' + done + (( k1 = ( 16#${nn} >> 6 ) )); + (( k2 = ( 16#${nn} % 64 ) )); + outA+=("${charmap[$k1]}" "${charmap[$k2]}") + done < <(${hexProg} -v <"${1}" | head -n -1 | sed -E 's/^[0-9a-f]+[[:space:]]+//; s/([0-9a-f]{2})([0-9a-f]{2})/\2\1/g; s/[[:space:]]//g' | sed -zE 's/\n//g'); + + IFS= + out="${outA[*]}" + unset IFS + + (( outN = ( outN >> 1 ) << 1 )) + + # get orig file size + if type -p stat &>/dev/null; then + outB="$(stat -c %s "$1")" + elif type -p wc &>/dev/null; then + outB="$(wc -c <"$1")" + else + outB=0 + fi + + # embed checksums + if type -p md5sum &>/dev/null; then + nnSum="$(md5sum "$1")" + nnSumA[0]="${nnSum%%[ \t]*}" + nnSumA[0]='md5sum:'"${nnSumA[0]}" + else + nnSumA[0]=0 + fi + if type -p sha256sum &>/dev/null; then + nnSum="$(sha256sum "$1")" + nnSumA[1]="${nnSum%%[ \t]*}" + nnSumA[1]='sha256sum:'"${nnSumA[1]}" + else + nnSumA[1]=0 + fi + + # compress base64 and assemble the header + if ${noCompressFlag}; then + printf -v out0 '%s\n' "${outN} ${outB}" "${nnSumA[@]}" + else + # initial compression run + compressI=('~' '`' '!' '#' '$' '%' '^' '&' '*' '(' ')' '-' '+' '=' '{' '[' '}' ']' ':' ';' '<' ',' '>' '.' '?' '/' '|') + mapfile -t compressV < <(sed -E 's/(00+)(([^0]+0?[^0]+)*)/\1\n\2/g; s/([^0]+)/\1\n/g' <<<"${out}" | grep -E '..' | sort | uniq -c | sed -E 's/^[ \t]+//' | grep -vE '^1 ' | sort -nr -k1,1 | while read -r v1 v2; do (( v0 = v1 * ${#v2} - v1 - ${#v2} )); printf '%s %s %s %s\n' "$v0" "${#v2}" "$v1" "$v2"; done | grep -vE '^-' | sort -nr -k 1,1 | head -n 25 | sort -nr -k2,2 | sed -E 's/^([0-9]+ ){3}//') + for kk in "${!compressV[@]}"; do + out="${out//"${compressV[$kk]}"/"${compressI[$kk]}"}" + done + # 2 final compression runs where we re-generate the list of possible replacements and expand it to also look for simple repeated chars (with a limit of a maximum of 32 chars) + for kk0 in 1 2; do + ((kk++)) + compressV[$kk]="$({ sed -E 's/(00+)(([^0]+0?[^0]+)*)/\1\n\2/g; s/([^0]+)/\1\n/g' <<<"${out}" | grep -E '..' | sort | uniq -c | sed -E 's/^[ \t]+//'; { read -r -N 1 y; while read -r -N 1 x; do if [[ "$x" == "${y: -1}" ]]; then y+="$x"; else echo "$y"; read -r -N 1 y; fi; done; } <<<"${out}" | grep -E '..' | sort | uniq -c| sed -E 's/^[ \t]+//' | while read -r v1 v2; do if ((${#v2} > 32 )); then (( v1 = v1 * ( ${#v2} / 32 ) )); v2="${v2:0:32}"; fi; printf '%s %s\n' "$v1" "$v2"; done } | grep -vE '^1 ' | sort -nr -k1,1 | while read -r v1 v2; do (( v0 = v1 * ${#v2} - v1 - ${#v2} )); printf '%s %s %s %s\n' "$v0" "${#v2}" "$v1" "$v2"; done | grep -vE '^-' | sort -nr -k 1,1 | head -n 1 | sed -E 's/^([0-9]+ ){3}//')" + out="${out//"${compressV[$kk]}"/"${compressI[$kk]}"}" + done + + printf -v out0 '%s\n' "${outN} ${outB}" "${nnSumA[@]}" "${compressV[@]}" + fi + + # combine header and base64 + printf -v outF '%s'$'\034''%s' "${out0%$'\n'}" "${out}" + + # print output, optionally quoted + if ${quoteFlag}; then + printf '%s' "${outF@Q}" + else + printf '%s' "${outF}" + fi +} + +# Fallback for head -n N and head -n -N +if ! type -p head >/dev/null; then +head() { + local -a A A1 + local n kk + if (( $# == 0 )); then + mapfile -t -n 20 A + else + [[ $1 == -n ]] && shift 1 + case $1 in + [0-9]*) n=$1; mapfile -t -n "$n" A ;; + -[0-9]*) + + n=${1#-}; + + (( kk = n < 20 ? 20 : n )); + + mapfile -t -n $kk A; + + (( ${#A[@]} >= n )) && while true; do + mapfile -t -n $kk A1 + + if (( ${#A1[@]} < kk )); then + A=("${A[@]}" "${A1[@]}") + break + else + printf '%s\n' "${A[@]}" + fi + mapfile -t -n $kk A + if (( ${#A[@]} < kk )); then + A=("${A1[@]}" "${A[@]}") + break + else + printf '%s\n' "${A1[@]}" + fi + + done + if (( ( ${#A[@]} - n ) >= 0 )); then + A=("${A[@]:0:${#A[@]}-n}") + else + return 0 + fi + + ;; + *) return 1 ;; + esac + fi + printf '%s\n' "${A[@]}" +} +fi + +# Fallback for tail -n N and tail -n +N +if ! type -p tail >/dev/null; then +tail() { + local -a A A1 + local n kk + if (( $# == 0 )); then + n=20 + else + [[ $1 == -n ]] && shift 1 + case $1 in + +[0-9]*) (( n = ${1#+} - 1 )); mapfile -t -n $n _; mapfile -t A; printf '%s\n' "${A[@]}"; return 0 ;; + [0-9]*) n=$1 ;; + *) return 1 ;; + esac + + fi + (( kk = n < 20 ? 20 : n )); + + mapfile -t -n $kk A; + + (( ${#A[@]} >= n )) && while true; do + mapfile -t -n $kk A1 + + if (( ${#A1[@]} < kk )); then + A=("${A[@]}" "${A1[@]}") + break + else + printf '%s\n' "${A[@]}" + fi + mapfile -t -n $kk A + if (( ${#A[@]} < kk )); then + A=("${A1[@]}" "${A[@]}") + break + else + printf '%s\n' "${A1[@]}" + fi + + done + (( ( ${#A[@]} - n ) >= 0 )) && A=("${A[@]:${#A[@]}-n}") ; + printf '%s\n' "${A[@]}" +} +fi - _forkrun_getLoad() { ## computes a "smoothed average system CPU load" using info gathered from /proc/stat # 100% load on all CPU's gives pLOAD=1000000 (1 million) @@ -2139,6 +2470,124 @@ _forkrun_getLoad() { } +shopt ${forkrun_extglobState} extglob +unset forkrun_extglobState + + +# +# +#_forkrun_loadable_setup() { +# ## sets up a "loadable" bash builtin for x86_64 machines +# local loadableArch cksumAlg cksumVal cksumAll loadablePre loadableDir loadableGetFlag loadableCurlFailedFlag forkrunRepo +# +# loadableGetFlag=false +# loadableCurlFailedFlag=false +# #forkrunRepo='main' +# forkrunRepo='forkrun_testing_nSpawn_5' +# +# type curl &>/dev/null || { +# if [[ -f "${BASH_LOADABLES_PATH%%:*}"/forkrun.so ]]; then +# enable -f "${BASH_LOADABLES_PATH%%:*}"/forkrun.so lseek 2>/dev/null +# enable -f "${BASH_LOADABLES_PATH%%:*}"/forkrun.so childusage 2>/dev/null +# else +# enable lseek 2>/dev/null +# enable childusage 2>/dev/null +# fi +# return +# } +# +# if type uname &>/dev/null; then +# loadableArch="$(uname -m)" +# elif [[ -f /proc/sys/kernel/arch ]] ; then +# loadableArch="$(/dev/null && break || cksumAlg='' +# done +# [[ ${cksumAlg} ]] && { +# cksumVal="$($cksumAlg "$loadablePre")" +# cksumVal="${cksumVal%% *}" +# cksumAll="$(curl 'https://raw.githubusercontent.com/jkool702/forkrun/refs/heads/'"${forkrunRepo}"'/loadables/CHECKSUMS' 2>/dev/null)" +# [[ "${cksumAll}" == *"${cksumVal}"* ]] || loadableGetFlag=true +# } +# } +# +# if [[ ${loadablePre} ]] && ! ${loadableGetFlag}; then +# enable -f "${loadablePre}" lseek 2>/dev/null || loadableGetFlag=true +# enable -f "${loadablePre}" childusage 2>/dev/null || loadableGetFlag=true +# else +# loadableGetFlag=true +# fi +# +# if ${loadableGetFlag}; then +# ${loadablePre} && \mv -f "${loadablePre}" "${loadablePre}".old +# case "${USER}" in +# root) loadableDir='/usr/local/lib/bash' ;; +# *) loadableDir='/dev/shm/.forkrun.loadable' ;; +# esac +# +# mkdir -p "${loadableDir}" +# [[ "${BASH_LOADABLES_PATH}" == *"${loadableDir}"* ]] || export BASH_LOADABLES_PATH="${loadableDir}:${BASH_LOADABLES_PATH}" +# curl -o "${loadableDir}"/forkrun.so 'https://raw.githubusercontent.com/jkool702/forkrun/'"${forkrunRepo}"'/loadables/bin/'"${loadableArch}"'/forkrun.so' || loadableCurlFailedFlag=true +# +# [[ ${loadablePre} ]] && { +# if ${loadableCurlFailedFlag}; then +# \mv "${loadablePre}".old "${loadablePre}" +# else +# enable -d lseek 2>/dev/null +# enable -d childusage 2>/dev/null +# fi +# } +# +# enable -f "${loadableDir}"/forkrun.so lseek 2>/dev/null || return 1 +# enable -f "${loadableDir}"/forkrun.so childusage 2>/dev/null || return 1 +# else +# enable -f "${loadableDir}"/forkrun.so lseek 2>/dev/null || return 1 +# enable -f "${loadableDir}"/forkrun.so childusage 2>/dev/null || return 1 +# fi +# +# echo 'abc' >/dev/shm/.forkrun.lseek.test +# { +# read -r -u $fd -N 1 +# lseek $fd -1 >/dev/null +# read -r -u $fd -N 1 +# exec {fd}>&- +# } {fd}&2 +# [[ ${loadablePre} ]] && ! ${loadableCurlFailedFlag} && { +# \rm -f "${loadablePre}" +# \mv "${loadablePre}".old "${loadablePre}" +# } +# return 1 +# ;; +# esac +#} +# +# +#_forkrun_loadable_setup +##enable -f forkrun_loadables.so evfd_init evfd_wait evfd_signal evfd_close evfd_copy lseek cpuusage childusage +# +#enable -f forkrun_loadables.so evfd_init evfd_wait evfd_signal evfd_close evfd_copy order_init order_get lseek cpuusage childusage # export -fp _forkrun_getLoad &>/dev/null && export -nf _forkrun_getLoad # # _forkrun_getLoad() ( diff --git a/hyperfine_benchmark/speedtest_vs_xargs_simple_v2.bash b/hyperfine_benchmark/speedtest_vs_xargs_simple_v2.bash index 6767c81..59c0930 100644 --- a/hyperfine_benchmark/speedtest_vs_xargs_simple_v2.bash +++ b/hyperfine_benchmark/speedtest_vs_xargs_simple_v2.bash @@ -15,29 +15,27 @@ find /mnt/ramdisk/usr -type f >/mnt/ramdisk/flist find /mnt/ramdisk/usr -type f -print0 >/mnt/ramdisk/flist0 ff() { -sha1sum "${@}" >>/mnt/ramdisk/sum.sha1sum -sha256sum "${@}" >>/mnt/ramdisk/sum.sha256sum -sha512sum "${@}" >>/mnt/ramdisk/sum.sha512sum -sha224sum "${@}" >>/mnt/ramdisk/sum.sha224sum -sha384sum "${@}" >>/mnt/ramdisk/sum.sha384sum -md5sum "${@}" >>/mnt/ramdisk/sum.md5sum -sum -s "${@}" >>/mnt/ramdisk/sum.sum_s -sum -r "${@}" >>/mnt/ramdisk/sum.sum_r -cksum "${@}" >>/mnt/ramdisk/sum.cksum -b2sum "${@}" >>/mnt/ramdisk/sum.b2sum -cksum -a sm3 "${@}" >>/mnt/ramdisk/sum.cksum_a_sm3 -xxhsum "${@}" >>/mnt/ramdisk/sum.xxhsum -xxhsum -H3 "${@}" >>/mnt/ramdisk/sum.xxhsum_H3 +sha1sum "${@}" +sha256sum "${@}" +sha512sum "${@}" +sha224sum "${@}" +sha384sum "${@}" +md5sum "${@}" +sum -s "${@}" +sum -r "${@}" +cksum "${@}" +b2sum "${@}" +cksum -a sm3 "${@}" +xxhsum "${@}" +xxhsum -H3 "${@}" } - export -f ff # # # # # forkrun # # # # # -# time { forkrun -z ff <../flist0; forkrun ff <../flist; cat /mnt/ramdisk/sum* | wc -l; } - +# time { { forkrun -z ff