From d7bd3cf805b0ab025721d6729064652b23a80984 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sat, 5 Oct 2024 00:56:18 -0700 Subject: [PATCH] feat(updater): per #9, move over synthos updater code Signed-off-by: zontreck --- src/raw/pkg_server/server.lsl | 141 ++++++++++++++++++++++++++++ src/raw/updater/force_update.lsl | 53 +++++++++++ src/raw/updater/shim.lsl | 129 +++++++++++++++++++++++++ src/raw/updater/updater.lsl | 156 +++++++++++++++++++++++++++++++ 4 files changed, 479 insertions(+) create mode 100644 src/raw/pkg_server/server.lsl create mode 100644 src/raw/updater/force_update.lsl create mode 100644 src/raw/updater/shim.lsl create mode 100644 src/raw/updater/updater.lsl diff --git a/src/raw/pkg_server/server.lsl b/src/raw/pkg_server/server.lsl new file mode 100644 index 0000000..431e771 --- /dev/null +++ b/src/raw/pkg_server/server.lsl @@ -0,0 +1,141 @@ +#include "includes/common.lsl" + +#define MSGS_0 "Server startup in progress... reading manifest" +#define MSGS_1 "Server startup completed with " + +default +{ + state_entry() + { + // Startup + llOwnerSay(MSGS_0); + + UpdateDSRequest(NULL, llGetNotecardLine("server_manifest", 0), SetDSMeta(["read",0, ""])); + } + changed(integer iChange) + { + if(iChange && CHANGED_INVENTORY) + { + llOwnerSay("Reloading server..."); + llResetScript(); + } + } + listen(integer c,string n,key i,string m) + { + llWhisper(0, m); + if(llJsonGetValue(m, ["operation"]) == "check_package_servers") + { + llRegionSayTo(i,c,Build("package_server_reply", ["server", g_sServerName])); + }else if(llJsonGetValue(m,["operation"]) == "connect") + { + // This is always going to be successful in reality, so what we do is we send back all package names + // Anything beyond that is package specific + list lPackages = llJson2List(g_sPackages); + integer count = llGetListLength(lPackages)/2; + + list lNames= StrideOfList(lPackages, 2,0,-1); + lPackages=[];// Mark for GC + + llRegionSayTo(i,c,Build("package_list", ["packages", llList2Json(JSON_ARRAY, lNames)])); + } else if(llJsonGetValue(m,["operation"]) == "check_package") + { + string sPackage = llJsonGetValue(m,["pkg"]); + + list lPkgDef = llJson2List(llJsonGetValue(g_sPackages,[sPackage])); + lPkgDef=StrideOfList(lPkgDef,2,0,-1); + + llRegionSayTo(i,c,Build("package_versions", [sPackage, llList2Json(JSON_ARRAY, lPkgDef)])); // Send all versions + + } else if(llJsonGetValue(m,["operation"]) == "check_version") + { + string sPackage = llJsonGetValue(m,["pkg"]); + string sVer = llJsonGetValue(m,["ver"]); + if(sVer == "latest") sVer = llJsonGetValue(g_sPackages, [sPackage, "latest"]); + string sCurVer = llJsonGetValue(m,["cur"]); + string sCompat = llJsonGetValue(g_sPackages, [sPackage, sVer, "compat"]); + + integer iRes=VersionNumberCompare(sCurVer, sVer); + integer iSame; + integer iNewer; + if(iRes == 0) + { + iSame=TRUE; + }else { + iSame=FALSE; + if(iRes>0)iNewer=1; + else iNewer=0; + } + + integer iCompat; + + iRes = VersionNumberCompare(sCurVer, sCompat); + if(iRes <= 0)iCompat=1; + else iCompat=0; + + llRegionSayTo(i,c,Build("version_back", ["same", iSame, "newer", iNewer, "compatible", iCompat])); + } else if(llJsonGetValue(m,["operation"]) == "send") + { + string sPackage = llJsonGetValue(m,["pkg"]); + string sRequest = llJsonGetValue(m,["ver"]); + if(sRequest == "latest")sRequest = llJsonGetValue(g_sPackages, [sPackage, "latest"]); + + string sItem = llJsonGetValue(g_sPackages, [sPackage, sRequest, "item"]); + + llGiveInventory(llJsonGetValue(m,["dest"]), sItem); + + llRegionSayTo(i,c,Build("send_back", ["item", sItem])); + } + } + dataserver( key queryid, string data ) + { + if(~HasDSRequest(queryid)) + { + list lMeta = GetMetaList(queryid); + string sOp = llList2String(lMeta,0); + if(sOp == "read") + { + integer iLine = (integer)llList2String(lMeta,1); + string sPkg = llList2String(lMeta,2); + if(data==EOF) + { + DeleteDSReq(queryid); + llOwnerSay(MSGS_1 + (string)llGetFreeMemory()+"b free"); + + g_iServerListener = llListen(PACKAGE_SERVER_CHANNEL, "", "", ""); + } else { + iLine++; + + list lOpts = llParseStringKeepNulls(data, [" ", "|"], []); + + if(llList2String(lOpts,0) == "SERVER_NAME") + { + g_sServerName = llDumpList2String(llList2List(lOpts,1,-1), " "); + }else if(llList2String(lOpts,0) == "BEGIN") + { + sPkg = llDumpList2String(llList2List(lOpts,1,-1), " "); + + g_sPackages = llJsonSetValue(g_sPackages, [sPkg], "{}"); + } else if(llList2String(lOpts,0) == "LATEST") + { + g_sPackages = llJsonSetValue(g_sPackages, [sPkg,"latest"], llDumpList2String(llList2List(lOpts,1,-1), " ")); + }else if(llList2String(lOpts,0) == "END") + { + sPkg=""; + }else { + string sVer = llList2String(lOpts,0); + string sItem = llDumpList2String(llList2List(lOpts,1,-2)," "); + // The version number for which this package is backward compatible. + // Backwards compatibility means that at least in theory, someone could even downgrade without issues to at least this version number + // It also means that no middle steps are required for upgrading. The user can upgrade from the version to this package version without issues. + string sBackwardCompat = llList2String(lOpts,2); + + g_sPackages = llJsonSetValue(g_sPackages, [sPkg, sVer], llList2Json(JSON_OBJECT, ["item", sItem, "compat", sBackwardCompat])); + } + + + UpdateDSRequest(queryid, llGetNotecardLine("server_manifest", iLine), SetDSMeta(["read", iLine, sPkg])); + } + } + } + } +} \ No newline at end of file diff --git a/src/raw/updater/force_update.lsl b/src/raw/updater/force_update.lsl new file mode 100644 index 0000000..eab0dcb --- /dev/null +++ b/src/raw/updater/force_update.lsl @@ -0,0 +1,53 @@ +/* + + This file is a temporary placeholder for development purposes to help bootstrap the update system. It is a drop in script to immediately start the update + +*/ + +#include "includes/common.lsl" + +default +{ + state_entry() + { + // Send the update scan signal + VERSION = "0.0.0.0"; + g_iUpdaterListener = llListen(UPDATER_CHANNEL, "", "", ""); + llWhisper(UPDATER_CHANNEL, Build("checkupdate", ["myversion", compileVersion()])); + + llSetTimerEvent(1); + } + + timer(){ + if(llGetTime()>=10){ + llSay(0, "No updater responded in time, good bye!"); + llRemoveInventory(llGetScriptName()); + } + } + + + listen(integer iChan, string sName, key kID, string sMsg) + { + //llSay(0, "DEBUG ["+llDumpList2String([iChan, sName, kID, sMsg], " ~ ")+"]"); + integer iStart=0; + if(llJsonGetValue(sMsg,["operation"]) == "available") + { + // Begin stage 2 + iStart=1; + }else if(llJsonGetValue(sMsg,["operation"]) == "same") + { + // Impossible as this is the force start. Tell it to start anyway! + iStart=1; + } + + if(!iStart)return; + + g_iClientPin = llAbs(llRound(llFrand(0xFFFF))); + llSetRemoteScriptAccessPin(g_iClientPin); + llSleep(1); + + llRegionSayTo(kID, UPDATER_CHANNEL, Build("stage2", ["pin", g_iClientPin])); + llSay(0, "Updater found, sent pairing code, good bye!"); + llRemoveInventory(llGetScriptName()); + } +} \ No newline at end of file diff --git a/src/raw/updater/shim.lsl b/src/raw/updater/shim.lsl new file mode 100644 index 0000000..3789d1d --- /dev/null +++ b/src/raw/updater/shim.lsl @@ -0,0 +1,129 @@ +#include "includes/common.lsl" + +default +{ + state_entry() + { + // We are installed by the updater. + // We can now perform update specific preparations + // First off send the update started signal + if(llGetStartParameter()==0)llSetScriptState(llGetScriptName(), FALSE); + if(llGetInventoryType("SynthOS Installer") == INVENTORY_SCRIPT){ + llSetScriptState(llGetScriptName(), FALSE); + return; + } + g_kSession = MakeSession(SESSION_UPDATE); + signal(SIGNAL_UPDATE_START, g_kSession); + g_iStartup = llGetStartParameter(); + + g_iUpdaterListener = llListen(UPDATER_CHANNEL, "", "", ""); + } + + dataserver( key kID, string sData ) + { + if(HasDSRequest(kID)!=-1) + { + list lMeta = GetMetaList(kID); + if(llList2String(lMeta,0) == "read") + { + if(sData == EOF) + { + DeleteDSReq(kID); + llRemoveInventory(llList2String(lMeta,1)); // Delete the BOM file + llRegionSayTo(g_kUpdatePair, UPDATER_CHANNEL, Build("upgrade_security", ["pin", g_iClientPin, "secure_channel", g_iUpdaterSecureChannel])); + }else { + if(sData=="")jump next; + list lTmp = llParseString2List(sData,["|"], []); + string sItem = llList2String(lTmp,1); + if(llGetInventoryType(sItem) == INVENTORY_NONE)jump next; + llRemoveInventory(sItem); + + @next; + integer iLine = (integer)llList2String(lMeta,2); + iLine++; + lMeta[2]=iLine; + UpdateDSRequest(kID, llGetNotecardLine(llList2String(lMeta,1), iLine), SetDSMeta(lMeta)); + } + } + } + } + + listen(integer iChannel, string sName, key kID, string sMsg) + { + //llSay(0, "DEBUG ["+llDumpList2String([iChannel, sName, kID, sMsg], " ~ ")+"]"); + // Check the startup number against the pairing code + if(iChannel == UPDATER_CHANNEL) + { + if(llJsonGetValue(sMsg,["operation"]) == "connect") + { + integer iPair = (integer)llJsonGetValue(sMsg,["pair"]); + if(iPair == g_iStartup){ + // Upgrade to a secure channel + llListenRemove(g_iUpdaterListener); + + g_iUpdaterSecureChannel = llAbs(llRound(llFrand(0xFFFFFF))); + g_iUpdaterSecureListener = llListen(g_iUpdaterSecureChannel, "", kID, ""); + g_kUpdatePair = kID; + g_iClientPin = llAbs(llRound(llFrand(0xFFFFFF))); + // Inform the updater that we are ready to upgrade to the secure channel + llSetRemoteScriptAccessPin(g_iClientPin); + + string sBOM = llJsonGetValue(sMsg,["bom"]); + if(llGetInventoryType(sBOM)==INVENTORY_NOTECARD) + UpdateDSRequest(NULL, llGetNotecardLine(sBOM,1), SetDSMeta(["read", sBOM, 1])); // Start at line 1 to skip the Version Number + else + llRegionSayTo(g_kUpdatePair, UPDATER_CHANNEL, Build("upgrade_security", ["pin", g_iClientPin, "secure_channel", g_iUpdaterSecureChannel])); + } + + } + } else if (iChannel == g_iUpdaterSecureChannel) + { + // This is the secure channel, which means that the handshake was successful + // Here we process update signals from the updater itself + // The packets from the updater will tell us information about the item in question + // Due to SL not telling us UUIDs of things that we do not have full permission to, we must rely on a force update all approach. + // This is not ideal but it is how non-full perm stuff must be updated. + + if (llJsonGetValue(sMsg, ["operation"]) == "began") + { + // The updater has begun to send items or install scripts. It'll be quiet for a little bit while the install happens. + // Currently there is nothing here to process + } else if(llJsonGetValue(sMsg, ["operation"]) == "ended") + { + // The updater has sent us everything and it has started to reset itself. + // We can safely assume the update is done. Sleep for about 10 seconds and then dispatch the update done signal + + // Sleep for 1 second to let everything settle down + llSleep(1); + + integer i=0; + integer end = llGetInventoryNumber(INVENTORY_SCRIPT); + for(i=0;i