From b2476f9bef82a52881a00bcfa84b41d2bcde6eae Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Thu, 11 Jun 2026 01:05:07 -0700 Subject: [PATCH 1/4] Code Improvements and Fine-Tuning - Replaced the newly-added FLock mechanism with the existing Lock mechanism already in place since it can handle a blocking lock to wait until the current process releases the lock. - Code improvements when mounting the GNUton-specific webs_update.sh file to make sure it's installed only if the F/W-installed version is older than the fixed version from MerlinAU. --- MerlinAU.sh | 156 +++++++++++++++++++++------------------------------- README.md | 2 +- 2 files changed, 63 insertions(+), 95 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 1df6fb2d..c3c06772 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -4,16 +4,16 @@ # # Original Creation Date: 2023-Oct-01 by @ExtremeFiretop. # Official Co-Author: @Martinski W. - Date: 2023-Nov-01 -# Last Modified: 2026-May-21 +# Last Modified: 2026-Jun-11 ################################################################### set -u ## Set version for each Production Release ## readonly SCRIPT_VERSION=1.6.4 -readonly SCRIPT_VERSTAG="26061019" +readonly SCRIPT_VERSTAG="26061100" readonly SCRIPT_NAME="MerlinAU" ## Set to "master" for Production Releases ## -SCRIPT_BRANCH="master" +SCRIPT_BRANCH="dev" ##----------------------------------------## ## Modified by Martinski W. [2024-Jul-03] ## @@ -413,7 +413,7 @@ _WaitForYESorNO_() ## Modified by Martinski W. [2025-Jan-11] ## ##----------------------------------------## readonly LockFilePath="/tmp/var/${ScriptFNameTag}.LOCK" -readonly LockTypeRegEx="(cliMenuLock|cliOptsLock|cliFileLock)" +readonly LockTypeRegEx="(cliInitLock|cliMenuLock|cliOptsLock|cliFileLock)" _FindLockFileTypes_() { grep -woE "$LockTypeRegEx" "$LockFilePath" | tr '\n' ' ' | sed 's/[ ]*$//' ; } @@ -434,7 +434,7 @@ _ReleaseLock_() fi [ -s "$LockFilePath" ] && return 0 fi - rm -f "$LockFilePath" + printf '' > "$LockFilePath" } ## Defaults ## @@ -445,7 +445,7 @@ LockFileMaxAgeSecs=600 #10-minutes# if [ $# -eq 0 ] || [ -z "$1" ] then #Interactive Mode# - LockMaxTimeoutSecs=3 + LockMaxTimeoutSecs=5 LockFileMaxAgeSecs=1200 else case "$1" in @@ -478,7 +478,7 @@ _AcquireLock_() _CreateLockFile_() { echo "$$|$lockTypeReq" > "$LockFilePath" ; } - if [ ! -f "$LockFilePath" ] + if [ ! -s "$LockFilePath" ] then _CreateLockFile_ ; return 0 fi @@ -546,12 +546,6 @@ fwupMutexFLock_FD=576 fwupMutexFLock_FN="/tmp/var/${ScriptFNameTag}_FW_Update.FLock" fwupMutexFLock_OK=false #DO NOT have FLock# -##-------------------------------------## -## Added for initialization operations ## -##-------------------------------------## -initMutexFLock_FD=577 -initMutexFLock_FN="/tmp/var/${ScriptFNameTag}_Initialization.FLock" - _ReleaseMutexFLock_() { if [ $# -gt 0 ] && \ @@ -779,7 +773,8 @@ Toggle_LEDs_PID="" # To enable/disable the built-in "F/W Update Check" # FW_UpdateCheckState="$(nvram get firmware_check_enable)" -FW_UpdateCheckScript="/usr/sbin/webs_update.sh" +readonly FW_WebsUpdateFile="webs_update.sh" +readonly FW_UpdateCheckScript="/usr/sbin/$FW_WebsUpdateFile" ##-------------------------------------## ## Added by Martinski W. [2023-Nov-24] ## @@ -2229,20 +2224,19 @@ readonly POST_UPDATE_EMAIL_SCRIPT_HOOK="[ -x $ScriptFilePath ] && $POST_UPDATE_E _CleanUpOldLogFiles_() { [ ! -d "$FW_LOG_DIR" ] && return 1 - local numLogFiles topFile savedTopFile + local numLogFiles topLogFile savedTopLogFile="" numLogFiles="$(ls -1lt "$FW_LOG_DIR"/*.log 2>/dev/null | wc -l)" # Leave one log file (if any available) # [ "$numLogFiles" -lt 2 ] && return 0 # Save the most recent log file # - topFile="$(ls -1t "$FW_LOG_DIR"/*.log 2>/dev/null | head -n1)" + topLogFile="$(ls -1t "$FW_LOG_DIR"/*.log 2>/dev/null | head -n1)" - if [ -n "$topFile" ] + if [ -n "$topLogFile" ] && [ -s "$topLogFile" ] then - savedTopFile="${topFile}.SAVED.$$.TEMP.LOG" - - if ! mv -f "$topFile" "$savedTopFile" + savedTopLogFile="${topLogFile}.SAVED.$$.TEMP.LOG" + if ! mv -f "$topLogFile" "$savedTopLogFile" then return 1 fi @@ -2252,14 +2246,15 @@ _CleanUpOldLogFiles_() /usr/bin/find -L "$FW_LOG_DIR" -name '*.log' -mtime +30 -exec rm {} \; # Restore the most recent log file # - if [ -n "$topFile" ] && [ -f "$savedTopFile" ] + if [ -n "$topLogFile" ] && \ + [ -n "$savedTopLogFile" ] && \ + [ -s "$savedTopLogFile" ] then - if ! mv -f "$savedTopFile" "$topFile" + if ! mv -f "$savedTopLogFile" "$topLogFile" then return 1 fi fi - return 0 } @@ -12057,9 +12052,9 @@ _DoInitializationStartup_() _SetDefaultBuildType_ } -##------------------------------------------## -## Modified by ExtremeFiretop [2026-Jun-10] ## -##------------------------------------------## +##----------------------------------------## +## Modified by Martinski W. [2026-Jun-11] ## +##----------------------------------------## ####################################################################### # TEMPORARY hack to check if the Gnuton F/W built-in 'webs_update.sh' # script is the most recent version that includes required fixes. @@ -12077,10 +12072,9 @@ _Gnuton_Check_Webs_Update_Script_() return 0 fi - local theWebsUpdateFile="webs_update.sh" - local fixedGnutonWebsUpdateFilePath="${SETTINGS_DIR}/$theWebsUpdateFile" - local dwnldGnutonWebsUpdateFilePath="${SETTINGS_DIR}/${theWebsUpdateFile}.GNUTON.$$" - local localVersTag remoteVersTag diffRC + local fixedGnutonWebsUpdateFilePath="${SETTINGS_DIR}/$FW_WebsUpdateFile" + local dwnldGnutonWebsUpdateFilePath="${SETTINGS_DIR}/${FW_WebsUpdateFile}.GNUTON.$$" + local localVersTag remoteVersTag diffRC mountPoint # Get local VERSTAG (if any) # localVersTag="$(_Get_GnutonWebUpdate_ScriptVersTag_ "$FW_UpdateCheckScript")" @@ -12091,11 +12085,10 @@ _Gnuton_Check_Webs_Update_Script_() then if ! chmod 755 "$dwnldGnutonWebsUpdateFilePath" then - Say "${REDct}**ERROR**${NOct}: Unable to set permissions on the downloaded GNUton webs_update.sh file." + Say "${REDct}**ERROR**${NOct}: Unable to set permissions on the downloaded GNUton \"${FW_WebsUpdateFile}\" file." rm -f "$dwnldGnutonWebsUpdateFilePath" return 1 fi - remoteVersTag="$(_Get_GnutonWebUpdate_ScriptVersTag_ "$dwnldGnutonWebsUpdateFilePath")" [ -z "$remoteVersTag" ] && remoteVersTag=0 else @@ -12103,118 +12096,88 @@ _Gnuton_Check_Webs_Update_Script_() return 1 fi - # Distinguish files that differ from an actual comparison error. + # Distinguish files that differ from an actual comparison error # diff -q "$FW_UpdateCheckScript" "$dwnldGnutonWebsUpdateFilePath" >/dev/null 2>&1 - - diffRC=$? + diffRC="$?" if [ "$diffRC" -gt 1 ] then - Say "${REDct}**ERROR**${NOct}: Unable to compare the GNUton webs_update.sh files." + Say "${REDct}**ERROR**${NOct}: Unable to compare the GNUton \"${FW_WebsUpdateFile}\" files." rm -f "$dwnldGnutonWebsUpdateFilePath" return 1 fi # (Re)bind/mount only if remote is newer version OR files differ # if [ "$remoteVersTag" -gt "$localVersTag" ] || \ - [ "$diffRC" -eq 1 ] + { [ "$remoteVersTag" -eq "$localVersTag" ] && [ "$diffRC" -ne 0 ] ; } then - # Remove all existing bind-mount layers, including duplicates. - while awk -v target="$FW_UpdateCheckScript" ' - $2 == target { found=1 } - END { exit !found } - ' /proc/mounts - do - if ! umount "$FW_UpdateCheckScript" - then - Say "${REDct}**ERROR**${NOct}: Unable to unmount \"$FW_UpdateCheckScript\"." - rm -f "$dwnldGnutonWebsUpdateFilePath" - return 1 - fi + for mountPoint in $(grep "$FW_UpdateCheckScript" /proc/mounts | awk -F' ' '{print $2}') + do umount "$FW_UpdateCheckScript" 2>/dev/null done + if grep -q "$FW_UpdateCheckScript" /proc/mounts + then + Say "${REDct}**ERROR**${NOct}: Unable to unmount \"$FW_UpdateCheckScript\"." + rm -f "$dwnldGnutonWebsUpdateFilePath" + return 1 + fi + if ! mv -f "$dwnldGnutonWebsUpdateFilePath" "$fixedGnutonWebsUpdateFilePath" then - Say "${REDct}**ERROR**${NOct}: Unable to install the fixed GNUton webs_update.sh file." + Say "${REDct}**ERROR**${NOct}: Unable to install the fixed GNUton \"${FW_WebsUpdateFile}\" file." rm -f "$dwnldGnutonWebsUpdateFilePath" return 1 fi - if [ ! -f "$fixedGnutonWebsUpdateFilePath" ] + if [ ! -s "$fixedGnutonWebsUpdateFilePath" ] then - Say "${REDct}**ERROR**${NOct}: GNUton webs_update.sh source file is missing before bind mount." + Say "${REDct}**ERROR**${NOct}: GNUton \"${FW_WebsUpdateFile}\" source file is missing before bind mount." return 1 fi - if [ ! -e "$FW_UpdateCheckScript" ] + if [ ! -s "$FW_UpdateCheckScript" ] then Say "${REDct}**ERROR**${NOct}: Bind-mount target \"$FW_UpdateCheckScript\" does not exist." return 1 fi - if ! mount -o bind \ - "$fixedGnutonWebsUpdateFilePath" \ - "$FW_UpdateCheckScript" + if ! mount -o bind "$fixedGnutonWebsUpdateFilePath" "$FW_UpdateCheckScript" then - Say "${REDct}**ERROR**${NOct}: Unable to bind-mount the fixed GNUton webs_update.sh file." + Say "${REDct}**ERROR**${NOct}: Unable to bind-mount the fixed GNUton \"${FW_WebsUpdateFile}\" file." return 1 fi - - Say "${YLWct}Set up a fixed version of the \"${theWebsUpdateFile}\" script file.${NOct}" + Say "${YLWct}Set up a fixed version of the \"${FW_WebsUpdateFile}\" script file.${NOct}" else rm -f "$dwnldGnutonWebsUpdateFilePath" fi - return 0 } -##---------------------------------------## -## Added by ExtremeFiretop [2026-Jun-10] ## -##---------------------------------------## -# Serialize startup-time operations that use shared log and GNUton files. +##----------------------------------------## +## Modified by Martinski W. [2026-Jun-11] ## +##----------------------------------------## +# Startup/Initialization operations done by possible concurrent processes # _RunLockedInitializationChecks_() { local retCode=0 - [ ! -d /tmp/var ] && mkdir -p /tmp/var - - # Open the dedicated lock file on its own file descriptor. - if ! eval "exec ${initMutexFLock_FD}>\"${initMutexFLock_FN}\"" - then - Say "${REDct}**ERROR**${NOct}: Unable to open the initialization lock file." - return 1 - fi - - # Use a blocking exclusive lock so concurrent MerlinAU processes wait. - if ! flock -x "$initMutexFLock_FD" 2>/dev/null - then - Say "${REDct}**ERROR**${NOct}: Unable to acquire the initialization lock." - eval "exec ${initMutexFLock_FD}>&-" - return 1 - fi - - # These operations access shared paths and must not overlap. - if [ -d "$FW_LOG_DIR" ] && \ - ! _CleanUpOldLogFiles_ + if ! _CleanUpOldLogFiles_ then Say "${YLWct}WARNING:${NOct} Unable to clean up old firmware-update log files." retCode=1 fi + # Set variable to 'false' to stop the check # checkWebsUpdateScriptForGnuton="$isGNUtonFW" if ! _Gnuton_Check_Webs_Update_Script_ then - Say "${YLWct}WARNING:${NOct} Unable to check or install the GNUton webs_update.sh patch." + Say "${YLWct}WARNING:${NOct} Unable to check or install the GNUton \"${FW_WebsUpdateFile}\" patch." retCode=1 fi - flock -u "$initMutexFLock_FD" 2>/dev/null - eval "exec ${initMutexFLock_FD}>&-" - return "$retCode" } - if [ "$SCRIPT_BRANCH" = "master" ] then SCRIPT_VERS_INFO="" else SCRIPT_VERS_INFO="[$versionDev_TAG]" @@ -12224,6 +12187,14 @@ FW_InstalledVersion="$(_GetCurrentFWInstalledLongVersion_)" FW_InstalledVerStr="${GRNct}${FW_InstalledVersion}${NOct}" FW_NewUpdateVerInit=TBD +if _AcquireLock_ cliInitLock +then + _RunLockedInitializationChecks_ + _ReleaseLock_ cliInitLock +else + Say "Exiting..." ; exit 1 +fi + ##----------------------------------------## ## Modified by Martinski W. [2025-Apr-07] ## ##----------------------------------------## @@ -12234,8 +12205,6 @@ then then Say "Exiting..." ; exit 1 fi - _RunLockedInitializationChecks_ - inMenuMode=true _DoInitializationStartup_ _CheckFor_VersionFile_ @@ -12261,9 +12230,8 @@ fi if [ $# -gt 0 ] then if ! _AcquireLock_ cliOptsLock - then Say "Exiting..." ; exit 1 ; fi - - _RunLockedInitializationChecks_ + then Say "Exiting..." ; exit 1 + fi [ "$1" = "amtmupdate" ] && isVerbose=false diff --git a/README.md b/README.md index 6904f844..de39449f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MerlinAU - AsusWRT-Merlin Firmware Auto Updater ## v1.6.4 -## 2026-June-10 +## 2026-June-11 ## WebUI: image From db8dad9ca1b982a8ad58c51a2578ca59b717bbb0 Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Thu, 11 Jun 2026 03:10:29 -0700 Subject: [PATCH 2/4] Update MerlinAU.sh Actually I re-added the separate, dedicated FLOCK blocking mechanism to protect the initialization routines because it was a cleaner and leaner implementation for fast operations. --- MerlinAU.sh | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index c3c06772..81d9b858 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -539,6 +539,27 @@ _AcquireLock_() return "$retCode" } +##-------------------------------------## +## Added for initialization operations ## +##-------------------------------------## +initMutexFLock_FD=577 +initMutexFLock_FN="/tmp/var/${ScriptFNameTag}_Initialization.FLock" + +#--------------------------------------------------------------# +# This is a mutually exclusive, blocking FLOCK mechanism used +# to protect initialization routines when concurrent MerlinAU +# executions are running (e.g. called from services-start). +#--------------------------------------------------------------# +_AcquireInitMutexFLock_() +{ + eval exec "$initMutexFLock_FD>$initMutexFLock_FN" + flock -x "$initMutexFLock_FD" 2>/dev/null + return "$?" +} + +_ReleaseInitMutexFLock_() +{ flock -u "$initMutexFLock_FD" 2>/dev/null ; } + ##-------------------------------------## ## Added by Martinski W. [2026-Mar-18] ## ##-------------------------------------## @@ -2236,7 +2257,7 @@ _CleanUpOldLogFiles_() if [ -n "$topLogFile" ] && [ -s "$topLogFile" ] then savedTopLogFile="${topLogFile}.SAVED.$$.TEMP.LOG" - if ! mv -f "$topLogFile" "$savedTopLogFile" + if ! mv -f "$topLogFile" "$savedTopLogFile" 2>/dev/null then return 1 fi @@ -2250,7 +2271,7 @@ _CleanUpOldLogFiles_() [ -n "$savedTopLogFile" ] && \ [ -s "$savedTopLogFile" ] then - if ! mv -f "$savedTopLogFile" "$topLogFile" + if ! mv -f "$savedTopLogFile" "$topLogFile" 2>/dev/null then return 1 fi @@ -12083,7 +12104,7 @@ _Gnuton_Check_Webs_Update_Script_() # Get the fixed version of the script targeted for Gnuton F/W # if _CurlFileDownload_ "gnuton_webs_update.sh" "$dwnldGnutonWebsUpdateFilePath" then - if ! chmod 755 "$dwnldGnutonWebsUpdateFilePath" + if ! chmod 755 "$dwnldGnutonWebsUpdateFilePath" 2>/dev/null then Say "${REDct}**ERROR**${NOct}: Unable to set permissions on the downloaded GNUton \"${FW_WebsUpdateFile}\" file." rm -f "$dwnldGnutonWebsUpdateFilePath" @@ -12122,7 +12143,7 @@ _Gnuton_Check_Webs_Update_Script_() return 1 fi - if ! mv -f "$dwnldGnutonWebsUpdateFilePath" "$fixedGnutonWebsUpdateFilePath" + if ! mv -f "$dwnldGnutonWebsUpdateFilePath" "$fixedGnutonWebsUpdateFilePath" 2>/dev/null then Say "${REDct}**ERROR**${NOct}: Unable to install the fixed GNUton \"${FW_WebsUpdateFile}\" file." rm -f "$dwnldGnutonWebsUpdateFilePath" @@ -12187,12 +12208,13 @@ FW_InstalledVersion="$(_GetCurrentFWInstalledLongVersion_)" FW_InstalledVerStr="${GRNct}${FW_InstalledVersion}${NOct}" FW_NewUpdateVerInit=TBD -if _AcquireLock_ cliInitLock +if _AcquireInitMutexFLock_ then _RunLockedInitializationChecks_ - _ReleaseLock_ cliInitLock + _ReleaseInitMutexFLock_ else - Say "Exiting..." ; exit 1 + Say "${REDct}**ERROR**${NOct}: Unable to acquire the initialization lock. Exiting..." + exit 1 fi ##----------------------------------------## From e94a477cd25fb4b65953622abeeb2f6583d99c00 Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Thu, 11 Jun 2026 04:06:56 -0700 Subject: [PATCH 3/4] Update MerlinAU.sh Make sure to close the FD when releasing the lock before continuing execution. --- MerlinAU.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index 81d9b858..f16055c1 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -552,13 +552,19 @@ initMutexFLock_FN="/tmp/var/${ScriptFNameTag}_Initialization.FLock" #--------------------------------------------------------------# _AcquireInitMutexFLock_() { - eval exec "$initMutexFLock_FD>$initMutexFLock_FN" - flock -x "$initMutexFLock_FD" 2>/dev/null - return "$?" + eval exec "${initMutexFLock_FD}>$initMutexFLock_FN" + if flock -x "$initMutexFLock_FD" 2>/dev/null + then return 0 + fi + eval exec "${initMutexFLock_FD}>&-" + return 1 } _ReleaseInitMutexFLock_() -{ flock -u "$initMutexFLock_FD" 2>/dev/null ; } +{ + flock -u "$initMutexFLock_FD" 2>/dev/null + eval exec "${initMutexFLock_FD}>&-" +} ##-------------------------------------## ## Added by Martinski W. [2026-Mar-18] ## From c70fb5016e29b7e77563f45ddea8fdc9ba13a94b Mon Sep 17 00:00:00 2001 From: Martinski4GitHub <119833648+Martinski4GitHub@users.noreply.github.com> Date: Thu, 11 Jun 2026 18:23:47 -0700 Subject: [PATCH 4/4] Remove unused parameter for "Lock Type" --- MerlinAU.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MerlinAU.sh b/MerlinAU.sh index f16055c1..f0552434 100644 --- a/MerlinAU.sh +++ b/MerlinAU.sh @@ -10,7 +10,7 @@ set -u ## Set version for each Production Release ## readonly SCRIPT_VERSION=1.6.4 -readonly SCRIPT_VERSTAG="26061100" +readonly SCRIPT_VERSTAG="26061118" readonly SCRIPT_NAME="MerlinAU" ## Set to "master" for Production Releases ## SCRIPT_BRANCH="dev" @@ -413,7 +413,7 @@ _WaitForYESorNO_() ## Modified by Martinski W. [2025-Jan-11] ## ##----------------------------------------## readonly LockFilePath="/tmp/var/${ScriptFNameTag}.LOCK" -readonly LockTypeRegEx="(cliInitLock|cliMenuLock|cliOptsLock|cliFileLock)" +readonly LockTypeRegEx="(cliMenuLock|cliOptsLock|cliFileLock)" _FindLockFileTypes_() { grep -woE "$LockTypeRegEx" "$LockFilePath" | tr '\n' ' ' | sed 's/[ ]*$//' ; }