/* ========================================================================= * * Nanite Systems Advanced Research Encapsulation System * * Copyright (c) 2022–2024 Nanite Systems Corporation * * ========================================================================= * * Exec System Module * * This program is covered under the terms of the ARES Software Copyright * License, Section 1 (ASCL-i). It is offered to you on a limited basis to * facilitate modification and customization. * * To see the full text of the ASCL, type 'help license' on any standard * ARES distribution, or visit http://nanite-systems.com/ASCL for the * current version. * * DISCLAIMER * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS * IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY * DAMAGES HOWEVER CAUSED ON ANY THEORY OF LIABILITY ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * ========================================================================= * */ #include #define CLIENT_VERSION ARES_VERSION #define CLIENT_VERSION_TAGS ARES_VERSION_TAGS // it's good enough for real computers: #define NON_VOLATILE #define DAEMON_LISTEN #ifdef TRACE integer trace; #endif string file_name; integer file_offset; integer file_length; string file_unit; key file_pipe; key file_outs; key file_user; string script; list script_lines; integer script_li; key script_handle = NULL_KEY; // don't continue executing a script until we get a suitable DONE back string expression(list tokens) { if((string)tokens == "undefined") return JSON_INVALID; float value = (float)gets(tokens, 0); integer ki = 1; integer kmax = count(tokens); while(ki < kmax) { string op = gets(tokens, ki); float r = (float)gets(tokens, ki + 1); if(op == "!=") { value = (float)(llFabs(value - r) >= 0.001); } else if(op == "==") { value = (float)(llFabs(value - r) < 0.001); } else if(op == ">") { value = (float)((value - r) > 0.001); } else if(op == "<") { value = (float)((r - value) > 0.001); } else if(op == ">=") { value = (float)((value - r) > -0.001); } else if(op == "<=") { value = (float)((r - value) > -0.001); } else if(op == "+") { value += r; } else if(op == "-") { value -= r; } else if(op == "*") { value *= r; } else if(op == "%") { value = (float)((integer)value % (integer)r); } else if(op == "**") { value = llPow(value, r); } else if(op == "/" || op == "\\") { if(r != 0) { value /= r; } else { echo("[_exec] division by 0 in expression: " + concat(tokens, " ")); return "0"; } if(op == "\\") value = (integer)value; } else { echo("[_exec] unknown operator '" + op + "' in expression: " + concat(tokens, " ")); return "0"; } ki += 2; } // return integer if within a thousandth: if(llFabs((float)((integer)value) - value) < 0.001) return (string)((integer)value); else return (string)value; } string pipe_queue = "{}"; // {handle:[[commands], outs, ins, user, rc, [pipe-keys]]} - will replace script handle integer process_input(key outs, key handle, key user, string cml, integer in_script) { @internal_restart; if(in_script) script_handle = handle = llGenerateKey(); #ifdef TRACE if(trace) echo(" >> " + cml); #endif string first = substr(cml, 0, 0); string second = substr(cml, 0, 1); if(~strpos(cml, "\\n")) cml = replace(cml, "\\n", "\n"); // ======================================= // shell control commands // ======================================= integer varpos = strpos(cml, "$"); if(~varpos) { integer vi = varpos; integer vL = strlen(cml); string varacc; integer vaL = 0; integer vaStart; integer vp_state = 0; while(vi < vL) { integer vc = llOrd(cml, vi); ++vi; if(vc == 0x24 && vp_state == 0) { // '$' if(llOrd(cml, vi - 2) == 0x5c) { // '\' cml = delstring(cml, vi - 2, vi - 2); --vi; --vL; } else { varacc = ""; vaL = 0; vaStart = vi - 1; vp_state = 1; } } else if(vp_state == 1) { integer vd = llOrd(cml, vi); //if(vc == 0x2e) echo((string)vd); if((vc >= 0x41 && vc <= 0x5a) // uppercase || (vc >= 0x61 && vc <= 0x7a) // lowercase || vc == 0x5f || vc == 0x2d // _ and - || (vaL > 0 && vc == 0x2e && (vd >= 0x61 && vd <= 0x7a)) // . followed by lowercase letter || (vaL > 0 && vc == 0x2e && (vd >= 0x30 && vd <= 0x39)) // . followed by digit || (vaL > 0 && (vc >= 0x30 && vc <= 0x39))) { // digits if non-first varacc += llChar(vc); // echo("added " + (string)vc); ++vaL; } else { vp_state = 0; } if(vi == vL && vp_state) { vp_state = 0; // end automatically at string end ++vi; } } if(vaL && vp_state == 0) { // echo("subbing in " + varacc); string sub; if(varacc == "name") { sub = llGetDisplayName(user); } else if(varacc == "user") { sub = (string)user; } else if(varacc == "self") { sub = (string)avatar; } else if(varacc == "me") { sub = getdbl("id", ["callsign"]); } else { sub = getdbl("env", split(varacc, ".")); } cml = delstring(cml, vaStart, vi - 2); cml = llInsertString(cml, vaStart, sub); vi -= vaL; vi += strlen(sub) - 2; vL = strlen(cml); vaStart = 0; vaL = 0; varacc = ""; } } } list cargv = splitnulls(cml, " "); string prog = gets(cargv, 0); string aliascmd; if((aliascmd = getdbl("input", ["alias", prog])) != JSON_INVALID) { cml = aliascmd + " " + concat(delitem(cargv, 0), " "); cargv = splitnulls(cml, " "); prog = gets(cargv, 0); } // string pipe_queue; // {handle:[[commands], outs, ins, user, rc]} - will replace script handle cml = replace(cml, "\\|", NAK); integer pipe_pos = strpos(cml, "|"); if(~pipe_pos) { list commands = split(replace(cml, "|", "|exec "), "|"); list pipe_keys; integer cimax = count(commands); integer ci = cimax; while(ci--) { pipe_keys += [llGenerateKey()]; commands = alter(commands, [replace(gets(commands, ci), NAK, "|")], ci, ci); } key new_handle = getk(pipe_keys, cimax - 1); key feed_handle = getk(pipe_keys, 1); pipe_keys += [outs]; if(in_script) script_handle = new_handle; ci = cimax; while(ci--) // patch last two pipes so we can resolve them properly: if(ci > 0) commands = alter(commands, [ "p:" + gets(pipe_keys, ci) + " n:" + gets(pipe_keys, ci + 1) + " " + gets(commands, ci) ], ci, ci); //if(count(commands) > 2) { //commands = alter(commands, ["n:" + (string)new_handle + " " + gets(commands, -2)], -2, -2); // commands = alter(commands, ["p:" + (string)feed_handle + " " + gets(commands, 1)], 1, 1); if(cimax == 2) feed_handle = new_handle; pipe_queue = setjs(pipe_queue, [new_handle], jsarray([ jsarray(commands), outs, handle, user, _resolved, feed_handle, jsarray(pipe_keys), in_script ])); #ifdef DEBUG echo("pipes formed to: " + pipe_queue); #endif pipe_open(delitem(commands, 0)); return TRUE; } else if(~strpos(cml, NAK)) { cml = replace(cml, NAK, "|"); cargv = splitnulls(cml, " "); prog = gets(cargv, 0); } string filetype; integer invtype; string error_msg; if(prog == "runaway") { if(llGetInventoryType("_security") == INVENTORY_SCRIPT) { invoke("_security runaway", outs, handle, user); } else { error_msg = "Can't run away: _security is not installed (yikes)"; } } else if(prog == "safeword") { if(llGetInventoryType("restraint") == INVENTORY_SCRIPT) { invoke("restraint safeword", outs, handle, user); } else { error_msg = "Can't safeword: restraint is not installed"; } } else if(prog == "alias") { string word = gets(cargv, 1); string command = concat(delrange(cargv, 0, 1), " "); if(word == "delete" && command != "") { string alias = getdbl("input", ["alias", command]); if(alias != JSON_INVALID) setdbl("input", ["alias", command], JSON_DELETE); error_msg = "OK."; } else if(command != "") { setdbl("input", ["alias", word], command); } else if(word != "") { error_msg = getdbl("input", ["alias", word]); } else { list aliases = js2list(getdbl("input", ["alias"])); integer ai = count(aliases) >> 1; if(!ai) error_msg = "No aliases defined."; else while(ai--) { error_msg += "\n - " + gets(aliases, ai << 1) + ": " + gets(aliases, (ai << 1) + 1); } } #ifdef TRACE } else if(prog == "trace") { if(count(cargv) > 1) trace = llListFindList(["off", "on"], [gets(cargv, 1)]); else error_msg = "Trace is " + gets(["off.", "on."], trace); if(!~trace) trace = 0; #endif } else if(prog == "to") { string statement = concat(delrange(cargv, 0, 1), " "); outs = gets(cargv, 1); cml = statement; jump internal_restart; // process_input(gets(cargv, 1), user, statement, in_script); } else if(prog == "from") { handle = gets(cargv, 1); if(in_script) script_handle = handle; invoke(concat(delrange(cargv, 0, 1), " "), outs, handle, user); } else if(prog == "do") { setdbl("env", ["arg"], jsarray(delrange(cargv, 0, 0))); setdbl("env", ["args"], (string)(count(cargv) - 2)); file_offset = file_length = NOWHERE; file_outs = outs; file_user = user; file_open(file_pipe = llGenerateKey(), file_name = gets(cargv, 1)); // task_begin(file_pipe, file_name); // not checking in_script; allow manual interruption if(script_lines != []) { llSetTimerEvent(0); script_lines = []; script_li = 0; script_handle = NULL_KEY; set_mode(_mode & ~MODE_ACCEPT_DONE); // resolve_io(script_trigger_resolve, script_trigger_outs, script_trigger_ins); resolve_i(script_trigger_resolve, script_trigger_ins); echo("[_exec] interrupted by new script"); } script_trigger_ins = handle; script_trigger_outs = outs; script_trigger_resolve = _resolved; return TRUE; // do not continue_script(); } else if(prog == "exit") { // not checking in_script; allow manual interruption if(script_lines != []) { script_lines = []; script_li = 0; script_handle = NULL_KEY; set_mode(_mode & ~MODE_ACCEPT_DONE); // resolve_io(script_trigger_resolve, script_trigger_outs, script_trigger_ins); resolve_i(script_trigger_resolve, script_trigger_ins); llSetTimerEvent(0); } setdbl("env", ["arg"], "[]"); setdbl("env", ["args"], "0"); return FALSE; // do not continue_script(); } else if(prog == "say") { /*cml = concat(delitem(cargv, 0), " "); say_restart = TRUE; jump internal_restart;*/ if((user == avatar) && !(integer)getdbl("input", ["mind"]) && !in_script) { print(outs, user, "You cannot think."); } else { invoke("input " + cml, outs, handle, user); } return FALSE; // process_input(outs, user, concat(delitem(cargv, 0), " "), in_script); } else if(prog == "jump") { integer new_script_li = index(script_lines, "@" + gets(cargv, 1) + ":"); if(~new_script_li) { script_li = new_script_li + 1; } else { error_msg = "[_exec] script error: no label " + gets(cargv, 1); } } else if(prog == "echo") { string a1 = gets(cargv, 1); if(a1 == "-c") { echo(replace(concat(delrange(cargv, 0, 1), " "), "\\n", "\n")); } else if(a1 == "-d") { llWhisper(DEBUG_CHANNEL, replace(concat(delrange(cargv, 0, 1), " "), "\\n", "\n")); } else { print(outs, user, replace(concat(delitem(cargv, 0), " "), "\\n", "\n")); } } else if(prog == "set") { if(count(cargv) > 2) { string value; if(gets(cargv, 2) == "=") value = expression(delrange(cargv, 0, 2)); else value = concat(delrange(cargv, 0, 1), " "); string sv = gets(cargv, 2); if(value == "%key") { value = llGenerateKey(); } else if(value == "%undefined") { value = JSON_DELETE; } else if(value == "%empty") { value = ""; } else if(sv == "%keys") { value = concat(delrange(cargv, 0, 2), " "); value = concat(jskeys(value), " "); } else if(sv == "%count") { string mode = gets(cargv, 3); value = concat(delrange(cargv, 0, 3), " "); if(mode == "words") { value = (string)count(llParseString2List(value, [" ", "\n"], [])); } else if(mode == "lines") { value = (string)count(splitnulls(value, "\n")); } else if(mode == "chars") { value = (string)strlen(value); } else if(mode == "keys") { value = (string)count(jskeys(value)); } } else if((gets(cargv, 4) == "of") && (sv == "%index")) { // set %index $a of $json // set %index $a.$b of $json list indices = split(gets(cargv, 3), "."); // echo("getting indices " + concat(indices, ", ")); value = concat(delrange(cargv, 0, 4), " "); value = getjs(value, indices); } else if((gets(cargv, 4) == "in") && (sv == "%word" || sv == "%line" || sv == "%char")) { // set %word $i in $text integer i = (integer)gets(cargv, 3); value = concat(delrange(cargv, 0, 4), " "); // echo("command: " + cml); // echo("extracting " + sv + " #" + (string)i + " from " + value); if(sv == "%word") { value = gets(llParseString2List(value, [" ", "\n"], []), i); } else if(sv == "%line") { value = gets(splitnulls(value, "\n"), i); } else if(sv == "%char") { value = substr(value, i, i); } // echo("got " + value); } setdb("env", gets(cargv, 1), value); } else { error_msg = "[_exec] not enough arguments: set"; } } else if(prog == "if") { integer negate = 0; if(gets(cargv, 1) == "not") { cargv = delitem(cargv, 1); negate = 1; } integer endpoint = index(cargv, "then"); if(~endpoint) { string statement = concat(delrange(cargv, 0, endpoint), " "); integer success = 0; if(gets(cargv, 1) == "exists") { string filename = concat(sublist(cargv, 2, endpoint - 1), " "); success = llGetInventoryType(filename) != INVENTORY_NONE; } else { integer mid_pos = index(cargv, "is"); if(mid_pos > 1 && mid_pos < endpoint) { string LHS = concat(sublist(cargv, 1, mid_pos - 1), " "); string RHS = concat(sublist(cargv, mid_pos + 1, endpoint - 1), " "); // echo("cargv: " + concat(cargv, " ") + "\nLHS: " + LHS + "\nRHS: " + RHS); if(LHS == "%undefined") LHS = JSON_INVALID; else if(LHS == "%empty" || LHS == "\"\"") LHS = ""; if(RHS == "%undefined") RHS = JSON_INVALID; else if(RHS == "%empty" || RHS == "\"\"") RHS = ""; success = (LHS == RHS); // echo("success? " + (string)success); } else { mid_pos = index(cargv, "in"); if(mid_pos > 1 && mid_pos < endpoint) { string LHS = concat(sublist(cargv, 1, mid_pos - 1), " "); string RHS = concat(sublist(cargv, mid_pos + 1, endpoint - 1), " "); success = (getjs(RHS, [LHS]) != JSON_INVALID); } else { string value = expression(sublist(cargv, 1, endpoint - 1)); success = (llFabs((float)value) >= 0.001); } } } if(negate) success = !success; if(success) { cml = statement; jump internal_restart; /*process_input(outs, user, statement, in_script); return; // do not continue_script(); - the true branch will handle that */ } } else { error_msg = "[_exec] script error: incomplete statement " + cml; } } else if(prog == "service") { system(SIGNAL_CALL, E_UNKNOWN + E_PROGRAM_NUMBER + (string)outs + " " + (string)user + " " + concat(delitem(cargv, 0), " ")); } else if(~index(js2list(getdbl("input", ["device-command"])), prog)) { system(SIGNAL_CALL, E_HARDWARE + E_PROGRAM_NUMBER + (string)outs + " " + (string)user + " hardware command " + cml); } else if((invtype = llGetInventoryType("_" + prog)) == INVENTORY_SCRIPT) { invoke("_" + cml, outs, handle, user); return FALSE; } else if((invtype = llGetInventoryType(prog)) == INVENTORY_SCRIPT) { invoke(cml, outs, handle, user); return FALSE; } else { integer exists = (invtype != INVENTORY_NONE); if(!exists) { exists = ((filetype = getdbl("fs:root", [prog])) != JSON_INVALID); } if(exists) { string ext = gets(splitnulls(prog, "."), LAST); string assoc = getdbl("fs", ["open", ext]); if(~strpos(prog, ".")) { if(assoc != JSON_INVALID) { invoke(assoc + " " + cml, outs, handle, user); return FALSE; } else { error_msg = "No association for file type: ." + ext; } } } else { error_msg = prog + ": not found"; } } if(error_msg) print(outs, user, error_msg); // reduces interference from user acting during script execution if(in_script) { llSetTimerEvent(0.1); // continue_script() return TRUE; } else return FALSE; } integer script_trigger_resolve; key script_trigger_ins = NULL_KEY; key script_trigger_outs = NULL_KEY; continue_script() { integer script_length = count(script_lines); if(script_li < script_length) { string line; integer fc; while(script_li < script_length && (line == "" || fc == 0x40 || fc == 0x23)) { // skip blank lines and lines starting with '@' or '#' // echo((string)script_li + "/" + (string)script_length + " skipped"); line = llStringTrim(gets(script_lines, script_li++), STRING_TRIM); fc = llOrd(line, 0); } // echo((string)script_li + "/" + (string)script_length + ": " + line); if(script_li <= script_length) { process_input(file_outs, NULL_KEY, file_user, line, TRUE); return; } } #ifdef DEBUG echo("[_exec] script done; resolving " + (string)script_trigger_ins); #endif setdbl("env", ["arg"], "[]"); setdbl("env", ["args"], "0"); script_lines = []; script_li = 0; script_handle = NULL_KEY; llSetTimerEvent(0); set_mode(_mode & ~MODE_ACCEPT_DONE); // resolve_io(script_trigger_resolve, script_trigger_outs, script_trigger_ins); resolve_i(script_trigger_resolve, script_trigger_ins); script_trigger_resolve = 0; } main(integer src, integer n, string m, key outs, key ins, key user) { if(n == SIGNAL_DONE) { // echo("[_exec] receipt: " + m); ins = substr(m, 0, 35); string pipestruct = getjs(pipe_queue, [ins]); if(pipestruct != JSON_INVALID) { echo("[_exec] finished pipe procedure " + (string)ins); pipe_queue = setjs(pipe_queue, [ins], JSON_DELETE); } if(_mode & MODE_ACCEPT_DONE && ins == script_handle) { continue_script(); } else { echo("[_exec] script '" + file_name + "' still running; use 'exit' to abort"); // echo("(got " + (string)ins + " instead of " + (string)script_handle); } } else if(n == SIGNAL_INVOKE) { list argv = splitnulls(llStringTrim(m, STRING_TRIM), " "); integer argc = count(argv); integer still_awake; // started a script if(argc > 1) { key handle = ins; if(handle == NULL_KEY) handle = llGenerateKey(); still_awake = process_input(outs, handle, user, llStringTrim(concat(delitem(argv, 0), " "), STRING_TRIM), FALSE); /* string action = gets(argv, 1); if(action == "reset") { main(0, SIGNAL_INIT, "", "", "", ""); resolve_io(src, outs, ins); } else if(action == "do") { process_input(outs, NULL_KEY, user, "@do " + concat(delrange(argv, 0, 1), " "), FALSE, FALSE); script_trigger_resolve = src; script_trigger_outs = ins; script_trigger_ins = ins; } else if(action == "say") { process_input(outs, ins, user, concat(delrange(argv, 0, 1), " "), FALSE, FALSE); } else { print(outs, user, "usage: " + gets(argv, 0) + " reset|do |say "); resolve_io(src, outs, ins); }*/ } // must include this here since exec never sleeps: if(!still_awake) resolve_i(src, ins); } else if(n == SIGNAL_NOTIFY) { // echo("EXEC notify: " + m); list argv = splitnulls(m, " "); string action = gets(argv, 1); if(action == "pipe") { #ifdef DEBUG echo("exec: pipe created (" + (string)ins + "): " + m); #endif string pipestruct = getjs(pipe_queue, [ins]); if(pipestruct != JSON_INVALID) { #ifdef DEBUG echo("exec: pipe struct " + pipestruct); #endif key feed_handle = getjs(pipestruct, [5]); string first_command = getjs(pipestruct, [0, 0]); key user = getjs(pipestruct, [3]); // invoke(first_command, feed_handle, NULL_KEY, user); // process_input(key outs, key handle, key user, string cml, integer in_script) { process_input(feed_handle, llGenerateKey(), user, first_command, (integer)getjs(pipestruct, [7])); } else { echo("exec: unknown pipe (struct not found): " + m); } } else if(action == "file") { // file data available if(ins == file_pipe) { string file_buffer; pipe_read(file_pipe, file_buffer); integer read_length = 10; if(file_length != NOWHERE) { if(file_unit == "b") read_length *= FILE_PAGE_LENGTH; file_offset += read_length; if(file_offset + read_length > file_length) read_length = file_length - file_offset; // print(file_outs, file_user, file_buffer); script += file_buffer; if(file_offset < file_length) file_read(file_pipe, file_name, (string)file_offset + " " + (string)read_length); else { file_close(file_pipe); // print(avatar, avatar, script); set_mode(MODE_MASK_BATCH); // start run script: script_lines = split(script, "\n"); script = ""; script_li = 0; continue_script(); // task_end(file_pipe); } } else { list bfs = splitnulls(file_buffer, " "); file_length = (integer)gets(bfs, 0); file_unit = gets(bfs, 1); if(file_unit == "b") read_length *= FILE_PAGE_LENGTH; // echo("File " + file_name + " stat: " + (string)file_buffer); if(file_length > 0) { file_offset = 0; if(file_offset + read_length > file_length) read_length = file_length - file_offset; file_read(file_pipe, file_name, (string)file_offset + " " + (string)read_length); } else { // file not found or file empty print(file_user, file_user, "No file: " + file_name); file_close(file_pipe); // task_end(file_pipe); } } } /*else { echo("File data offered via unexpected pipe: " + (string)ins); }*/ } else { echo("Unknown notify: " + m); } resolve_i(src, ins); } else if(n == SIGNAL_INIT) { // NOP } else if(n == SIGNAL_UNKNOWN_SCRIPT) { echo("[" + PROGRAM_NAME + "] failed to run '" + m + "' (kernel could not find the program specified)"); } else { echo("[" + PROGRAM_NAME + "] unimplemented signal " + (string)n + ": " + m); } } #define EXT_EVENT_HANDLER "ARES/system/exec.event.lsl" #include