Merge pull request #3985 from Snarling/synchronize

NETSCRIPT: ns.scp and ns.write are now synchronous + fix exec race condition
This commit is contained in:
hydroflame
2022-08-23 12:25:24 -03:00
committed by GitHub
6 changed files with 142 additions and 222 deletions
+93 -144
View File
@@ -829,142 +829,106 @@ const base: InternalAPI<NS> = {
},
scp:
(ctx: NetscriptContext) =>
async (
_scriptname: unknown,
_destination: unknown,
_source: unknown = ctx.workerScript.hostname,
): Promise<boolean> => {
(_files: unknown, _destination: unknown, _source: unknown = ctx.workerScript.hostname): boolean => {
const destination = helpers.string(ctx, "destination", _destination);
const source = helpers.string(ctx, "source", _source);
if (Array.isArray(_scriptname)) {
// Recursively call scp on all elements of array
const scripts: string[] = _scriptname;
if (scripts.length === 0) {
throw helpers.makeRuntimeErrorMsg(ctx, "No scripts to copy");
}
let res = true;
await Promise.all(
scripts.map(async function (script) {
if (!(await NetscriptFunctions(ctx.workerScript).scp(script, destination, source))) {
res = false;
}
}),
);
return Promise.resolve(res);
}
const scriptName = helpers.string(ctx, "scriptName", _scriptname);
// Invalid file type
if (!isValidFilePath(scriptName)) {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: '${scriptName}'`);
}
// Invalid file name
if (!scriptName.endsWith(".lit") && !isScriptFilename(scriptName) && !scriptName.endsWith("txt")) {
throw helpers.makeRuntimeErrorMsg(ctx, "Only works for scripts, .lit and .txt files");
}
const destServer = helpers.getServer(ctx, destination);
const currServ = helpers.getServer(ctx, source);
const sourceServ = helpers.getServer(ctx, source);
const files = Array.isArray(_files) ? _files : [_files];
// Scp for lit files
if (scriptName.endsWith(".lit")) {
let found = false;
for (let i = 0; i < currServ.messages.length; ++i) {
if (currServ.messages[i] == scriptName) {
found = true;
break;
//First loop through filenames to find all errors before moving anything.
for (const file of files) {
// Not a string
if (typeof file !== "string")
throw helpers.makeRuntimeErrorMsg(ctx, "files should be a string or an array of strings.");
// Invalid file name
if (!isValidFilePath(file)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filename: '${file}'`);
// Invalid file type
if (!file.endsWith(".lit") && !isScriptFilename(file) && !file.endsWith("txt")) {
throw helpers.makeRuntimeErrorMsg(ctx, "Only works for scripts, .lit and .txt files.");
}
}
let noFailures = true;
//ts detects files as any[] here even though we would have thrown in the above loop if it wasn't string[]
for (const file of files as string[]) {
// Scp for lit files
if (file.endsWith(".lit")) {
const sourceMessage = sourceServ.messages.find((message) => message === file);
if (!sourceMessage) {
helpers.log(ctx, () => `File '${file}' does not exist.`);
noFailures = false;
continue;
}
}
if (!found) {
helpers.log(ctx, () => `File '${scriptName}' does not exist.`);
return Promise.resolve(false);
}
for (let i = 0; i < destServer.messages.length; ++i) {
if (destServer.messages[i] === scriptName) {
helpers.log(ctx, () => `File '${scriptName}' copied over to '${destServer?.hostname}'.`);
return Promise.resolve(true); // Already exists
const destMessage = destServer.messages.find((message) => message === file);
if (destMessage) {
helpers.log(ctx, () => `File '${file}' was already on '${destServer?.hostname}'.`);
continue;
}
}
destServer.messages.push(scriptName);
helpers.log(ctx, () => `File '${scriptName}' copied over to '${destServer?.hostname}'.`);
return Promise.resolve(true);
}
// Scp for text files
if (scriptName.endsWith(".txt")) {
let txtFile;
for (let i = 0; i < currServ.textFiles.length; ++i) {
if (currServ.textFiles[i].fn === scriptName) {
txtFile = currServ.textFiles[i];
break;
destServer.messages.push(file);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
continue;
}
// Scp for text files
if (file.endsWith(".txt")) {
const sourceTextFile = sourceServ.textFiles.find((textFile) => textFile.fn === file);
if (!sourceTextFile) {
helpers.log(ctx, () => `File '${file}' does not exist.`);
noFailures = false;
continue;
}
}
if (txtFile === undefined) {
helpers.log(ctx, () => `File '${scriptName}' does not exist.`);
return Promise.resolve(false);
}
for (let i = 0; i < destServer.textFiles.length; ++i) {
if (destServer.textFiles[i].fn === scriptName) {
// Overwrite
destServer.textFiles[i].text = txtFile.text;
helpers.log(ctx, () => `File '${scriptName}' copied over to '${destServer?.hostname}'.`);
return Promise.resolve(true);
const destTextFile = destServer.textFiles.find((textFile) => textFile.fn === file);
if (destTextFile) {
destTextFile.text = sourceTextFile.text;
helpers.log(ctx, () => `File '${file}' overwritten on '${destServer?.hostname}'.`);
continue;
}
const newFile = new TextFile(sourceTextFile.fn, sourceTextFile.text);
destServer.textFiles.push(newFile);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
continue;
}
const newFile = new TextFile(txtFile.fn, txtFile.text);
destServer.textFiles.push(newFile);
helpers.log(ctx, () => `File '${scriptName}' copied over to '${destServer?.hostname}'.`);
return Promise.resolve(true);
// Scp for script files
const sourceScript = sourceServ.scripts.find((script) => script.filename === file);
if (!sourceScript) {
helpers.log(ctx, () => `File '${file}' does not exist.`);
noFailures = false;
continue;
}
// Overwrite script if it already exists
const destScript = destServer.scripts.find((script) => script.filename === file);
if (destScript) {
if (destScript.code === sourceScript.code) {
helpers.log(ctx, () => `Identical file '${file}' was already on '${destServer?.hostname}'`);
continue;
}
destScript.code = sourceScript.code;
destScript.ramUsage = destScript.ramUsage;
destScript.markUpdated();
helpers.log(ctx, () => `WARNING: File '${file}' overwritten on '${destServer?.hostname}'`);
continue;
}
// Create new script if it does not already exist
const newScript = new Script(Player, file);
newScript.code = sourceScript.code;
newScript.ramUsage = sourceScript.ramUsage;
newScript.server = destServer.hostname;
destServer.scripts.push(newScript);
helpers.log(ctx, () => `File '${file}' copied over to '${destServer?.hostname}'.`);
newScript.updateRamUsage(Player, destServer.scripts);
}
// Scp for script files
let sourceScript = null;
for (let i = 0; i < currServ.scripts.length; ++i) {
if (scriptName == currServ.scripts[i].filename) {
sourceScript = currServ.scripts[i];
break;
}
}
if (sourceScript == null) {
helpers.log(ctx, () => `File '${scriptName}' does not exist.`);
return Promise.resolve(false);
}
// Overwrite script if it already exists
for (let i = 0; i < destServer.scripts.length; ++i) {
if (scriptName == destServer.scripts[i].filename) {
helpers.log(ctx, () => `WARNING: File '${scriptName}' overwritten on '${destServer?.hostname}'`);
const oldScript = destServer.scripts[i];
// If it's the exact same file don't actually perform the
// copy to avoid recompiling uselessly. Players tend to scp
// liberally.
if (oldScript.code === sourceScript.code) return Promise.resolve(true);
oldScript.code = sourceScript.code;
oldScript.ramUsage = sourceScript.ramUsage;
oldScript.markUpdated();
return Promise.resolve(true);
}
}
// Create new script if it does not already exist
const newScript = new Script(Player, scriptName);
newScript.code = sourceScript.code;
newScript.ramUsage = sourceScript.ramUsage;
newScript.server = destServer.hostname;
destServer.scripts.push(newScript);
helpers.log(ctx, () => `File '${scriptName}' copied over to '${destServer?.hostname}'.`);
return new Promise((resolve) => {
if (destServer === null) {
resolve(false);
return;
}
newScript.updateRamUsage(Player, destServer.scripts).then(() => resolve(true));
});
return noFailures;
},
ls:
(ctx: NetscriptContext) =>
@@ -1501,34 +1465,19 @@ const base: InternalAPI<NS> = {
},
write:
(ctx: NetscriptContext) =>
(_port: unknown, data: unknown = "", _mode: unknown = "a"): Promise<void> => {
(_port: unknown, _data: unknown = "", _mode: unknown = "a"): void => {
const port = helpers.string(ctx, "port", _port);
const data = helpers.string(ctx, "data", _data);
const mode = helpers.string(ctx, "mode", _mode);
if (isString(port)) {
// Write to script or text file
let fn = port;
if (!isValidFilePath(fn)) {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filepath: ${fn}`);
}
if (!isValidFilePath(fn)) throw helpers.makeRuntimeErrorMsg(ctx, `Invalid filepath: ${fn}`);
if (fn.lastIndexOf("/") === 0) {
fn = removeLeadingSlash(fn);
}
if (fn.lastIndexOf("/") === 0) fn = removeLeadingSlash(fn);
// Coerce 'data' to be a string
try {
data = String(data);
} catch (e: unknown) {
throw helpers.makeRuntimeErrorMsg(
ctx,
`Invalid data (${String(e)}). Data being written must be convertible to a string`,
);
}
const server = helpers.getServer(ctx, ctx.workerScript.hostname);
const server = ctx.workerScript.getServer();
if (server == null) {
throw helpers.makeRuntimeErrorMsg(ctx, "Error getting Server. This is a bug. Report to dev.");
}
if (isScriptFilename(fn)) {
// Write to script
let script = ctx.workerScript.getScriptOnServer(fn, server);
@@ -1545,7 +1494,7 @@ const base: InternalAPI<NS> = {
const txtFile = getTextFile(fn, server);
if (txtFile == null) {
createTextFile(fn, String(data), server);
return Promise.resolve();
return;
}
if (mode === "w") {
txtFile.write(String(data));
@@ -1553,7 +1502,7 @@ const base: InternalAPI<NS> = {
txtFile.append(String(data));
}
}
return Promise.resolve();
return;
} else {
throw helpers.makeRuntimeErrorMsg(ctx, `Invalid argument: ${port}`);
}