From 6ee553ffcee25914eaf17e620d831b1e3fb8cef3 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Thu, 29 May 2025 16:02:53 -0400 Subject: [PATCH 01/31] Add files via upload --- quickjs-libc.c | 483 +++++++++++++++++++++++++++++++++++++++++++++++-- test_std.js | 387 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 857 insertions(+), 13 deletions(-) create mode 100644 test_std.js diff --git a/quickjs-libc.c b/quickjs-libc.c index d08ff7f74..06a851d52 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -1034,6 +1034,14 @@ typedef struct { bool is_popen; } JSSTDFile; +#if defined(__MINGW32__) || defined(__MINGW64__) +typedef struct { + FILE *f; + int is_kind; + char filename[64]; +} JSTMPFile; +#endif + static bool is_stdio(FILE *f) { return f == stdin || f == stdout || f == stderr; @@ -1044,7 +1052,15 @@ static void js_std_file_finalizer(JSRuntime *rt, JSValueConst val) JSThreadState *ts = js_get_thread_state(rt); JSSTDFile *s = JS_GetOpaque(val, ts->std_file_class_id); if (s) { +#if defined(__MINGW32__) || defined(__MINGW64__) + JSTMPFile *ss = (JSTMPFile*) s; + if (ss->is_kind==2) { + if (ss->f) fclose(ss->f); + if (ss->filename[0] != 0) remove(ss->filename); + } else if (s->f && !is_stdio(s->f)) { +#else if (s->f && !is_stdio(s->f)) { +#endif #if !defined(__wasi__) if (s->is_popen) pclose(s->f); @@ -1207,6 +1223,62 @@ static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, } #if !defined(__wasi__) +#if defined(__MINGW32__) || defined(__MINGW64__) +// c:/tmp no longer works in windows. c:/ doesn't work. permissions! +static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSRuntime *rt = JS_GetRuntime(ctx); + JSThreadState *ts = js_get_thread_state(rt); + JSTMPFile *s; + JSValue obj; + obj = JS_NewObjectClass(ctx, ts->std_file_class_id); + if (JS_IsException(obj)) + return obj; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + + char * env = getenv("TMP"); + if (!env) env = getenv("TEMP"); + int i = 0; + if (env) { + while (env[i]) { + s->filename[i] = env[i]; + i++; + if (i > 50) return JS_NULL; + }; + }; + char* fname = &s->filename[i]; + char* templ = "\\qXXXXXXX"; + while (templ[0]) { + fname[0] = templ[0]; + fname++; templ++; + }; + fname[0] = 0; + int mkf = mkstemp(s->filename); + if (mkf == -1) { + JS_FreeValue(ctx, obj); + js_free(ctx, s); + return JS_NULL; + }; + int fd = dup(mkf); + s->f = fdopen( fd, "a+"); + close(mkf); + if (argc >= 1) js_set_error_object(ctx, argv[0], s->f ? 0 : errno); + if (!s->f) { + JS_FreeValue(ctx, obj); + js_free(ctx, s); + return JS_NULL; + }; + + s->is_kind = 2; + JS_SetOpaque(obj, s); + return obj; +} +#else static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -1218,7 +1290,8 @@ static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, return JS_NULL; return js_new_std_file(ctx, f, false); } -#endif +#endif // _WIN32 +#endif // !defined(__wasi__) static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3057,7 +3130,20 @@ static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, } #endif -#if !defined(_WIN32) && !defined(__wasi__) +/* start of mods - windows versions +js_os_symlink - now includes _WIN32 +js_os_pipe - now includes _WIN32 (fd version) +js_os_exec - now includes _WIN32 + -build_ms_envp - _WIN32 only ( one large double-nulled string, no array ) +js_watchpid - linux and _WIN32, hybrid function to do the wait, no status +js_os_kill - now includes _WIN32 (just a kill on pid, no signals) +js_std_tmpfile new _WIN32 version. permissions fail these days. + -std file finalizer - added remove tmpfile on delete + +verified with test_std.js modifications. +//#if !defined(_WIN32) && !defined(__wasi__) +*/ +#if !defined(__wasi__) static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3072,12 +3158,92 @@ static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, JS_FreeCString(ctx, target); return JS_EXCEPTION; } +#ifdef _WIN32 + int isdir = 0; // might need to pass a value in for folders. + if (argc >=2) { + if (JS_ToInt32(ctx, &isdir, argv[2])) return JS_EXCEPTION; + }; + err = CreateSymbolicLinkA(linkpath, target, ( isdir | 2 ) ) ; + if (!err) err = GetLastError(); + else err = 0; +#else err = js_get_errno(symlink(target, linkpath)); +#endif JS_FreeCString(ctx, target); JS_FreeCString(ctx, linkpath); return JS_NewInt32(ctx, err); } +#endif + +#if defined(_WIN32) +#define MS_ENVP_BUFFSIZE 8192 +static char *build_ms_envp(JSContext *ctx, JSValue obj) +{ + uint32_t len, i; + JSPropertyEnum *tab; + char *envp, *pair; + const char *key, *str; + JSValue val; + size_t key_len, str_len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) + return NULL; + envp = js_mallocz(ctx,MS_ENVP_BUFFSIZE); + pair = envp; + if (!envp) goto fail; + for(i = 0; i < len; i++) { + val = JS_GetProperty(ctx, obj, tab[i].atom); + if (JS_IsException(val)) + goto fail; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) goto fail; + key = JS_AtomToCString(ctx, tab[i].atom); + if (!key) { + JS_FreeCString(ctx, str); + goto fail; + } + key_len = strlen(key); + str_len = strlen(str); + /* parallel to build_envp for comparison + pair = js_malloc(ctx, key_len + str_len + 2); + if (!pair) { + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + goto fail; + }*/ + memcpy(pair, key, key_len); + pair += key_len; + pair[0] = '='; pair++; + memcpy(pair, str, str_len); + pair += str_len; + pair[0] = '\0'; + pair++; + //envp[i] = pair; + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + } + pair[0] = '\0'; + end: + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + return envp; + fail: + if (envp) { + //for(i = 0; i < len; i++) js_free(ctx, envp[i]); + js_free(ctx, envp); + envp = NULL; + } + goto end; + +}; + +#endif + +#if !defined(_WIN32) && !defined(__wasi__) /* return [path, errorcode] */ static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3230,19 +3396,32 @@ static void js_os_exec_once_init(void) #endif +#endif // !_WIN32 !__wasi__ + + +#if !defined(__wasi__) + /* exec(args[, options]) -> exitcode */ +#define OS_EXEC_CMD_BUFFSIZE 2048 static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst options, args = argv[0]; JSValue val, ret_val; - const char **exec_argv, *file = NULL, *str, *cwd = NULL; - char **envp = environ; + const char *file = NULL, *str, *cwd = NULL; uint32_t exec_argc, i; - int ret, pid, status; - bool block_flag = true, use_path = true; + int ret, pid; + bool block_flag = true, use_path = true, specified_fd = false; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; +#ifdef _WIN32 + char cmdbuff [OS_EXEC_CMD_BUFFSIZE]; + int cmdi; + char *envp = 0; +#else + char **envp = environ; + const char **exec_argv; + int status; uint32_t uid = -1, gid = -1; int ngroups = -1; gid_t groups[64]; @@ -3272,6 +3451,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, exec_argv[i] = str; } exec_argv[exec_argc] = NULL; +#endif for(i = 0; i < 3; i++) std_fds[i] = i; @@ -3317,9 +3497,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; std_fds[i] = fd; + specified_fd = true; } } +#ifndef _WIN32 val = JS_GetPropertyStr(ctx, options, "env"); if (JS_IsException(val)) goto exception; @@ -3329,7 +3511,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (!envp) goto exception; } - + val = JS_GetPropertyStr(ctx, options, "uid"); if (JS_IsException(val)) goto exception; @@ -3339,7 +3521,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; } - + val = JS_GetPropertyStr(ctx, options, "gid"); if (JS_IsException(val)) goto exception; @@ -3382,10 +3564,177 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (idx < len) goto exception; } + + } +#else + val = JS_GetPropertyStr(ctx, options, "env"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + envp = build_ms_envp(ctx, val); + JS_FreeValue(ctx, val); + if (!envp) + goto exception; + } + + + } + + cmdi = 0; int cmdsl; + if (cwd) { + cmdsl = strlen(cwd); + if (cmdsl) { + strncpy(&cmdbuff[cmdi], cwd, cmdsl); + cmdi += cmdsl; + }; + }; + if (file) { + cmdsl = strlen(file); + if (cmdsl) { + strncpy(&cmdbuff[cmdi], file, cmdsl); + cmdi += cmdsl; + cmdbuff[cmdi] = '"'; + cmdi++; + }; +#define WIN32_OS_EXEC_OPTION1 +#define WIN32_OS_EXEC_WATCHING +// still playing with cwd + file combinations +#ifdef WIN32_OS_EXEC_OPTION1 + } else { + strncpy(&cmdbuff[cmdi], "cmd.exe /c \"", 12); + cmdi += 12; +#endif + }; + val = JS_GetPropertyStr(ctx, args, "length"); + if (JS_IsException(val)) goto exception; + ret = JS_ToUint32(ctx, &exec_argc, val); + JS_FreeValue(ctx, val); + if (ret) goto exception; + /* arbitrary limit to avoid overflow */ + if (exec_argc < 1 || exec_argc > 65535) { + JS_ThrowTypeError(ctx, "invalid number of arguments"); + goto exception; } + for(i = 0; i < exec_argc; i++) { + val = JS_GetPropertyUint32(ctx, args, i); + if (JS_IsException(val)) goto exception; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) goto exception; + cmdsl = strlen(str); + if (cmdsl) { +#ifdef WIN32_OS_EXEC_OPTION1 + if ((file) || (i != 0)) { +#else + if (true) { +#endif + if (OS_EXEC_CMD_BUFFSIZE < (cmdi + cmdsl)) { + JS_ThrowRangeError(ctx, "exec command line too long."); + goto exception; + }; + cmdbuff[cmdi] = ' '; + cmdi++; + }; + strncpy(&cmdbuff[cmdi], str, cmdsl); + cmdi += cmdsl; + }; + + } + cmdbuff[cmdi] = '"'; + cmdi++; + cmdbuff[cmdi] = 0; +#ifdef WIN32_OS_EXEC_WORKING + printf("path to exec: \"%s\"\r\n", cmdbuff); +#endif +#endif //_WIN32 -#if !defined(EMSCRIPTEN) && !defined(__wasi__) +#ifdef _WIN32 + STARTUPINFO istart; + PROCESS_INFORMATION iproc; + // memset was absolutely necessary. I kept getting deeper system crashes + memset(&iproc, 0, sizeof(iproc)); + memset(&istart, 0, sizeof(istart) ); + istart.cb = sizeof(istart); + + if (specified_fd) { // this is also something worth tinkering with + istart.dwFlags = STARTF_USESTDHANDLES; + }; + + istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); + if (istart.hStdInput == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stdin of process"); + goto exception; + }; + istart.hStdOutput = (HANDLE) _get_osfhandle( std_fds[1] ); + if (istart.hStdOutput == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stdout of process"); + goto exception; + }; + istart.hStdError = (HANDLE) _get_osfhandle( std_fds[2] ); + if (istart.hStdError == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stderr of process"); + goto exception; + }; +/* + printf("using handles %" PRIx64 " from %d, %" PRIx64 " from %d, %" PRIx64 " from %d\r\n", + (int64_t) istart.hStdInput, std_fds[0], (int64_t) istart.hStdOutput, std_fds[1], + (int64_t) istart.hStdError, std_fds[2]); + +BOOL CreateProcessA( + [in, optional] LPCSTR lpApplicationName, + [in, out, optional] LPSTR lpCommandLine, + [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, + [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, + [in] BOOL bInheritHandles, + [in] DWORD dwCreationFlags, + [in, optional] LPVOID lpEnvironment, + [in, optional] LPCSTR lpCurrentDirectory, + [in] LPSTARTUPINFOA lpStartupInfo, + [out] LPPROCESS_INFORMATION lpProcessInformation +);*/ + + int cflags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; // prevents output on stdout + + // seems like inherithandles and environ crash the return value if not perfect! + // we may also need to sim a cmd.exe chdir and a manual push with a cwd .... + if (!CreateProcessA(0, cmdbuff, 0, 0, true, cflags, envp, 0, &istart, &iproc) ) + { + int e = GetLastError(); + JS_ThrowInternalError(ctx, "failed to start process with error: %d", e); + goto exception; + }; + + int32_t excode = 0; + pid = iproc.dwProcessId; + HANDLE phandle = iproc.hProcess; + HANDLE thandle = iproc.hThread; + int besafe = 0; + if (block_flag) { + for(;;) { + int dwait = WaitForSingleObject(phandle, INFINITE); + if (dwait == WAIT_OBJECT_0) { + GetExitCodeProcess (phandle, (DWORD*) &excode); + ret_val = JS_NewInt32(ctx, (int32_t) excode); + excode = GetLastError(); + break; + } else { + besafe++; if (besafe==20) { + JS_ThrowPlainError(ctx, "exec process did not complete."); + goto exception; + }; + }; + }; + CloseHandle(phandle); + CloseHandle(thandle); + } else { + excode = (int32_t) iproc.dwProcessId; + ret_val = JS_NewInt32(ctx, excode); //(int64_t) phandle); + } + +#else // _WIN32 + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) // should happen pre-fork because it calls dlsym() // and that's not an async-signal-safe function js_once(&js_os_exec_once, js_os_exec_once_init); @@ -3457,9 +3806,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, ret = pid; } ret_val = JS_NewInt32(ctx, ret); +#endif // !_WIN32 done: JS_FreeCString(ctx, file); JS_FreeCString(ctx, cwd); +#ifndef _WIN32 for(i = 0; i < exec_argc; i++) JS_FreeCString(ctx, exec_argv[i]); js_free(ctx, exec_argv); @@ -3473,10 +3824,115 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, js_free(ctx, envp); } return ret_val; +#else + if (envp) js_free(ctx, envp); + return ret_val; +#endif exception: ret_val = JS_EXCEPTION; goto done; } +#endif + +#ifdef _WIN32 +/* pipe() -> [read_fd, write_fd] or null if error */ +static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pipe_fds[2], ret; + JSValue obj; +#ifdef _WIN32 +#define PIPE_BUFFSIZE 4096 +#define WIN_PIPE_SPECIALFLAGS 0 +#define WIN_OS_PIPE_USE_FDS + HANDLE pipe_handles[2]; + struct _SECURITY_ATTRIBUTES secur; + secur.nLength = sizeof(secur); + secur.lpSecurityDescriptor = 0; + secur.bInheritHandle = true; + ret = CreatePipe(&pipe_handles[0], &pipe_handles[1], &secur, PIPE_BUFFSIZE); + if (ret == 0) return JS_NULL; +#ifdef WIN_OS_PIPE_USE_FDS + pipe_fds[0] = _open_osfhandle( (intptr_t) pipe_handles[0], WIN_PIPE_SPECIALFLAGS ); + if (pipe_fds[0] == -1 ) { + CloseHandle(pipe_handles[0]); + return JS_NULL; + }; + pipe_fds[1] = _open_osfhandle( (intptr_t) pipe_handles[1], WIN_PIPE_SPECIALFLAGS ); + if (pipe_fds[1] == -1 ) { + CloseHandle(pipe_handles[1]); + return JS_NULL; + }; +#else + pipe_fds[0] = pipe_handles[0]; + pipe_fds[1] = pipe_handles[1]; +#endif // WIN_OS_PIPE_USE_FDS +#else + ret = pipe(pipe_fds); + if (ret < 0) return JS_NULL; +#endif // _WIN32 + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), + JS_PROP_C_W_E); + return obj; +} + +/* kill(pid, sig) */ +static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid; + HANDLE ph; + DWORD flags = PROCESS_TERMINATE; + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + ph = OpenProcess(flags, false, (DWORD) pid); + if (!ph) return JS_NewInt32(ctx, GetLastError()); + if (TerminateProcess(ph, 0)) return JS_NULL; + int err = GetLastError(); + return JS_NewInt32(ctx,err); +} + +/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ +static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid; + HANDLE ph; + int block = 0, ret; + DWORD options = 0, flags = PROCESS_QUERY_INFORMATION | SYNCHRONIZE; + if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; + if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; + if (block==1) options = INFINITE; + ph = OpenProcess(flags, false, (DWORD) pid); + if (!ph) return JS_NewInt32(ctx, -GetLastError()); + ret = WaitForSingleObject((HANDLE) ph, options); + if (ret == WAIT_TIMEOUT) return JS_NewInt32(ctx, 0); // timed out + if (ret != 0) return JS_NewInt32(ctx, -GetLastError()); + return JS_NewInt32(ctx, pid); +} +#else +/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ +static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, status, block = 0, options = 0, ret; + if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; + if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; + if (!block) options = WNOHANG; + ret = waitpid(pid, &status, options); + if (ret == 0) return JS_NewInt32(ctx, 0); + if (ret==pid) return JS_NewInt32(ctx, pid); + return JS_NewInt32(ctx, -errno); +} +#endif + + +#if !defined(_WIN32) && !defined(__wasi__) /* getpid() -> pid */ static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, @@ -4113,17 +4569,18 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("sleep", 1, js_os_sleep ), #if !defined(__wasi__) JS_CFUNC_DEF("realpath", 1, js_os_realpath ), + JS_CFUNC_DEF("symlink", 3, js_os_symlink ), + JS_CFUNC_DEF("pipe", 0, js_os_pipe ), + JS_CFUNC_DEF("exec", 1, js_os_exec ), + JS_CFUNC_DEF("watchpid", 2, js_os_watchpid ), + JS_CFUNC_DEF("kill", 2, js_os_kill ), #endif #if !defined(_WIN32) && !defined(__wasi__) JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), - JS_CFUNC_DEF("symlink", 2, js_os_symlink ), JS_CFUNC_DEF("readlink", 1, js_os_readlink ), - JS_CFUNC_DEF("exec", 1, js_os_exec ), JS_CFUNC_DEF("getpid", 0, js_os_getpid ), JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), OS_FLAG(WNOHANG), - JS_CFUNC_DEF("pipe", 0, js_os_pipe ), - JS_CFUNC_DEF("kill", 2, js_os_kill ), JS_CFUNC_DEF("dup", 1, js_os_dup ), JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), #endif diff --git a/test_std.js b/test_std.js new file mode 100644 index 000000000..207f39ed6 --- /dev/null +++ b/test_std.js @@ -0,0 +1,387 @@ +import * as std from "qjs:std"; +import * as os from "qjs:os"; +import { assert } from "./assert.js"; + +const isWin = os.platform === 'win32'; +const isCygwin = os.platform === 'cygwin'; + + +function test_printf() +{ + assert(std.sprintf("a=%d s=%s", 123, "abc"), "a=123 s=abc"); + assert(std.sprintf("%010d", 123), "0000000123"); + assert(std.sprintf("%x", -2), "fffffffe"); + assert(std.sprintf("%lx", -2), "fffffffffffffffe"); + assert(std.sprintf("%10.1f", 2.1), " 2.1"); + assert(std.sprintf("%*.*f", 10, 2, -2.13), " -2.13"); + assert(std.sprintf("%#lx", 0x7fffffffffffffffn), "0x7fffffffffffffff"); +} + +function test_file1() +{ + var f, len, str, size, buf, ret, i, str1, ab; + + f = std.tmpfile(); + str = "hello world\n"; + f.puts(str); + + f.seek(0, std.SEEK_SET); + ab = f.readAsArrayBuffer(); + assert([...new Uint8Array(ab)], str.split("").map(c => c.charCodeAt(0))); + + f.seek(0, std.SEEK_SET); + str1 = f.readAsString(); + assert(str1, str); + + f.seek(0, std.SEEK_END); + size = f.tell(); + assert(size, str.length); + + f.seek(0, std.SEEK_SET); + + buf = new Uint8Array(size); + ret = f.read(buf.buffer, 0, size); + assert(ret, size); + for(i = 0; i < size; i++) + assert(buf[i], str.charCodeAt(i)); + + f.close(); +} + +function test_file2() +{ + var f, str, i, size; + f = std.tmpfile(); + str = "hello world\n"; + size = str.length; + for(i = 0; i < size; i++) + f.putByte(str.charCodeAt(i)); + f.seek(0, std.SEEK_SET); + for(i = 0; i < size; i++) { + assert(str.charCodeAt(i), f.getByte()); + } + assert(f.getByte(), -1); + f.close(); +} + +function test_getline() +{ + var f, line, line_count, lines, i; + + lines = ["hello world", "line 1", "line 2" ]; + f = std.tmpfile(); + for(i = 0; i < lines.length; i++) { + f.puts(lines[i], "\n"); + } + + f.seek(0, std.SEEK_SET); + assert(!f.eof()); + line_count = 0; + for(;;) { + line = f.getline(); + if (line === null) + break; + assert(line, lines[line_count]); + line_count++; + } + assert(f.eof()); + assert(line_count, lines.length); + + f.close(); +} + +function test_popen() +{ + var str, f, fname = "tmp_file.txt"; + var ta, content = "hello world"; + var cmd = isWin ? "type" : "cat"; + + ta = new Uint8Array([...content].map(c => c.charCodeAt(0))); + std.writeFile(fname, ta); + assert(std.loadFile(fname), content); + std.writeFile(fname, ta.buffer); + assert(std.loadFile(fname), content); + std.writeFile(fname, content); + assert(std.loadFile(fname), content); + + /* execute shell command */ + f = std.popen(cmd + " " + fname, "r"); + str = f.readAsString(); + f.close(); + + assert(str, content); + + os.remove(fname); +} + +function test_os() +{ + var fd, fpath, fname, fdir, buf, buf2, i, files, err, fdate, st, link_path; + + // XXX(bnoordhuis) disabled because stdio is not a tty on CI + //assert(os.isatty(0)); + + fdir = "test_tmp_dir"; + fname = "tmp_file.txt"; + fpath = fdir + "/" + fname; + link_path = fdir + "/test_link"; + + os.remove(link_path); + os.remove(fpath); + os.remove(fdir); + + err = os.mkdir(fdir, 0o755); + assert(err, 0); + + fd = os.open(fpath, os.O_RDWR | os.O_CREAT | os.O_TRUNC); + assert(fd >= 0); + + buf = new Uint8Array(10); + for(i = 0; i < buf.length; i++) + buf[i] = i; + assert(os.write(fd, buf.buffer, 0, buf.length), buf.length); + + assert(os.seek(fd, 0, std.SEEK_SET), 0); + buf2 = new Uint8Array(buf.length); + assert(os.read(fd, buf2.buffer, 0, buf2.length), buf2.length); + + for(i = 0; i < buf.length; i++) + assert(buf[i] == buf2[i]); + + if (typeof BigInt !== "undefined") { + assert(os.seek(fd, BigInt(6), std.SEEK_SET), BigInt(6)); + assert(os.read(fd, buf2.buffer, 0, 1), 1); + assert(buf[6] == buf2[0]); + } + + assert(os.close(fd), 0); + + [files, err] = os.readdir(fdir); + assert(err, 0); + assert(files.indexOf(fname) >= 0); + + fdate = 10000; + + err = os.utimes(fpath, fdate, fdate); + assert(err, 0); + + [st, err] = os.stat(fpath); + assert(err, 0); + assert(st.mode & os.S_IFMT, os.S_IFREG); + + + if (!isWin) { + // moved + assert(st.mtime, fdate); + + err = os.symlink(fname, link_path); + assert(err, 0); + + [st, err] = os.lstat(link_path); + assert(err, 0); + assert(st.mode & os.S_IFMT, os.S_IFLNK); + + [buf, err] = os.readlink(link_path); + assert(err, 0); + assert(buf, fname); + + assert(os.remove(link_path) === 0); + + } + + [buf, err] = os.getcwd(); + assert(err, 0); + + [buf2, err] = os.realpath("."); + assert(err, 0); + + assert(buf, buf2); + + assert(os.remove(fpath) === 0); + + fd = os.open(fpath, os.O_RDONLY); + assert(fd < 0); + + assert(os.remove(fdir) === 0); +} + +function test_os_pipe() { + + var fds, fr, fw, ab; + fds = os.pipe(); +// console.log("got: " + fds[0] + " " + fds[1]); + fw = std.fdopen(fds[1], "w"); + fr = std.fdopen(fds[0], "r"); + const tstr = "this is a test\r"; + fw.puts(tstr + "\n"); +// fw.puts("this is slow!\r\n"); + fw.puts("\r\n"); + fw.flush(); +// fw.close(); + var barray = new Uint8Array(64); +// ab = fr.read(barray.buffer, 0, 64); +// console.log(ab); +// console.log(fr.getline()); +// console.log(fr.getline()); + ab = fr.getline(); + assert(ab.length, tstr.length); + fw.close(); + fr.close(); +}; + +test_os_pipe(); + +function test_win_os_exec() { + var ret, fds, pid, f, status; + +/* functionality just isn't the same in win32! + in fact, in MSYS the command prompt behaves differently. + I did manage all of this with FDS and PIDS and not handles + hello/r is an artifact of the getline unix emulation + I made the executive decision so that this isn't useless: + watchpid and kill should be made compatible on all systems. + my os.kill is just a terminateprocess call. it returns early. + watchpid is too fast for the kill to complete in VC + NT + at least we can monitor status of a script and execute programs. +*/ + ret = os.exec(["smeegul desgpglam golum"]); + assert(ret, 1); + + ret = os.exec(["echo test 0"]); + assert(ret, 0); + + ret = os.exec(["cmd.exe", "/c", "exit 2"], { usePath: false }); + assert(ret, 2); + + fds = os.pipe(); + pid = os.exec(["echo %FOO%"], { + stdout: fds[1], + block: false, + env: { FOO: "hello" }, + } ); + assert(pid >= 0); + os.close(fds[1]); /* close the write end (as it is only in the child) */ + f = std.fdopen(fds[0], "r"); + var gl = f.getline(); + assert(gl, "hello\r"); + assert(f.getline(), null); + f.close(); + ret = os.watchpid(pid, 1); + assert(ret, pid); + pid = os.exec(["cat"], { block: false } ); + assert(pid >= 0); + os.kill(pid, os.SIGTERM); + os.sleep(1); + ret = os.watchpid(pid, 0); + assert(ret, pid); + // notes from before...... + // Flaky on cygwin for unclear reasons, see + // https://github.com/quickjs-ng/quickjs/issues/184 +}; + +function test_os_exec() +{ + var ret, fds, pid, f, status; + + ret = os.exec(["true"]); + assert(ret, 0); + + ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false }); + assert(ret, 1); + + fds = os.pipe(); + pid = os.exec(["sh", "-c", "echo $FOO"], { + stdout: fds[1], + block: false, + env: { FOO: "hello" }, + } ); + assert(pid >= 0); + os.close(fds[1]); /* close the write end (as it is only in the child) */ + f = std.fdopen(fds[0], "r"); + assert(f.getline(), "hello"); + assert(f.getline(), null); + f.close(); + /* + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + assert(status & 0x7f, 0); /* exited */ + //assert(status >> 8, 0); /* exit code */ + + // added to test watchpid (for win32 compatible code) + ret = os.watchpid(pid, 1); + assert(ret, pid); + + pid = os.exec(["cat"], { block: false } ); + ret = os.watchpid(pid, 0); + assert(ret, 0); + assert(pid >= 0); + os.kill(pid, os.SIGTERM); + //os.sleep(1); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + // Flaky on cygwin for unclear reasons, see + // https://github.com/quickjs-ng/quickjs/issues/184 + if (!isCygwin) { + assert(status & 0x7f, os.SIGTERM); + } +} + +function test_interval() +{ + var t = os.setInterval(f, 1); + function f() { + if (++f.count === 3) os.clearInterval(t); + } + f.count = 0; +} + +function test_timeout() +{ + var th, i; + + /* just test that a timer can be inserted and removed */ + th = []; + for(i = 0; i < 3; i++) + th[i] = os.setTimeout(function () { }, 1000); + for(i = 0; i < 3; i++) + os.clearTimeout(th[i]); +} + +function test_timeout_order() +{ + var s = ""; + os.setTimeout(a, 0); + os.setTimeout(b, 100); + os.setTimeout(d, 700); + function a() { s += "a"; os.setTimeout(c, 300); } + function b() { s += "b"; } + function c() { s += "c"; } + function d() { assert(s, "abc"); } // not "acb" +} + +function test_stdio_close() +{ + for (const f of [std.in, std.out, std.err]) { + let caught = false; + try { + f.close(); + } catch (e) { + assert(/cannot close stdio/.test(e.message)); + caught = true; + } + assert(caught); + } +} + +test_printf(); +test_file1(); +test_file2(); +test_getline(); +test_popen(); +test_os(); +if (!isWin) test_os_exec(); +else test_win_os_exec(); +test_interval(); +test_timeout(); +test_timeout_order(); +test_stdio_close(); From f323678262314526f3c617cd62feb1bce1f16eef Mon Sep 17 00:00:00 2001 From: woodtygr Date: Thu, 29 May 2025 16:10:23 -0400 Subject: [PATCH 02/31] Add files via upload --- quickjs-libc.c | 483 ++----------------------------------------------- 1 file changed, 13 insertions(+), 470 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 06a851d52..d08ff7f74 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -1034,14 +1034,6 @@ typedef struct { bool is_popen; } JSSTDFile; -#if defined(__MINGW32__) || defined(__MINGW64__) -typedef struct { - FILE *f; - int is_kind; - char filename[64]; -} JSTMPFile; -#endif - static bool is_stdio(FILE *f) { return f == stdin || f == stdout || f == stderr; @@ -1052,15 +1044,7 @@ static void js_std_file_finalizer(JSRuntime *rt, JSValueConst val) JSThreadState *ts = js_get_thread_state(rt); JSSTDFile *s = JS_GetOpaque(val, ts->std_file_class_id); if (s) { -#if defined(__MINGW32__) || defined(__MINGW64__) - JSTMPFile *ss = (JSTMPFile*) s; - if (ss->is_kind==2) { - if (ss->f) fclose(ss->f); - if (ss->filename[0] != 0) remove(ss->filename); - } else if (s->f && !is_stdio(s->f)) { -#else if (s->f && !is_stdio(s->f)) { -#endif #if !defined(__wasi__) if (s->is_popen) pclose(s->f); @@ -1223,62 +1207,6 @@ static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, } #if !defined(__wasi__) -#if defined(__MINGW32__) || defined(__MINGW64__) -// c:/tmp no longer works in windows. c:/ doesn't work. permissions! -static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = js_get_thread_state(rt); - JSTMPFile *s; - JSValue obj; - obj = JS_NewObjectClass(ctx, ts->std_file_class_id); - if (JS_IsException(obj)) - return obj; - s = js_mallocz(ctx, sizeof(*s)); - if (!s) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - - char * env = getenv("TMP"); - if (!env) env = getenv("TEMP"); - int i = 0; - if (env) { - while (env[i]) { - s->filename[i] = env[i]; - i++; - if (i > 50) return JS_NULL; - }; - }; - char* fname = &s->filename[i]; - char* templ = "\\qXXXXXXX"; - while (templ[0]) { - fname[0] = templ[0]; - fname++; templ++; - }; - fname[0] = 0; - int mkf = mkstemp(s->filename); - if (mkf == -1) { - JS_FreeValue(ctx, obj); - js_free(ctx, s); - return JS_NULL; - }; - int fd = dup(mkf); - s->f = fdopen( fd, "a+"); - close(mkf); - if (argc >= 1) js_set_error_object(ctx, argv[0], s->f ? 0 : errno); - if (!s->f) { - JS_FreeValue(ctx, obj); - js_free(ctx, s); - return JS_NULL; - }; - - s->is_kind = 2; - JS_SetOpaque(obj, s); - return obj; -} -#else static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -1290,8 +1218,7 @@ static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, return JS_NULL; return js_new_std_file(ctx, f, false); } -#endif // _WIN32 -#endif // !defined(__wasi__) +#endif static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3130,20 +3057,7 @@ static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, } #endif -/* start of mods - windows versions -js_os_symlink - now includes _WIN32 -js_os_pipe - now includes _WIN32 (fd version) -js_os_exec - now includes _WIN32 - -build_ms_envp - _WIN32 only ( one large double-nulled string, no array ) -js_watchpid - linux and _WIN32, hybrid function to do the wait, no status -js_os_kill - now includes _WIN32 (just a kill on pid, no signals) -js_std_tmpfile new _WIN32 version. permissions fail these days. - -std file finalizer - added remove tmpfile on delete - -verified with test_std.js modifications. -//#if !defined(_WIN32) && !defined(__wasi__) -*/ -#if !defined(__wasi__) +#if !defined(_WIN32) && !defined(__wasi__) static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3158,92 +3072,12 @@ static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, JS_FreeCString(ctx, target); return JS_EXCEPTION; } -#ifdef _WIN32 - int isdir = 0; // might need to pass a value in for folders. - if (argc >=2) { - if (JS_ToInt32(ctx, &isdir, argv[2])) return JS_EXCEPTION; - }; - err = CreateSymbolicLinkA(linkpath, target, ( isdir | 2 ) ) ; - if (!err) err = GetLastError(); - else err = 0; -#else err = js_get_errno(symlink(target, linkpath)); -#endif JS_FreeCString(ctx, target); JS_FreeCString(ctx, linkpath); return JS_NewInt32(ctx, err); } -#endif - -#if defined(_WIN32) -#define MS_ENVP_BUFFSIZE 8192 -static char *build_ms_envp(JSContext *ctx, JSValue obj) -{ - uint32_t len, i; - JSPropertyEnum *tab; - char *envp, *pair; - const char *key, *str; - JSValue val; - size_t key_len, str_len; - - if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) - return NULL; - envp = js_mallocz(ctx,MS_ENVP_BUFFSIZE); - pair = envp; - if (!envp) goto fail; - for(i = 0; i < len; i++) { - val = JS_GetProperty(ctx, obj, tab[i].atom); - if (JS_IsException(val)) - goto fail; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) goto fail; - key = JS_AtomToCString(ctx, tab[i].atom); - if (!key) { - JS_FreeCString(ctx, str); - goto fail; - } - key_len = strlen(key); - str_len = strlen(str); - /* parallel to build_envp for comparison - pair = js_malloc(ctx, key_len + str_len + 2); - if (!pair) { - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - goto fail; - }*/ - memcpy(pair, key, key_len); - pair += key_len; - pair[0] = '='; pair++; - memcpy(pair, str, str_len); - pair += str_len; - pair[0] = '\0'; - pair++; - //envp[i] = pair; - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - } - pair[0] = '\0'; - end: - for(i = 0; i < len; i++) - JS_FreeAtom(ctx, tab[i].atom); - js_free(ctx, tab); - return envp; - fail: - if (envp) { - //for(i = 0; i < len; i++) js_free(ctx, envp[i]); - js_free(ctx, envp); - envp = NULL; - } - goto end; - -}; - -#endif - -#if !defined(_WIN32) && !defined(__wasi__) /* return [path, errorcode] */ static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3396,32 +3230,19 @@ static void js_os_exec_once_init(void) #endif -#endif // !_WIN32 !__wasi__ - - -#if !defined(__wasi__) - /* exec(args[, options]) -> exitcode */ -#define OS_EXEC_CMD_BUFFSIZE 2048 static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst options, args = argv[0]; JSValue val, ret_val; - const char *file = NULL, *str, *cwd = NULL; + const char **exec_argv, *file = NULL, *str, *cwd = NULL; + char **envp = environ; uint32_t exec_argc, i; - int ret, pid; - bool block_flag = true, use_path = true, specified_fd = false; + int ret, pid, status; + bool block_flag = true, use_path = true; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; -#ifdef _WIN32 - char cmdbuff [OS_EXEC_CMD_BUFFSIZE]; - int cmdi; - char *envp = 0; -#else - char **envp = environ; - const char **exec_argv; - int status; uint32_t uid = -1, gid = -1; int ngroups = -1; gid_t groups[64]; @@ -3451,7 +3272,6 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, exec_argv[i] = str; } exec_argv[exec_argc] = NULL; -#endif for(i = 0; i < 3; i++) std_fds[i] = i; @@ -3497,11 +3317,9 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; std_fds[i] = fd; - specified_fd = true; } } -#ifndef _WIN32 val = JS_GetPropertyStr(ctx, options, "env"); if (JS_IsException(val)) goto exception; @@ -3511,7 +3329,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (!envp) goto exception; } - + val = JS_GetPropertyStr(ctx, options, "uid"); if (JS_IsException(val)) goto exception; @@ -3521,7 +3339,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; } - + val = JS_GetPropertyStr(ctx, options, "gid"); if (JS_IsException(val)) goto exception; @@ -3564,177 +3382,10 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (idx < len) goto exception; } - - } -#else - val = JS_GetPropertyStr(ctx, options, "env"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - envp = build_ms_envp(ctx, val); - JS_FreeValue(ctx, val); - if (!envp) - goto exception; - } - - - } - - cmdi = 0; int cmdsl; - if (cwd) { - cmdsl = strlen(cwd); - if (cmdsl) { - strncpy(&cmdbuff[cmdi], cwd, cmdsl); - cmdi += cmdsl; - }; - }; - if (file) { - cmdsl = strlen(file); - if (cmdsl) { - strncpy(&cmdbuff[cmdi], file, cmdsl); - cmdi += cmdsl; - cmdbuff[cmdi] = '"'; - cmdi++; - }; -#define WIN32_OS_EXEC_OPTION1 -#define WIN32_OS_EXEC_WATCHING -// still playing with cwd + file combinations -#ifdef WIN32_OS_EXEC_OPTION1 - } else { - strncpy(&cmdbuff[cmdi], "cmd.exe /c \"", 12); - cmdi += 12; -#endif - }; - val = JS_GetPropertyStr(ctx, args, "length"); - if (JS_IsException(val)) goto exception; - ret = JS_ToUint32(ctx, &exec_argc, val); - JS_FreeValue(ctx, val); - if (ret) goto exception; - /* arbitrary limit to avoid overflow */ - if (exec_argc < 1 || exec_argc > 65535) { - JS_ThrowTypeError(ctx, "invalid number of arguments"); - goto exception; } - for(i = 0; i < exec_argc; i++) { - val = JS_GetPropertyUint32(ctx, args, i); - if (JS_IsException(val)) goto exception; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) goto exception; - cmdsl = strlen(str); - if (cmdsl) { -#ifdef WIN32_OS_EXEC_OPTION1 - if ((file) || (i != 0)) { -#else - if (true) { -#endif - if (OS_EXEC_CMD_BUFFSIZE < (cmdi + cmdsl)) { - JS_ThrowRangeError(ctx, "exec command line too long."); - goto exception; - }; - cmdbuff[cmdi] = ' '; - cmdi++; - }; - strncpy(&cmdbuff[cmdi], str, cmdsl); - cmdi += cmdsl; - }; - - } - cmdbuff[cmdi] = '"'; - cmdi++; - cmdbuff[cmdi] = 0; -#ifdef WIN32_OS_EXEC_WORKING - printf("path to exec: \"%s\"\r\n", cmdbuff); -#endif -#endif //_WIN32 -#ifdef _WIN32 - STARTUPINFO istart; - PROCESS_INFORMATION iproc; - // memset was absolutely necessary. I kept getting deeper system crashes - memset(&iproc, 0, sizeof(iproc)); - memset(&istart, 0, sizeof(istart) ); - istart.cb = sizeof(istart); - - if (specified_fd) { // this is also something worth tinkering with - istart.dwFlags = STARTF_USESTDHANDLES; - }; - - istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); - if (istart.hStdInput == INVALID_HANDLE_VALUE) { - JS_ThrowInternalError(ctx, "failed to associate stdin of process"); - goto exception; - }; - istart.hStdOutput = (HANDLE) _get_osfhandle( std_fds[1] ); - if (istart.hStdOutput == INVALID_HANDLE_VALUE) { - JS_ThrowInternalError(ctx, "failed to associate stdout of process"); - goto exception; - }; - istart.hStdError = (HANDLE) _get_osfhandle( std_fds[2] ); - if (istart.hStdError == INVALID_HANDLE_VALUE) { - JS_ThrowInternalError(ctx, "failed to associate stderr of process"); - goto exception; - }; -/* - printf("using handles %" PRIx64 " from %d, %" PRIx64 " from %d, %" PRIx64 " from %d\r\n", - (int64_t) istart.hStdInput, std_fds[0], (int64_t) istart.hStdOutput, std_fds[1], - (int64_t) istart.hStdError, std_fds[2]); - -BOOL CreateProcessA( - [in, optional] LPCSTR lpApplicationName, - [in, out, optional] LPSTR lpCommandLine, - [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, - [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, - [in] BOOL bInheritHandles, - [in] DWORD dwCreationFlags, - [in, optional] LPVOID lpEnvironment, - [in, optional] LPCSTR lpCurrentDirectory, - [in] LPSTARTUPINFOA lpStartupInfo, - [out] LPPROCESS_INFORMATION lpProcessInformation -);*/ - - int cflags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; // prevents output on stdout - - // seems like inherithandles and environ crash the return value if not perfect! - // we may also need to sim a cmd.exe chdir and a manual push with a cwd .... - if (!CreateProcessA(0, cmdbuff, 0, 0, true, cflags, envp, 0, &istart, &iproc) ) - { - int e = GetLastError(); - JS_ThrowInternalError(ctx, "failed to start process with error: %d", e); - goto exception; - }; - - int32_t excode = 0; - pid = iproc.dwProcessId; - HANDLE phandle = iproc.hProcess; - HANDLE thandle = iproc.hThread; - int besafe = 0; - if (block_flag) { - for(;;) { - int dwait = WaitForSingleObject(phandle, INFINITE); - if (dwait == WAIT_OBJECT_0) { - GetExitCodeProcess (phandle, (DWORD*) &excode); - ret_val = JS_NewInt32(ctx, (int32_t) excode); - excode = GetLastError(); - break; - } else { - besafe++; if (besafe==20) { - JS_ThrowPlainError(ctx, "exec process did not complete."); - goto exception; - }; - }; - }; - CloseHandle(phandle); - CloseHandle(thandle); - } else { - excode = (int32_t) iproc.dwProcessId; - ret_val = JS_NewInt32(ctx, excode); //(int64_t) phandle); - } - -#else // _WIN32 - -#if !defined(EMSCRIPTEN) && !defined(__wasi__) +#if !defined(EMSCRIPTEN) && !defined(__wasi__) // should happen pre-fork because it calls dlsym() // and that's not an async-signal-safe function js_once(&js_os_exec_once, js_os_exec_once_init); @@ -3806,11 +3457,9 @@ BOOL CreateProcessA( ret = pid; } ret_val = JS_NewInt32(ctx, ret); -#endif // !_WIN32 done: JS_FreeCString(ctx, file); JS_FreeCString(ctx, cwd); -#ifndef _WIN32 for(i = 0; i < exec_argc; i++) JS_FreeCString(ctx, exec_argv[i]); js_free(ctx, exec_argv); @@ -3824,115 +3473,10 @@ BOOL CreateProcessA( js_free(ctx, envp); } return ret_val; -#else - if (envp) js_free(ctx, envp); - return ret_val; -#endif exception: ret_val = JS_EXCEPTION; goto done; } -#endif - -#ifdef _WIN32 -/* pipe() -> [read_fd, write_fd] or null if error */ -static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pipe_fds[2], ret; - JSValue obj; -#ifdef _WIN32 -#define PIPE_BUFFSIZE 4096 -#define WIN_PIPE_SPECIALFLAGS 0 -#define WIN_OS_PIPE_USE_FDS - HANDLE pipe_handles[2]; - struct _SECURITY_ATTRIBUTES secur; - secur.nLength = sizeof(secur); - secur.lpSecurityDescriptor = 0; - secur.bInheritHandle = true; - ret = CreatePipe(&pipe_handles[0], &pipe_handles[1], &secur, PIPE_BUFFSIZE); - if (ret == 0) return JS_NULL; -#ifdef WIN_OS_PIPE_USE_FDS - pipe_fds[0] = _open_osfhandle( (intptr_t) pipe_handles[0], WIN_PIPE_SPECIALFLAGS ); - if (pipe_fds[0] == -1 ) { - CloseHandle(pipe_handles[0]); - return JS_NULL; - }; - pipe_fds[1] = _open_osfhandle( (intptr_t) pipe_handles[1], WIN_PIPE_SPECIALFLAGS ); - if (pipe_fds[1] == -1 ) { - CloseHandle(pipe_handles[1]); - return JS_NULL; - }; -#else - pipe_fds[0] = pipe_handles[0]; - pipe_fds[1] = pipe_handles[1]; -#endif // WIN_OS_PIPE_USE_FDS -#else - ret = pipe(pipe_fds); - if (ret < 0) return JS_NULL; -#endif // _WIN32 - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), - JS_PROP_C_W_E); - return obj; -} - -/* kill(pid, sig) */ -static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pid; - HANDLE ph; - DWORD flags = PROCESS_TERMINATE; - if (JS_ToInt32(ctx, &pid, argv[0])) - return JS_EXCEPTION; - ph = OpenProcess(flags, false, (DWORD) pid); - if (!ph) return JS_NewInt32(ctx, GetLastError()); - if (TerminateProcess(ph, 0)) return JS_NULL; - int err = GetLastError(); - return JS_NewInt32(ctx,err); -} - -/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ -static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pid; - HANDLE ph; - int block = 0, ret; - DWORD options = 0, flags = PROCESS_QUERY_INFORMATION | SYNCHRONIZE; - if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; - if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; - if (block==1) options = INFINITE; - ph = OpenProcess(flags, false, (DWORD) pid); - if (!ph) return JS_NewInt32(ctx, -GetLastError()); - ret = WaitForSingleObject((HANDLE) ph, options); - if (ret == WAIT_TIMEOUT) return JS_NewInt32(ctx, 0); // timed out - if (ret != 0) return JS_NewInt32(ctx, -GetLastError()); - return JS_NewInt32(ctx, pid); -} -#else -/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ -static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pid, status, block = 0, options = 0, ret; - if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; - if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; - if (!block) options = WNOHANG; - ret = waitpid(pid, &status, options); - if (ret == 0) return JS_NewInt32(ctx, 0); - if (ret==pid) return JS_NewInt32(ctx, pid); - return JS_NewInt32(ctx, -errno); -} -#endif - - -#if !defined(_WIN32) && !defined(__wasi__) /* getpid() -> pid */ static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, @@ -4569,18 +4113,17 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("sleep", 1, js_os_sleep ), #if !defined(__wasi__) JS_CFUNC_DEF("realpath", 1, js_os_realpath ), - JS_CFUNC_DEF("symlink", 3, js_os_symlink ), - JS_CFUNC_DEF("pipe", 0, js_os_pipe ), - JS_CFUNC_DEF("exec", 1, js_os_exec ), - JS_CFUNC_DEF("watchpid", 2, js_os_watchpid ), - JS_CFUNC_DEF("kill", 2, js_os_kill ), #endif #if !defined(_WIN32) && !defined(__wasi__) JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), + JS_CFUNC_DEF("symlink", 2, js_os_symlink ), JS_CFUNC_DEF("readlink", 1, js_os_readlink ), + JS_CFUNC_DEF("exec", 1, js_os_exec ), JS_CFUNC_DEF("getpid", 0, js_os_getpid ), JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), OS_FLAG(WNOHANG), + JS_CFUNC_DEF("pipe", 0, js_os_pipe ), + JS_CFUNC_DEF("kill", 2, js_os_kill ), JS_CFUNC_DEF("dup", 1, js_os_dup ), JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), #endif From 7774b71d6ebb440cb92a0e1d691af89cd9ee1cde Mon Sep 17 00:00:00 2001 From: woodtygr Date: Thu, 29 May 2025 16:13:45 -0400 Subject: [PATCH 03/31] Add files via upload --- tests/test_std.js | 95 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/tests/test_std.js b/tests/test_std.js index 32648ef57..207f39ed6 100644 --- a/tests/test_std.js +++ b/tests/test_std.js @@ -161,16 +161,19 @@ function test_os() assert(files.indexOf(fname) >= 0); fdate = 10000; - + err = os.utimes(fpath, fdate, fdate); assert(err, 0); [st, err] = os.stat(fpath); assert(err, 0); assert(st.mode & os.S_IFMT, os.S_IFREG); - assert(st.mtime, fdate); + if (!isWin) { + // moved + assert(st.mtime, fdate); + err = os.symlink(fname, link_path); assert(err, 0); @@ -183,6 +186,7 @@ function test_os() assert(buf, fname); assert(os.remove(link_path) === 0); + } [buf, err] = os.getcwd(); @@ -201,6 +205,80 @@ function test_os() assert(os.remove(fdir) === 0); } +function test_os_pipe() { + + var fds, fr, fw, ab; + fds = os.pipe(); +// console.log("got: " + fds[0] + " " + fds[1]); + fw = std.fdopen(fds[1], "w"); + fr = std.fdopen(fds[0], "r"); + const tstr = "this is a test\r"; + fw.puts(tstr + "\n"); +// fw.puts("this is slow!\r\n"); + fw.puts("\r\n"); + fw.flush(); +// fw.close(); + var barray = new Uint8Array(64); +// ab = fr.read(barray.buffer, 0, 64); +// console.log(ab); +// console.log(fr.getline()); +// console.log(fr.getline()); + ab = fr.getline(); + assert(ab.length, tstr.length); + fw.close(); + fr.close(); +}; + +test_os_pipe(); + +function test_win_os_exec() { + var ret, fds, pid, f, status; + +/* functionality just isn't the same in win32! + in fact, in MSYS the command prompt behaves differently. + I did manage all of this with FDS and PIDS and not handles + hello/r is an artifact of the getline unix emulation + I made the executive decision so that this isn't useless: + watchpid and kill should be made compatible on all systems. + my os.kill is just a terminateprocess call. it returns early. + watchpid is too fast for the kill to complete in VC + NT + at least we can monitor status of a script and execute programs. +*/ + ret = os.exec(["smeegul desgpglam golum"]); + assert(ret, 1); + + ret = os.exec(["echo test 0"]); + assert(ret, 0); + + ret = os.exec(["cmd.exe", "/c", "exit 2"], { usePath: false }); + assert(ret, 2); + + fds = os.pipe(); + pid = os.exec(["echo %FOO%"], { + stdout: fds[1], + block: false, + env: { FOO: "hello" }, + } ); + assert(pid >= 0); + os.close(fds[1]); /* close the write end (as it is only in the child) */ + f = std.fdopen(fds[0], "r"); + var gl = f.getline(); + assert(gl, "hello\r"); + assert(f.getline(), null); + f.close(); + ret = os.watchpid(pid, 1); + assert(ret, pid); + pid = os.exec(["cat"], { block: false } ); + assert(pid >= 0); + os.kill(pid, os.SIGTERM); + os.sleep(1); + ret = os.watchpid(pid, 0); + assert(ret, pid); + // notes from before...... + // Flaky on cygwin for unclear reasons, see + // https://github.com/quickjs-ng/quickjs/issues/184 +}; + function test_os_exec() { var ret, fds, pid, f, status; @@ -223,14 +301,22 @@ function test_os_exec() assert(f.getline(), "hello"); assert(f.getline(), null); f.close(); + /* [ret, status] = os.waitpid(pid, 0); assert(ret, pid); assert(status & 0x7f, 0); /* exited */ - assert(status >> 8, 0); /* exit code */ + //assert(status >> 8, 0); /* exit code */ + + // added to test watchpid (for win32 compatible code) + ret = os.watchpid(pid, 1); + assert(ret, pid); pid = os.exec(["cat"], { block: false } ); + ret = os.watchpid(pid, 0); + assert(ret, 0); assert(pid >= 0); os.kill(pid, os.SIGTERM); + //os.sleep(1); [ret, status] = os.waitpid(pid, 0); assert(ret, pid); // Flaky on cygwin for unclear reasons, see @@ -293,7 +379,8 @@ test_file2(); test_getline(); test_popen(); test_os(); -!isWin && test_os_exec(); +if (!isWin) test_os_exec(); +else test_win_os_exec(); test_interval(); test_timeout(); test_timeout_order(); From ba9e4c4e80208ff78ccd02ce0174e67d8768a207 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Thu, 29 May 2025 16:14:38 -0400 Subject: [PATCH 04/31] Add files via upload --- tests/test_std.js | 95 ++--------------------------------------------- 1 file changed, 4 insertions(+), 91 deletions(-) diff --git a/tests/test_std.js b/tests/test_std.js index 207f39ed6..32648ef57 100644 --- a/tests/test_std.js +++ b/tests/test_std.js @@ -161,19 +161,16 @@ function test_os() assert(files.indexOf(fname) >= 0); fdate = 10000; - + err = os.utimes(fpath, fdate, fdate); assert(err, 0); [st, err] = os.stat(fpath); assert(err, 0); assert(st.mode & os.S_IFMT, os.S_IFREG); - + assert(st.mtime, fdate); if (!isWin) { - // moved - assert(st.mtime, fdate); - err = os.symlink(fname, link_path); assert(err, 0); @@ -186,7 +183,6 @@ function test_os() assert(buf, fname); assert(os.remove(link_path) === 0); - } [buf, err] = os.getcwd(); @@ -205,80 +201,6 @@ function test_os() assert(os.remove(fdir) === 0); } -function test_os_pipe() { - - var fds, fr, fw, ab; - fds = os.pipe(); -// console.log("got: " + fds[0] + " " + fds[1]); - fw = std.fdopen(fds[1], "w"); - fr = std.fdopen(fds[0], "r"); - const tstr = "this is a test\r"; - fw.puts(tstr + "\n"); -// fw.puts("this is slow!\r\n"); - fw.puts("\r\n"); - fw.flush(); -// fw.close(); - var barray = new Uint8Array(64); -// ab = fr.read(barray.buffer, 0, 64); -// console.log(ab); -// console.log(fr.getline()); -// console.log(fr.getline()); - ab = fr.getline(); - assert(ab.length, tstr.length); - fw.close(); - fr.close(); -}; - -test_os_pipe(); - -function test_win_os_exec() { - var ret, fds, pid, f, status; - -/* functionality just isn't the same in win32! - in fact, in MSYS the command prompt behaves differently. - I did manage all of this with FDS and PIDS and not handles - hello/r is an artifact of the getline unix emulation - I made the executive decision so that this isn't useless: - watchpid and kill should be made compatible on all systems. - my os.kill is just a terminateprocess call. it returns early. - watchpid is too fast for the kill to complete in VC + NT - at least we can monitor status of a script and execute programs. -*/ - ret = os.exec(["smeegul desgpglam golum"]); - assert(ret, 1); - - ret = os.exec(["echo test 0"]); - assert(ret, 0); - - ret = os.exec(["cmd.exe", "/c", "exit 2"], { usePath: false }); - assert(ret, 2); - - fds = os.pipe(); - pid = os.exec(["echo %FOO%"], { - stdout: fds[1], - block: false, - env: { FOO: "hello" }, - } ); - assert(pid >= 0); - os.close(fds[1]); /* close the write end (as it is only in the child) */ - f = std.fdopen(fds[0], "r"); - var gl = f.getline(); - assert(gl, "hello\r"); - assert(f.getline(), null); - f.close(); - ret = os.watchpid(pid, 1); - assert(ret, pid); - pid = os.exec(["cat"], { block: false } ); - assert(pid >= 0); - os.kill(pid, os.SIGTERM); - os.sleep(1); - ret = os.watchpid(pid, 0); - assert(ret, pid); - // notes from before...... - // Flaky on cygwin for unclear reasons, see - // https://github.com/quickjs-ng/quickjs/issues/184 -}; - function test_os_exec() { var ret, fds, pid, f, status; @@ -301,22 +223,14 @@ function test_os_exec() assert(f.getline(), "hello"); assert(f.getline(), null); f.close(); - /* [ret, status] = os.waitpid(pid, 0); assert(ret, pid); assert(status & 0x7f, 0); /* exited */ - //assert(status >> 8, 0); /* exit code */ - - // added to test watchpid (for win32 compatible code) - ret = os.watchpid(pid, 1); - assert(ret, pid); + assert(status >> 8, 0); /* exit code */ pid = os.exec(["cat"], { block: false } ); - ret = os.watchpid(pid, 0); - assert(ret, 0); assert(pid >= 0); os.kill(pid, os.SIGTERM); - //os.sleep(1); [ret, status] = os.waitpid(pid, 0); assert(ret, pid); // Flaky on cygwin for unclear reasons, see @@ -379,8 +293,7 @@ test_file2(); test_getline(); test_popen(); test_os(); -if (!isWin) test_os_exec(); -else test_win_os_exec(); +!isWin && test_os_exec(); test_interval(); test_timeout(); test_timeout_order(); From 3d43bdb6da13a87d5cec9aea12a5aab599d4e48f Mon Sep 17 00:00:00 2001 From: woodtygr Date: Fri, 30 May 2025 16:42:58 -0400 Subject: [PATCH 05/31] Update test_std.js I put this back like it was. Changes will be split into smaller parts and I've gotten confused when permissions went back and forth --- test_std.js | 95 +++-------------------------------------------------- 1 file changed, 4 insertions(+), 91 deletions(-) diff --git a/test_std.js b/test_std.js index 207f39ed6..32648ef57 100644 --- a/test_std.js +++ b/test_std.js @@ -161,19 +161,16 @@ function test_os() assert(files.indexOf(fname) >= 0); fdate = 10000; - + err = os.utimes(fpath, fdate, fdate); assert(err, 0); [st, err] = os.stat(fpath); assert(err, 0); assert(st.mode & os.S_IFMT, os.S_IFREG); - + assert(st.mtime, fdate); if (!isWin) { - // moved - assert(st.mtime, fdate); - err = os.symlink(fname, link_path); assert(err, 0); @@ -186,7 +183,6 @@ function test_os() assert(buf, fname); assert(os.remove(link_path) === 0); - } [buf, err] = os.getcwd(); @@ -205,80 +201,6 @@ function test_os() assert(os.remove(fdir) === 0); } -function test_os_pipe() { - - var fds, fr, fw, ab; - fds = os.pipe(); -// console.log("got: " + fds[0] + " " + fds[1]); - fw = std.fdopen(fds[1], "w"); - fr = std.fdopen(fds[0], "r"); - const tstr = "this is a test\r"; - fw.puts(tstr + "\n"); -// fw.puts("this is slow!\r\n"); - fw.puts("\r\n"); - fw.flush(); -// fw.close(); - var barray = new Uint8Array(64); -// ab = fr.read(barray.buffer, 0, 64); -// console.log(ab); -// console.log(fr.getline()); -// console.log(fr.getline()); - ab = fr.getline(); - assert(ab.length, tstr.length); - fw.close(); - fr.close(); -}; - -test_os_pipe(); - -function test_win_os_exec() { - var ret, fds, pid, f, status; - -/* functionality just isn't the same in win32! - in fact, in MSYS the command prompt behaves differently. - I did manage all of this with FDS and PIDS and not handles - hello/r is an artifact of the getline unix emulation - I made the executive decision so that this isn't useless: - watchpid and kill should be made compatible on all systems. - my os.kill is just a terminateprocess call. it returns early. - watchpid is too fast for the kill to complete in VC + NT - at least we can monitor status of a script and execute programs. -*/ - ret = os.exec(["smeegul desgpglam golum"]); - assert(ret, 1); - - ret = os.exec(["echo test 0"]); - assert(ret, 0); - - ret = os.exec(["cmd.exe", "/c", "exit 2"], { usePath: false }); - assert(ret, 2); - - fds = os.pipe(); - pid = os.exec(["echo %FOO%"], { - stdout: fds[1], - block: false, - env: { FOO: "hello" }, - } ); - assert(pid >= 0); - os.close(fds[1]); /* close the write end (as it is only in the child) */ - f = std.fdopen(fds[0], "r"); - var gl = f.getline(); - assert(gl, "hello\r"); - assert(f.getline(), null); - f.close(); - ret = os.watchpid(pid, 1); - assert(ret, pid); - pid = os.exec(["cat"], { block: false } ); - assert(pid >= 0); - os.kill(pid, os.SIGTERM); - os.sleep(1); - ret = os.watchpid(pid, 0); - assert(ret, pid); - // notes from before...... - // Flaky on cygwin for unclear reasons, see - // https://github.com/quickjs-ng/quickjs/issues/184 -}; - function test_os_exec() { var ret, fds, pid, f, status; @@ -301,22 +223,14 @@ function test_os_exec() assert(f.getline(), "hello"); assert(f.getline(), null); f.close(); - /* [ret, status] = os.waitpid(pid, 0); assert(ret, pid); assert(status & 0x7f, 0); /* exited */ - //assert(status >> 8, 0); /* exit code */ - - // added to test watchpid (for win32 compatible code) - ret = os.watchpid(pid, 1); - assert(ret, pid); + assert(status >> 8, 0); /* exit code */ pid = os.exec(["cat"], { block: false } ); - ret = os.watchpid(pid, 0); - assert(ret, 0); assert(pid >= 0); os.kill(pid, os.SIGTERM); - //os.sleep(1); [ret, status] = os.waitpid(pid, 0); assert(ret, pid); // Flaky on cygwin for unclear reasons, see @@ -379,8 +293,7 @@ test_file2(); test_getline(); test_popen(); test_os(); -if (!isWin) test_os_exec(); -else test_win_os_exec(); +!isWin && test_os_exec(); test_interval(); test_timeout(); test_timeout_order(); From a5395d3cb39bc543a45664bb9acf8fc7324bc80d Mon Sep 17 00:00:00 2001 From: woodtygr Date: Fri, 30 May 2025 17:03:06 -0400 Subject: [PATCH 06/31] Delete test_std.js getting back to original codebase --- test_std.js | 300 ---------------------------------------------------- 1 file changed, 300 deletions(-) delete mode 100644 test_std.js diff --git a/test_std.js b/test_std.js deleted file mode 100644 index 32648ef57..000000000 --- a/test_std.js +++ /dev/null @@ -1,300 +0,0 @@ -import * as std from "qjs:std"; -import * as os from "qjs:os"; -import { assert } from "./assert.js"; - -const isWin = os.platform === 'win32'; -const isCygwin = os.platform === 'cygwin'; - - -function test_printf() -{ - assert(std.sprintf("a=%d s=%s", 123, "abc"), "a=123 s=abc"); - assert(std.sprintf("%010d", 123), "0000000123"); - assert(std.sprintf("%x", -2), "fffffffe"); - assert(std.sprintf("%lx", -2), "fffffffffffffffe"); - assert(std.sprintf("%10.1f", 2.1), " 2.1"); - assert(std.sprintf("%*.*f", 10, 2, -2.13), " -2.13"); - assert(std.sprintf("%#lx", 0x7fffffffffffffffn), "0x7fffffffffffffff"); -} - -function test_file1() -{ - var f, len, str, size, buf, ret, i, str1, ab; - - f = std.tmpfile(); - str = "hello world\n"; - f.puts(str); - - f.seek(0, std.SEEK_SET); - ab = f.readAsArrayBuffer(); - assert([...new Uint8Array(ab)], str.split("").map(c => c.charCodeAt(0))); - - f.seek(0, std.SEEK_SET); - str1 = f.readAsString(); - assert(str1, str); - - f.seek(0, std.SEEK_END); - size = f.tell(); - assert(size, str.length); - - f.seek(0, std.SEEK_SET); - - buf = new Uint8Array(size); - ret = f.read(buf.buffer, 0, size); - assert(ret, size); - for(i = 0; i < size; i++) - assert(buf[i], str.charCodeAt(i)); - - f.close(); -} - -function test_file2() -{ - var f, str, i, size; - f = std.tmpfile(); - str = "hello world\n"; - size = str.length; - for(i = 0; i < size; i++) - f.putByte(str.charCodeAt(i)); - f.seek(0, std.SEEK_SET); - for(i = 0; i < size; i++) { - assert(str.charCodeAt(i), f.getByte()); - } - assert(f.getByte(), -1); - f.close(); -} - -function test_getline() -{ - var f, line, line_count, lines, i; - - lines = ["hello world", "line 1", "line 2" ]; - f = std.tmpfile(); - for(i = 0; i < lines.length; i++) { - f.puts(lines[i], "\n"); - } - - f.seek(0, std.SEEK_SET); - assert(!f.eof()); - line_count = 0; - for(;;) { - line = f.getline(); - if (line === null) - break; - assert(line, lines[line_count]); - line_count++; - } - assert(f.eof()); - assert(line_count, lines.length); - - f.close(); -} - -function test_popen() -{ - var str, f, fname = "tmp_file.txt"; - var ta, content = "hello world"; - var cmd = isWin ? "type" : "cat"; - - ta = new Uint8Array([...content].map(c => c.charCodeAt(0))); - std.writeFile(fname, ta); - assert(std.loadFile(fname), content); - std.writeFile(fname, ta.buffer); - assert(std.loadFile(fname), content); - std.writeFile(fname, content); - assert(std.loadFile(fname), content); - - /* execute shell command */ - f = std.popen(cmd + " " + fname, "r"); - str = f.readAsString(); - f.close(); - - assert(str, content); - - os.remove(fname); -} - -function test_os() -{ - var fd, fpath, fname, fdir, buf, buf2, i, files, err, fdate, st, link_path; - - // XXX(bnoordhuis) disabled because stdio is not a tty on CI - //assert(os.isatty(0)); - - fdir = "test_tmp_dir"; - fname = "tmp_file.txt"; - fpath = fdir + "/" + fname; - link_path = fdir + "/test_link"; - - os.remove(link_path); - os.remove(fpath); - os.remove(fdir); - - err = os.mkdir(fdir, 0o755); - assert(err, 0); - - fd = os.open(fpath, os.O_RDWR | os.O_CREAT | os.O_TRUNC); - assert(fd >= 0); - - buf = new Uint8Array(10); - for(i = 0; i < buf.length; i++) - buf[i] = i; - assert(os.write(fd, buf.buffer, 0, buf.length), buf.length); - - assert(os.seek(fd, 0, std.SEEK_SET), 0); - buf2 = new Uint8Array(buf.length); - assert(os.read(fd, buf2.buffer, 0, buf2.length), buf2.length); - - for(i = 0; i < buf.length; i++) - assert(buf[i] == buf2[i]); - - if (typeof BigInt !== "undefined") { - assert(os.seek(fd, BigInt(6), std.SEEK_SET), BigInt(6)); - assert(os.read(fd, buf2.buffer, 0, 1), 1); - assert(buf[6] == buf2[0]); - } - - assert(os.close(fd), 0); - - [files, err] = os.readdir(fdir); - assert(err, 0); - assert(files.indexOf(fname) >= 0); - - fdate = 10000; - - err = os.utimes(fpath, fdate, fdate); - assert(err, 0); - - [st, err] = os.stat(fpath); - assert(err, 0); - assert(st.mode & os.S_IFMT, os.S_IFREG); - assert(st.mtime, fdate); - - if (!isWin) { - err = os.symlink(fname, link_path); - assert(err, 0); - - [st, err] = os.lstat(link_path); - assert(err, 0); - assert(st.mode & os.S_IFMT, os.S_IFLNK); - - [buf, err] = os.readlink(link_path); - assert(err, 0); - assert(buf, fname); - - assert(os.remove(link_path) === 0); - } - - [buf, err] = os.getcwd(); - assert(err, 0); - - [buf2, err] = os.realpath("."); - assert(err, 0); - - assert(buf, buf2); - - assert(os.remove(fpath) === 0); - - fd = os.open(fpath, os.O_RDONLY); - assert(fd < 0); - - assert(os.remove(fdir) === 0); -} - -function test_os_exec() -{ - var ret, fds, pid, f, status; - - ret = os.exec(["true"]); - assert(ret, 0); - - ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false }); - assert(ret, 1); - - fds = os.pipe(); - pid = os.exec(["sh", "-c", "echo $FOO"], { - stdout: fds[1], - block: false, - env: { FOO: "hello" }, - } ); - assert(pid >= 0); - os.close(fds[1]); /* close the write end (as it is only in the child) */ - f = std.fdopen(fds[0], "r"); - assert(f.getline(), "hello"); - assert(f.getline(), null); - f.close(); - [ret, status] = os.waitpid(pid, 0); - assert(ret, pid); - assert(status & 0x7f, 0); /* exited */ - assert(status >> 8, 0); /* exit code */ - - pid = os.exec(["cat"], { block: false } ); - assert(pid >= 0); - os.kill(pid, os.SIGTERM); - [ret, status] = os.waitpid(pid, 0); - assert(ret, pid); - // Flaky on cygwin for unclear reasons, see - // https://github.com/quickjs-ng/quickjs/issues/184 - if (!isCygwin) { - assert(status & 0x7f, os.SIGTERM); - } -} - -function test_interval() -{ - var t = os.setInterval(f, 1); - function f() { - if (++f.count === 3) os.clearInterval(t); - } - f.count = 0; -} - -function test_timeout() -{ - var th, i; - - /* just test that a timer can be inserted and removed */ - th = []; - for(i = 0; i < 3; i++) - th[i] = os.setTimeout(function () { }, 1000); - for(i = 0; i < 3; i++) - os.clearTimeout(th[i]); -} - -function test_timeout_order() -{ - var s = ""; - os.setTimeout(a, 0); - os.setTimeout(b, 100); - os.setTimeout(d, 700); - function a() { s += "a"; os.setTimeout(c, 300); } - function b() { s += "b"; } - function c() { s += "c"; } - function d() { assert(s, "abc"); } // not "acb" -} - -function test_stdio_close() -{ - for (const f of [std.in, std.out, std.err]) { - let caught = false; - try { - f.close(); - } catch (e) { - assert(/cannot close stdio/.test(e.message)); - caught = true; - } - assert(caught); - } -} - -test_printf(); -test_file1(); -test_file2(); -test_getline(); -test_popen(); -test_os(); -!isWin && test_os_exec(); -test_interval(); -test_timeout(); -test_timeout_order(); -test_stdio_close(); From 78fccd976f48a9e3aa22cf6118e6b336d75245eb Mon Sep 17 00:00:00 2001 From: woodtygr Date: Fri, 30 May 2025 18:22:34 -0400 Subject: [PATCH 07/31] adding test script for win32 os.exec tests on win32: os.exec os.pipe (fd version) os.kill os.watchpid maintains test on linux: os.exec os.kill os.waitpid os.watchpid os.pipe --- tests/test_win_exec.js | 92 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/test_win_exec.js diff --git a/tests/test_win_exec.js b/tests/test_win_exec.js new file mode 100644 index 000000000..4ed2467b1 --- /dev/null +++ b/tests/test_win_exec.js @@ -0,0 +1,92 @@ +import * as std from "qjs:std"; +import * as os from "qjs:os"; +import { assert } from "./assert.js"; + +const isWin = os.platform === 'win32'; +const isCygwin = os.platform === 'cygwin'; + +function test_win_os_exec() { + var ret, fds, pid, f, status; + + ret = os.exec(["smeegul desgpglam golum"]); + assert(ret, 1); + + ret = os.exec(["echo test 0"]); + assert(ret, 0); + + ret = os.exec(["cmd.exe", "/c", "exit 2"], { usePath: false }); + assert(ret, 2); + + fds = os.pipe(); + pid = os.exec(["echo %FOO%"], { + stdout: fds[1], + block: false, + env: { FOO: "hello" }, + } ); + assert(pid >= 0); + os.close(fds[1]); + f = std.fdopen(fds[0], "r"); + var gl = f.getline(); + /* artifact of windows to c compatibility. + may could change the code for getline, but that's a can of worms. + such a thing would require detecting /r and trimming every time. */ + assert(gl, "hello\r"); + assert(f.getline(), null); + f.close(); + /* I invented watchpid to at least notify if the PID is complete. + There's no windows equivalent, and windows would prefer Handles. + At least a user can script basic tasks with this asynchronously */ + ret = os.watchpid(pid, 1); + assert(ret, pid); + pid = os.exec(["cat"], { block: false } ); + assert(pid >= 0); + /* Just does TerminateProcess. There's no signal. + Windows does signal pid groups and that takes research. */ + os.kill(pid, os.SIGTERM); + os.sleep(1); + ret = os.watchpid(pid, 0); + assert(ret, pid); +}; + +function test_os_exec() +{ + var ret, fds, pid, f, status; + + ret = os.exec(["true"]); + assert(ret, 0); + + ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false }); + assert(ret, 1); + + fds = os.pipe(); + pid = os.exec(["sh", "-c", "echo $FOO"], { + stdout: fds[1], + block: false, + env: { FOO: "hello" }, + } ); + assert(pid >= 0); + os.close(fds[1]); /* close the write end (as it is only in the child) */ + f = std.fdopen(fds[0], "r"); + assert(f.getline(), "hello"); + assert(f.getline(), null); + f.close(); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + assert(status & 0x7f, 0); /* exited */ + assert(status >> 8, 0); /* exit code */ + + pid = os.exec(["cat"], { block: false } ); + assert(pid >= 0); + os.kill(pid, os.SIGTERM); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + // Flaky on cygwin for unclear reasons, see + // https://github.com/quickjs-ng/quickjs/issues/184 + if (!isCygwin) { + assert(status & 0x7f, os.SIGTERM); + } +} + + +if (!isWin) test_os_exec(); +else test_win_os_exec(); \ No newline at end of file From a1e3c0eeffba88536b81b54e4abf0d7b252b2c5c Mon Sep 17 00:00:00 2001 From: woodtygr Date: Fri, 30 May 2025 18:26:54 -0400 Subject: [PATCH 08/31] os_exec adds os_exec, os_pipe, build_ms_envp, os_watchpid (2 versions) os_kill --- quickjs-libc.c | 375 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 362 insertions(+), 13 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index d08ff7f74..7e08eb38f 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3057,6 +3057,73 @@ static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, } #endif +#if defined(_WIN32) +#define MS_ENVP_BUFFSIZE 8192 +static char *build_ms_envp(JSContext *ctx, JSValue obj) +{ + uint32_t len, i; + JSPropertyEnum *tab; + char *envp, *pair; + const char *key, *str; + JSValue val; + size_t key_len, str_len; + + if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) + return NULL; + envp = js_mallocz(ctx,MS_ENVP_BUFFSIZE); + pair = envp; + if (!envp) goto fail; + for(i = 0; i < len; i++) { + val = JS_GetProperty(ctx, obj, tab[i].atom); + if (JS_IsException(val)) + goto fail; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) goto fail; + key = JS_AtomToCString(ctx, tab[i].atom); + if (!key) { + JS_FreeCString(ctx, str); + goto fail; + } + key_len = strlen(key); + str_len = strlen(str); + /* parallel to build_envp for comparison + pair = js_malloc(ctx, key_len + str_len + 2); + if (!pair) { + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + goto fail; + }*/ + memcpy(pair, key, key_len); + pair += key_len; + pair[0] = '='; pair++; + memcpy(pair, str, str_len); + pair += str_len; + pair[0] = '\0'; + pair++; + //envp[i] = pair; + JS_FreeCString(ctx, key); + JS_FreeCString(ctx, str); + } + pair[0] = '\0'; + end: + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + return envp; + fail: + if (envp) { + //for(i = 0; i < len; i++) js_free(ctx, envp[i]); + js_free(ctx, envp); + envp = NULL; + } + goto end; + +}; + +#endif + #if !defined(_WIN32) && !defined(__wasi__) static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3230,19 +3297,30 @@ static void js_os_exec_once_init(void) #endif +#endif // ! WIN32 ! wasi + +#if !defined(__wasi__) /* exec(args[, options]) -> exitcode */ +#define OS_EXEC_CMD_BUFFSIZE 2048 static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { JSValueConst options, args = argv[0]; JSValue val, ret_val; - const char **exec_argv, *file = NULL, *str, *cwd = NULL; - char **envp = environ; + const char *file = NULL, *str, *cwd = NULL; uint32_t exec_argc, i; - int ret, pid, status; - bool block_flag = true, use_path = true; + int ret, pid; + bool block_flag = true, use_path = true, specified_fd = false; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; +#ifdef _WIN32 + char cmdbuff [OS_EXEC_CMD_BUFFSIZE]; + int cmdi; + char *envp = 0; +#else + char **envp = environ; + const char **exec_argv; + int status; uint32_t uid = -1, gid = -1; int ngroups = -1; gid_t groups[64]; @@ -3272,6 +3350,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, exec_argv[i] = str; } exec_argv[exec_argc] = NULL; +#endif for(i = 0; i < 3; i++) std_fds[i] = i; @@ -3317,9 +3396,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; std_fds[i] = fd; + specified_fd = true; } } +#ifndef _WIN32 val = JS_GetPropertyStr(ctx, options, "env"); if (JS_IsException(val)) goto exception; @@ -3329,7 +3410,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (!envp) goto exception; } - + val = JS_GetPropertyStr(ctx, options, "uid"); if (JS_IsException(val)) goto exception; @@ -3339,7 +3420,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; } - + val = JS_GetPropertyStr(ctx, options, "gid"); if (JS_IsException(val)) goto exception; @@ -3382,10 +3463,171 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (idx < len) goto exception; } - + } - -#if !defined(EMSCRIPTEN) && !defined(__wasi__) +#else + val = JS_GetPropertyStr(ctx, options, "env"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + envp = build_ms_envp(ctx, val); + JS_FreeValue(ctx, val); + if (!envp) + goto exception; + } + + + } + + cmdi = 0; int cmdsl; + /* uncomment this to put cwd into the cmdline + if (cwd) { + cmdsl = strlen(cwd); + if (cmdsl) { + strncpy(&cmdbuff[cmdi], cwd, cmdsl); + cmdi += cmdsl; + if (cmdbuff[cmdi - 1] != '\\') { + cmdbuff[cmdi] = '\\'; + cmdi++; + }; + }; + }; + */ + if (file) cmdsl = strlen(file); + else cmdsl = 0; + if (cmdsl) { + strncpy(&cmdbuff[cmdi], file, cmdsl); + cmdi += cmdsl; + } else { + strncpy(&cmdbuff[cmdi], "cmd /C ", 7); + cmdi += 7; + }; + + val = JS_GetPropertyStr(ctx, args, "length"); + if (JS_IsException(val)) goto exception; + ret = JS_ToUint32(ctx, &exec_argc, val); + JS_FreeValue(ctx, val); + if (ret) goto exception; + /* arbitrary limit to avoid overflow */ + if (exec_argc < 1 || exec_argc > 65535) { + JS_ThrowTypeError(ctx, "invalid number of arguments"); + goto exception; + } + for(i = 0; i < exec_argc; i++) { + val = JS_GetPropertyUint32(ctx, args, i); + if (JS_IsException(val)) goto exception; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) goto exception; + cmdsl = strlen(str); + if (cmdsl) { + if ((i != 0) || (file != 0)) { + cmdbuff[cmdi] = ' '; + cmdi++; + }; + if (OS_EXEC_CMD_BUFFSIZE < (cmdi + cmdsl + 1)) { + JS_ThrowRangeError(ctx, "exec command line too long."); + goto exception; + }; + strncpy(&cmdbuff[cmdi], str, cmdsl); + cmdi += cmdsl; + }; + } + cmdbuff[cmdi] = 0; +#ifdef WIN32_OS_EXEC_WORKING + printf("path to exec: [%s] path:[%s]\r\n", cmdbuff, cwd); +#endif // working + +#endif // WIN32 + +#ifdef WIN32 + STARTUPINFO istart; + PROCESS_INFORMATION iproc; + // memset was absolutely necessary. I kept getting deeper system crashes + memset(&iproc, 0, sizeof(iproc)); + memset(&istart, 0, sizeof(istart) ); + istart.cb = sizeof(istart); + + if (specified_fd) { // this is also something worth tinkering with + istart.dwFlags = STARTF_USESTDHANDLES; + //}; + + istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); + if (istart.hStdInput == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stdin of process"); + goto exception; + }; + istart.hStdOutput = (HANDLE) _get_osfhandle( std_fds[1] ); + if (istart.hStdOutput == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stdout of process"); + goto exception; + }; + istart.hStdError = (HANDLE) _get_osfhandle( std_fds[2] ); + if (istart.hStdError == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stderr of process"); + goto exception; + }; + // */ + }; +/* + printf("using handles %" PRIx64 " from %d, %" PRIx64 " from %d, %" PRIx64 " from %d\r\n", + (int64_t) istart.hStdInput, std_fds[0], (int64_t) istart.hStdOutput, std_fds[1], + (int64_t) istart.hStdError, std_fds[2]); + +BOOL CreateProcessA( + [in, optional] LPCSTR lpApplicationName, + [in, out, optional] LPSTR lpCommandLine, + [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, + [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, + [in] BOOL bInheritHandles, + [in] DWORD dwCreationFlags, + [in, optional] LPVOID lpEnvironment, + [in, optional] LPCSTR lpCurrentDirectory, + [in] LPSTARTUPINFOA lpStartupInfo, + [out] LPPROCESS_INFORMATION lpProcessInformation +);*/ + + int cflags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; // prevents output on stdout + + // seems like inherithandles and environ crash the return value if not perfect! + // we may also need to sim a cmd.exe chdir and a manual push with a cwd .... + if (!CreateProcessA(file, cmdbuff, 0, 0, true, cflags, envp, cwd, &istart, &iproc) ) + { + int e = GetLastError(); + JS_ThrowInternalError(ctx, "failed to start process with error: %d", e); + goto exception; + }; + + int32_t excode = 0; + pid = iproc.dwProcessId; + HANDLE phandle = iproc.hProcess; + HANDLE thandle = iproc.hThread; + int besafe = 0; + if (block_flag) { + for(;;) { + int dwait = WaitForSingleObject(phandle, INFINITE); + if (dwait == WAIT_OBJECT_0) { + GetExitCodeProcess (phandle, (DWORD*) &excode); + ret_val = JS_NewInt32(ctx, (int32_t) excode); + excode = GetLastError(); + break; + } else { + besafe++; if (besafe==20) { + JS_ThrowPlainError(ctx, "exec process did not complete."); + goto exception; + }; + }; + }; + CloseHandle(phandle); + CloseHandle(thandle); + } else { + excode = (int32_t) iproc.dwProcessId; + ret_val = JS_NewInt32(ctx, excode); //(int64_t) phandle); + } + +#else // _WIN32 + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) // should happen pre-fork because it calls dlsym() // and that's not an async-signal-safe function js_once(&js_os_exec_once, js_os_exec_once_init); @@ -3457,9 +3699,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, ret = pid; } ret_val = JS_NewInt32(ctx, ret); +#endif // !_WIN32 done: JS_FreeCString(ctx, file); JS_FreeCString(ctx, cwd); +#ifndef _WIN32 for(i = 0; i < exec_argc; i++) JS_FreeCString(ctx, exec_argv[i]); js_free(ctx, exec_argv); @@ -3473,10 +3717,114 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, js_free(ctx, envp); } return ret_val; +#else + if (envp) js_free(ctx, envp); + return ret_val; +#endif exception: ret_val = JS_EXCEPTION; goto done; } +#endif + +#ifdef _WIN32 +/* pipe() -> [read_fd, write_fd] or null if error */ +static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pipe_fds[2], ret; + JSValue obj; +#ifdef _WIN32 +#define PIPE_BUFFSIZE 4096 +#define WIN_PIPE_SPECIALFLAGS 0 +#define WIN_OS_PIPE_USE_FDS + HANDLE pipe_handles[2]; + struct _SECURITY_ATTRIBUTES secur; + secur.nLength = sizeof(secur); + secur.lpSecurityDescriptor = 0; + secur.bInheritHandle = true; + ret = CreatePipe(&pipe_handles[0], &pipe_handles[1], &secur, PIPE_BUFFSIZE); + if (ret == 0) return JS_NULL; +#ifdef WIN_OS_PIPE_USE_FDS + pipe_fds[0] = _open_osfhandle( (intptr_t) pipe_handles[0], WIN_PIPE_SPECIALFLAGS ); + if (pipe_fds[0] == -1 ) { + CloseHandle(pipe_handles[0]); + return JS_NULL; + }; + pipe_fds[1] = _open_osfhandle( (intptr_t) pipe_handles[1], WIN_PIPE_SPECIALFLAGS ); + if (pipe_fds[1] == -1 ) { + CloseHandle(pipe_handles[1]); + return JS_NULL; + }; +#else + pipe_fds[0] = pipe_handles[0]; + pipe_fds[1] = pipe_handles[1]; +#endif // WIN_OS_PIPE_USE_FDS +#else + ret = pipe(pipe_fds); + if (ret < 0) return JS_NULL; +#endif // _WIN32 + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), + JS_PROP_C_W_E); + JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), + JS_PROP_C_W_E); + return obj; +} + +/* kill(pid, sig) */ +static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid; + HANDLE ph; + DWORD flags = PROCESS_TERMINATE; + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + ph = OpenProcess(flags, false, (DWORD) pid); + if (!ph) return JS_NewInt32(ctx, GetLastError()); + if (TerminateProcess(ph, 0)) return JS_NULL; + int err = GetLastError(); + return JS_NewInt32(ctx,err); +} + +/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ +static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid; + HANDLE ph; + int block = 0, ret; + DWORD options = 0, flags = PROCESS_QUERY_INFORMATION | SYNCHRONIZE; + if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; + if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; + if (block==1) options = INFINITE; + ph = OpenProcess(flags, false, (DWORD) pid); + if (!ph) return JS_NewInt32(ctx, -GetLastError()); + ret = WaitForSingleObject((HANDLE) ph, options); + if (ret == WAIT_TIMEOUT) return JS_NewInt32(ctx, 0); // timed out + if (ret != 0) return JS_NewInt32(ctx, -GetLastError()); + return JS_NewInt32(ctx, pid); +} +#endif + +#if !defined(_WIN32) && !defined(__wasi__) +/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ +static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int pid, status, block = 0, options = 0, ret; + if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; + if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; + if (!block) options = WNOHANG; + ret = waitpid(pid, &status, options); + if (ret == 0) return JS_NewInt32(ctx, 0); + if (ret==pid) return JS_NewInt32(ctx, pid); + return JS_NewInt32(ctx, -errno); +} + /* getpid() -> pid */ static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, @@ -4113,17 +4461,18 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("sleep", 1, js_os_sleep ), #if !defined(__wasi__) JS_CFUNC_DEF("realpath", 1, js_os_realpath ), -#endif + JS_CFUNC_DEF("exec", 1, js_os_exec ), + JS_CFUNC_DEF("watchpid", 2, js_os_watchpid ), + JS_CFUNC_DEF("pipe", 0, js_os_pipe ), + JS_CFUNC_DEF("kill", 2, js_os_kill ), + #endif #if !defined(_WIN32) && !defined(__wasi__) JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), JS_CFUNC_DEF("symlink", 2, js_os_symlink ), JS_CFUNC_DEF("readlink", 1, js_os_readlink ), - JS_CFUNC_DEF("exec", 1, js_os_exec ), JS_CFUNC_DEF("getpid", 0, js_os_getpid ), JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), OS_FLAG(WNOHANG), - JS_CFUNC_DEF("pipe", 0, js_os_pipe ), - JS_CFUNC_DEF("kill", 2, js_os_kill ), JS_CFUNC_DEF("dup", 1, js_os_dup ), JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), #endif From 98608a04d760fec35ecd5c903197d85e7ef492cd Mon Sep 17 00:00:00 2001 From: woodtygr Date: Sat, 31 May 2025 13:32:09 -0400 Subject: [PATCH 09/31] Update and rename test_win_exec.js to test_os_exec.js just modified comments and changed name. I do plan to test watchpid on non-win32. --- tests/{test_win_exec.js => test_os_exec.js} | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) rename tests/{test_win_exec.js => test_os_exec.js} (83%) diff --git a/tests/test_win_exec.js b/tests/test_os_exec.js similarity index 83% rename from tests/test_win_exec.js rename to tests/test_os_exec.js index 4ed2467b1..365c63dd5 100644 --- a/tests/test_win_exec.js +++ b/tests/test_os_exec.js @@ -27,15 +27,14 @@ function test_win_os_exec() { os.close(fds[1]); f = std.fdopen(fds[0], "r"); var gl = f.getline(); - /* artifact of windows to c compatibility. - may could change the code for getline, but that's a can of worms. - such a thing would require detecting /r and trimming every time. */ + /* artifact of windows to c compatibility between getline and cmd.exe echo*/ assert(gl, "hello\r"); assert(f.getline(), null); f.close(); - /* I invented watchpid to at least notify if the PID is complete. - There's no windows equivalent, and windows would prefer Handles. - At least a user can script basic tasks with this asynchronously */ + /* I created watchpid to at least notify if the PID is complete cross-compatibly. + There's no windows equivalent of waitpid, and windows would prefer Handles. + watchpid returns negative error || 0 if still waiting || pid if complete. + watchpid returns no status. specify 1 in the 2nd param for blocking */ ret = os.watchpid(pid, 1); assert(ret, pid); pid = os.exec(["cat"], { block: false } ); @@ -89,4 +88,4 @@ function test_os_exec() if (!isWin) test_os_exec(); -else test_win_os_exec(); \ No newline at end of file +else test_win_os_exec(); From 9750c9d32e40527219448989dbbadff7cc9d8ae6 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Sat, 31 May 2025 14:43:10 -0400 Subject: [PATCH 10/31] Update test_os_exec.js managed to also verify execution of .exe, return values of exe, and stderr handle. verifies watchpid in non-win32 for compatible scripting. the only non-compatible issue left I see is that getline() pulls from windows needing \r to be trimmed from --- tests/test_os_exec.js | 59 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index 365c63dd5..0d5a876aa 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -8,7 +8,7 @@ const isCygwin = os.platform === 'cygwin'; function test_win_os_exec() { var ret, fds, pid, f, status; - ret = os.exec(["smeegul desgpglam golum"]); + ret = os.exec(["bad command or filename @@"]); assert(ret, 1); ret = os.exec(["echo test 0"]); @@ -26,25 +26,46 @@ function test_win_os_exec() { assert(pid >= 0); os.close(fds[1]); f = std.fdopen(fds[0], "r"); - var gl = f.getline(); - /* artifact of windows to c compatibility between getline and cmd.exe echo*/ - assert(gl, "hello\r"); + + /* \r artifact of windows compatibility between getline and cmd.exe echo*/ + assert(f.getline(), "hello\r"); assert(f.getline(), null); f.close(); - /* I created watchpid to at least notify if the PID is complete cross-compatibly. + /* I created watchpid to cross-compatibly notify if the PID is complete. There's no windows equivalent of waitpid, and windows would prefer Handles. watchpid returns negative error || 0 if still waiting || pid if complete. - watchpid returns no status. specify 1 in the 2nd param for blocking */ + watchpid returns no status. specify 1 in the 2nd param for blocking + watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ ret = os.watchpid(pid, 1); assert(ret, pid); + pid = os.exec(["cat"], { block: false } ); assert(pid >= 0); - /* Just does TerminateProcess. There's no signal. - Windows does signal pid groups and that takes research. */ + /* os.kill Just does TerminateProcess. There's no signal control in win32. + Windows does signal pid groups and killing a group requires research. */ + ret = os.watchpid(pid, 0); + assert(ret, 0); os.kill(pid, os.SIGTERM); os.sleep(1); ret = os.watchpid(pid, 0); assert(ret, pid); + + pid = os.exec([os.exePath(),"-q"], { block: true } ); + assert(pid, 0); + + fds = os.pipe(); + pid = os.exec([os.exePath(),"thereisno.js"], { + block: true, + stderr: fds[1], + } ); + assert(pid, 1); + os.close(fds[1]); + f = std.fdopen(fds[0], "r"); + + assert(f.getline(), "thereisno.js: No such file or directory\r"); + assert(f.getline(), null); + f.close(); + }; function test_os_exec() @@ -75,7 +96,10 @@ function test_os_exec() assert(status >> 8, 0); /* exit code */ pid = os.exec(["cat"], { block: false } ); - assert(pid >= 0); + assert(pid >= 0) + /* watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ + ret = os.watchpid(pid, 0); + assert(ret, 0); os.kill(pid, os.SIGTERM); [ret, status] = os.waitpid(pid, 0); assert(ret, pid); @@ -84,8 +108,23 @@ function test_os_exec() if (!isCygwin) { assert(status & 0x7f, os.SIGTERM); } -} + pid = os.exec([os.exePath(),"-q"], { block: true } ); + assert(pid, 0); + + fds = os.pipe(); + pid = os.exec([os.exePath(),"thereisno.js"], { + block: true, + stderr: fds[1], + } ); + assert(pid, 1); + os.close(fds[1]); + f = std.fdopen(fds[0], "r"); + assert(f.getline(), "thereisno.js: No such file or directory"); + assert(f.getline(), null); + f.close(); + +} if (!isWin) test_os_exec(); else test_win_os_exec(); From f9dbd9c9c2a4cc39bb550261e19aab000f577209 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Sat, 31 May 2025 14:53:03 -0400 Subject: [PATCH 11/31] Update quickjs-libc.c jscheck issue fixed. I did move the functions around to minimize code differences. env in win32 is one big zero-delimited buffer; I now double its size and copy on overflow. --- quickjs-libc.c | 132 +++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 81 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 7e08eb38f..c701dd7f0 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3057,73 +3057,6 @@ static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, } #endif -#if defined(_WIN32) -#define MS_ENVP_BUFFSIZE 8192 -static char *build_ms_envp(JSContext *ctx, JSValue obj) -{ - uint32_t len, i; - JSPropertyEnum *tab; - char *envp, *pair; - const char *key, *str; - JSValue val; - size_t key_len, str_len; - - if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) - return NULL; - envp = js_mallocz(ctx,MS_ENVP_BUFFSIZE); - pair = envp; - if (!envp) goto fail; - for(i = 0; i < len; i++) { - val = JS_GetProperty(ctx, obj, tab[i].atom); - if (JS_IsException(val)) - goto fail; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) goto fail; - key = JS_AtomToCString(ctx, tab[i].atom); - if (!key) { - JS_FreeCString(ctx, str); - goto fail; - } - key_len = strlen(key); - str_len = strlen(str); - /* parallel to build_envp for comparison - pair = js_malloc(ctx, key_len + str_len + 2); - if (!pair) { - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - goto fail; - }*/ - memcpy(pair, key, key_len); - pair += key_len; - pair[0] = '='; pair++; - memcpy(pair, str, str_len); - pair += str_len; - pair[0] = '\0'; - pair++; - //envp[i] = pair; - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - } - pair[0] = '\0'; - end: - for(i = 0; i < len; i++) - JS_FreeAtom(ctx, tab[i].atom); - js_free(ctx, tab); - return envp; - fail: - if (envp) { - //for(i = 0; i < len; i++) js_free(ctx, envp[i]); - js_free(ctx, envp); - envp = NULL; - } - goto end; - -}; - -#endif - #if !defined(_WIN32) && !defined(__wasi__) static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3168,20 +3101,36 @@ static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, JS_FreeCString(ctx, path); return make_string_error(ctx, buf, err); } +#endif +#if !defined(__wasi__) +#ifdef _WIN32 +#define MS_ENVP_BUFFSIZE 4096 +static char *build_envp(JSContext *ctx, JSValue obj) +{ + size_t key_len, str_len, buff_len = MS_ENVP_BUFFSIZE; + char *envp, *pair; +#else static char **build_envp(JSContext *ctx, JSValue obj) { - uint32_t len, i; - JSPropertyEnum *tab; + size_t key_len, str_len; char **envp, *pair; +#endif // WIN32 const char *key, *str; + uint32_t len, i; + JSPropertyEnum *tab; JSValue val; - size_t key_len, str_len; + if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) return NULL; +#ifdef _WIN32 + envp = js_mallocz(ctx,buff_len); + pair = envp; +#else envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); +#endif if (!envp) goto fail; for(i = 0; i < len; i++) { @@ -3199,17 +3148,31 @@ static char **build_envp(JSContext *ctx, JSValue obj) } key_len = strlen(key); str_len = strlen(str); +#ifdef _WIN32 + if (&pair[key_len + str_len + 2] > (envp + buff_len)) { + size_t buff_len2 = buff_len; + buff_len *= 2; + char *envp2 = envp; + envp = js_mallocz(ctx,buff_len); + memcpy(envp, envp2, buff_len2); + }; +#else pair = js_malloc(ctx, key_len + str_len + 2); if (!pair) { JS_FreeCString(ctx, key); JS_FreeCString(ctx, str); goto fail; } +#endif // _WIN32 memcpy(pair, key, key_len); pair[key_len] = '='; memcpy(pair + key_len + 1, str, str_len); pair[key_len + 1 + str_len] = '\0'; +#ifdef _WIN32 + pair += key_len + str_len + 2; +#else envp[i] = pair; +#endif JS_FreeCString(ctx, key); JS_FreeCString(ctx, str); } @@ -3220,14 +3183,18 @@ static char **build_envp(JSContext *ctx, JSValue obj) return envp; fail: if (envp) { +#ifndef _WIN32 for(i = 0; i < len; i++) js_free(ctx, envp[i]); +#endif // !_WIN32 js_free(ctx, envp); envp = NULL; } goto done; } +#endif // !(__wasi__) +#if !defined(_WIN32) && !defined(__wasi__) /* execvpe is not available on non GNU systems */ static int my_execvpe(const char *filename, char **argv, char **envp) { @@ -3295,9 +3262,8 @@ static void js_os_exec_once_init(void) *(void **) (&js_os_exec_closefrom) = dlsym(RTLD_DEFAULT, "closefrom"); } -#endif - -#endif // ! WIN32 ! wasi +#endif // !__wasi__ +#endif // !_WIN32 !__wasi__ #if !defined(__wasi__) /* exec(args[, options]) -> exitcode */ @@ -3310,10 +3276,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, const char *file = NULL, *str, *cwd = NULL; uint32_t exec_argc, i; int ret, pid; - bool block_flag = true, use_path = true, specified_fd = false; + bool block_flag = true, use_path = true; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; #ifdef _WIN32 + bool specified_fd = false; char cmdbuff [OS_EXEC_CMD_BUFFSIZE]; int cmdi; char *envp = 0; @@ -3396,7 +3363,9 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; std_fds[i] = fd; +#ifdef _WIN32 specified_fd = true; +#endif } } @@ -3470,7 +3439,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (JS_IsException(val)) goto exception; if (!JS_IsUndefined(val)) { - envp = build_ms_envp(ctx, val); + envp = build_envp(ctx, val); JS_FreeValue(ctx, val); if (!envp) goto exception; @@ -3756,7 +3725,7 @@ static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, CloseHandle(pipe_handles[1]); return JS_NULL; }; -#else +#else // windows prefers handles. but to stay consistent the system should use file desc pipe_fds[0] = pipe_handles[0]; pipe_fds[1] = pipe_handles[1]; #endif // WIN_OS_PIPE_USE_FDS @@ -3774,7 +3743,7 @@ static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, return obj; } -/* kill(pid, sig) */ +/* kill(pid) : works like standard c/linux kill except signal is ignored */ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3790,7 +3759,8 @@ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, return JS_NewInt32(ctx,err); } -/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ +/* watchpid(pid, blocking) -> ret < 0 = -error/ ret = 0 still waiting/ ret = pid complete + hybrid clone of waitpid that will work with both systems except there's no status */ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3811,7 +3781,8 @@ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, #endif #if !defined(_WIN32) && !defined(__wasi__) -/* watchpid(pid, blocking) -> -error/0= is still waiting/pid = complete */ +/* watchpid(pid, blocking) -> ret < 0 = -error/ ret = 0 still waiting/ ret = pid complete + hybrid clone of waitpid that will work with both systems except there's no status */ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3825,7 +3796,6 @@ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, return JS_NewInt32(ctx, -errno); } - /* getpid() -> pid */ static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -4465,7 +4435,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { JS_CFUNC_DEF("watchpid", 2, js_os_watchpid ), JS_CFUNC_DEF("pipe", 0, js_os_pipe ), JS_CFUNC_DEF("kill", 2, js_os_kill ), - #endif +#endif #if !defined(_WIN32) && !defined(__wasi__) JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), JS_CFUNC_DEF("symlink", 2, js_os_symlink ), From dc052eef20a7cb5f7eee8175e3626175994ba3d7 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Sat, 31 May 2025 15:12:47 -0400 Subject: [PATCH 12/31] Update quickjs-libc.c it's semantics. I just tested it. my compiler is not liking size_t addition to char* and for some reason even code editor seems to think it's typecasting crazy. so I'm comparing references to keep it clean enough looking. --- quickjs-libc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index c701dd7f0..5c00c9077 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3149,7 +3149,7 @@ static char **build_envp(JSContext *ctx, JSValue obj) key_len = strlen(key); str_len = strlen(str); #ifdef _WIN32 - if (&pair[key_len + str_len + 2] > (envp + buff_len)) { + if (&pair[key_len + str_len + 2] > &envp[buff_len]) { size_t buff_len2 = buff_len; buff_len *= 2; char *envp2 = envp; From 6c0db1aec777fd144859122df78a34fb812385b3 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Sat, 31 May 2025 15:34:40 -0400 Subject: [PATCH 13/31] Update quickjs-libc.c needed to free the memory when I re-cache. --- quickjs-libc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/quickjs-libc.c b/quickjs-libc.c index 5c00c9077..6d11b298b 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3155,6 +3155,7 @@ static char **build_envp(JSContext *ctx, JSValue obj) char *envp2 = envp; envp = js_mallocz(ctx,buff_len); memcpy(envp, envp2, buff_len2); + js_free(ctx, envp2); }; #else pair = js_malloc(ctx, key_len + str_len + 2); From 08183f7070dee22faa551e57b5e214ac998b60b8 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Sat, 31 May 2025 16:15:58 -0400 Subject: [PATCH 14/31] Update quickjs-libc.c tested again ... I had comments and printf functions to remove. I just keep reviewing and expanding my test environment. --- quickjs-libc.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 6d11b298b..8b0b44f18 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3504,9 +3504,6 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, }; } cmdbuff[cmdi] = 0; -#ifdef WIN32_OS_EXEC_WORKING - printf("path to exec: [%s] path:[%s]\r\n", cmdbuff, cwd); -#endif // working #endif // WIN32 @@ -3518,10 +3515,9 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, memset(&istart, 0, sizeof(istart) ); istart.cb = sizeof(istart); - if (specified_fd) { // this is also something worth tinkering with + if (specified_fd) { // this is also something worth tinkering with istart.dwFlags = STARTF_USESTDHANDLES; - //}; - + istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); if (istart.hStdInput == INVALID_HANDLE_VALUE) { JS_ThrowInternalError(ctx, "failed to associate stdin of process"); @@ -3537,13 +3533,8 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, JS_ThrowInternalError(ctx, "failed to associate stderr of process"); goto exception; }; - // */ }; -/* - printf("using handles %" PRIx64 " from %d, %" PRIx64 " from %d, %" PRIx64 " from %d\r\n", - (int64_t) istart.hStdInput, std_fds[0], (int64_t) istart.hStdOutput, std_fds[1], - (int64_t) istart.hStdError, std_fds[2]); - +/* from the windows documentation .... BOOL CreateProcessA( [in, optional] LPCSTR lpApplicationName, [in, out, optional] LPSTR lpCommandLine, @@ -3557,10 +3548,7 @@ BOOL CreateProcessA( [out] LPPROCESS_INFORMATION lpProcessInformation );*/ - int cflags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; // prevents output on stdout - - // seems like inherithandles and environ crash the return value if not perfect! - // we may also need to sim a cmd.exe chdir and a manual push with a cwd .... + int cflags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; // prevents output on stdout if (!CreateProcessA(file, cmdbuff, 0, 0, true, cflags, envp, cwd, &istart, &iproc) ) { int e = GetLastError(); From 0c1c62ac7c4ff2ba6dc216e2fb3c5b0557e73624 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Mon, 2 Jun 2025 02:55:00 -0400 Subject: [PATCH 15/31] cleaning up and updating removed the majority of flags. Only left flags that seemed necessary. I had a combined js_os_pipe with several #ifdefs inside to separate the OSs. I later restored the non-WIN32 version so that it would not be out of order from the code before. The logic was that a) the export list is maintained in the order of the function calls. and b) the code compare was less of a mess to keep that chunk intact. I realized there were useless #ifdefs and removed them. I did retest both WIN32 and GNU. --- quickjs-libc.c | 167 ++++++++++++++++++++----------------------------- 1 file changed, 69 insertions(+), 98 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 8b0b44f18..b2c1d4aef 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3104,8 +3104,8 @@ static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, #endif #if !defined(__wasi__) -#ifdef _WIN32 -#define MS_ENVP_BUFFSIZE 4096 +#if defined(_WIN32) +#define MS_ENVP_BUFFSIZE 4096 // this will automatically double as needed static char *build_envp(JSContext *ctx, JSValue obj) { size_t key_len, str_len, buff_len = MS_ENVP_BUFFSIZE; @@ -3148,7 +3148,8 @@ static char **build_envp(JSContext *ctx, JSValue obj) } key_len = strlen(key); str_len = strlen(str); -#ifdef _WIN32 +#if defined(_WIN32) + /* syntax highither AND MSVC AND ClangCl would not handle char* + size_t + size_t */ if (&pair[key_len + str_len + 2] > &envp[buff_len]) { size_t buff_len2 = buff_len; buff_len *= 2; @@ -3169,7 +3170,7 @@ static char **build_envp(JSContext *ctx, JSValue obj) pair[key_len] = '='; memcpy(pair + key_len + 1, str, str_len); pair[key_len + 1 + str_len] = '\0'; -#ifdef _WIN32 +#if defined(_WIN32) pair += key_len + str_len + 2; #else envp[i] = pair; @@ -3184,7 +3185,7 @@ static char **build_envp(JSContext *ctx, JSValue obj) return envp; fail: if (envp) { -#ifndef _WIN32 +#if !defined(_WIN32) for(i = 0; i < len; i++) js_free(ctx, envp[i]); #endif // !_WIN32 @@ -3268,7 +3269,6 @@ static void js_os_exec_once_init(void) #if !defined(__wasi__) /* exec(args[, options]) -> exitcode */ -#define OS_EXEC_CMD_BUFFSIZE 2048 static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3280,10 +3280,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, bool block_flag = true, use_path = true; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; -#ifdef _WIN32 +#if defined(_WIN32) bool specified_fd = false; - char cmdbuff [OS_EXEC_CMD_BUFFSIZE]; - int cmdi; +#define OS_EXEC_CMD_BUFFSIZE 2048 + char cmd_buff [OS_EXEC_CMD_BUFFSIZE]; + int cmd_ind, cmd_strlen;; char *envp = 0; #else char **envp = environ; @@ -3364,13 +3365,13 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; std_fds[i] = fd; -#ifdef _WIN32 +#if defined(_WIN32) specified_fd = true; #endif } } -#ifndef _WIN32 +#if !defined(_WIN32) val = JS_GetPropertyStr(ctx, options, "env"); if (JS_IsException(val)) goto exception; @@ -3445,32 +3446,17 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (!envp) goto exception; } - - } - cmdi = 0; int cmdsl; - /* uncomment this to put cwd into the cmdline - if (cwd) { - cmdsl = strlen(cwd); - if (cmdsl) { - strncpy(&cmdbuff[cmdi], cwd, cmdsl); - cmdi += cmdsl; - if (cmdbuff[cmdi - 1] != '\\') { - cmdbuff[cmdi] = '\\'; - cmdi++; - }; - }; - }; - */ - if (file) cmdsl = strlen(file); - else cmdsl = 0; - if (cmdsl) { - strncpy(&cmdbuff[cmdi], file, cmdsl); - cmdi += cmdsl; + cmd_ind = 0; cmd_strlen = 0; + if (file) cmd_strlen = strlen(file); + else cmd_strlen = 0; + if (cmd_strlen) { + strncpy(&cmd_buff[cmd_ind], file, cmd_strlen); + cmd_ind += cmd_strlen; } else { - strncpy(&cmdbuff[cmdi], "cmd /C ", 7); - cmdi += 7; + strncpy(&cmd_buff[cmd_ind], "cmd /C ", 7); + cmd_ind += 7; }; val = JS_GetPropertyStr(ctx, args, "length"); @@ -3489,33 +3475,32 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, str = JS_ToCString(ctx, val); JS_FreeValue(ctx, val); if (!str) goto exception; - cmdsl = strlen(str); - if (cmdsl) { + cmd_strlen = strlen(str); + if (cmd_strlen) { if ((i != 0) || (file != 0)) { - cmdbuff[cmdi] = ' '; - cmdi++; + cmd_buff[cmd_ind] = ' '; + cmd_ind++; }; - if (OS_EXEC_CMD_BUFFSIZE < (cmdi + cmdsl + 1)) { + if (OS_EXEC_CMD_BUFFSIZE < (cmd_ind + cmd_strlen + 1)) { JS_ThrowRangeError(ctx, "exec command line too long."); goto exception; }; - strncpy(&cmdbuff[cmdi], str, cmdsl); - cmdi += cmdsl; + strncpy(&cmd_buff[cmd_ind], str, cmd_strlen); + cmd_ind += cmd_strlen; }; } - cmdbuff[cmdi] = 0; + cmd_buff[cmd_ind] = 0; #endif // WIN32 -#ifdef WIN32 +#if defined(_WIN32) STARTUPINFO istart; PROCESS_INFORMATION iproc; - // memset was absolutely necessary. I kept getting deeper system crashes memset(&iproc, 0, sizeof(iproc)); memset(&istart, 0, sizeof(istart) ); istart.cb = sizeof(istart); - if (specified_fd) { // this is also something worth tinkering with + if (specified_fd) { istart.dwFlags = STARTF_USESTDHANDLES; istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); @@ -3534,22 +3519,9 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, goto exception; }; }; -/* from the windows documentation .... -BOOL CreateProcessA( - [in, optional] LPCSTR lpApplicationName, - [in, out, optional] LPSTR lpCommandLine, - [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, - [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, - [in] BOOL bInheritHandles, - [in] DWORD dwCreationFlags, - [in, optional] LPVOID lpEnvironment, - [in, optional] LPCSTR lpCurrentDirectory, - [in] LPSTARTUPINFOA lpStartupInfo, - [out] LPPROCESS_INFORMATION lpProcessInformation -);*/ int cflags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; // prevents output on stdout - if (!CreateProcessA(file, cmdbuff, 0, 0, true, cflags, envp, cwd, &istart, &iproc) ) + if (!CreateProcessA(file, cmd_buff, 0, 0, true, cflags, envp, cwd, &istart, &iproc) ) { int e = GetLastError(); JS_ThrowInternalError(ctx, "failed to start process with error: %d", e); @@ -3661,7 +3633,7 @@ BOOL CreateProcessA( done: JS_FreeCString(ctx, file); JS_FreeCString(ctx, cwd); -#ifndef _WIN32 +#if !defined(_WIN32) for(i = 0; i < exec_argc; i++) JS_FreeCString(ctx, exec_argv[i]); js_free(ctx, exec_argv); @@ -3685,43 +3657,32 @@ BOOL CreateProcessA( } #endif -#ifdef _WIN32 +#if defined(_WIN32) /* pipe() -> [read_fd, write_fd] or null if error */ static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int pipe_fds[2], ret; JSValue obj; -#ifdef _WIN32 -#define PIPE_BUFFSIZE 4096 -#define WIN_PIPE_SPECIALFLAGS 0 -#define WIN_OS_PIPE_USE_FDS HANDLE pipe_handles[2]; struct _SECURITY_ATTRIBUTES secur; secur.nLength = sizeof(secur); secur.lpSecurityDescriptor = 0; secur.bInheritHandle = true; - ret = CreatePipe(&pipe_handles[0], &pipe_handles[1], &secur, PIPE_BUFFSIZE); - if (ret == 0) return JS_NULL; -#ifdef WIN_OS_PIPE_USE_FDS - pipe_fds[0] = _open_osfhandle( (intptr_t) pipe_handles[0], WIN_PIPE_SPECIALFLAGS ); +#define WIN32_PIPE_BUFFSIZE 4096 + ret = CreatePipe(&pipe_handles[0], &pipe_handles[1], &secur, WIN32_PIPE_BUFFSIZE); + if (ret == 0) + return JS_NULL; + pipe_fds[0] = _open_osfhandle( (intptr_t) pipe_handles[0], 0 ); if (pipe_fds[0] == -1 ) { CloseHandle(pipe_handles[0]); return JS_NULL; - }; - pipe_fds[1] = _open_osfhandle( (intptr_t) pipe_handles[1], WIN_PIPE_SPECIALFLAGS ); + } + pipe_fds[1] = _open_osfhandle( (intptr_t) pipe_handles[1], 0 ); if (pipe_fds[1] == -1 ) { CloseHandle(pipe_handles[1]); return JS_NULL; - }; -#else // windows prefers handles. but to stay consistent the system should use file desc - pipe_fds[0] = pipe_handles[0]; - pipe_fds[1] = pipe_handles[1]; -#endif // WIN_OS_PIPE_USE_FDS -#else - ret = pipe(pipe_fds); - if (ret < 0) return JS_NULL; -#endif // _WIN32 + } obj = JS_NewArray(ctx); if (JS_IsException(obj)) return obj; @@ -3732,7 +3693,7 @@ static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, return obj; } -/* kill(pid) : works like standard c/linux kill except signal is ignored */ +/* (NULL | err) kill(pid) : works like standard c/linux kill except signal is ignored */ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3742,14 +3703,16 @@ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; ph = OpenProcess(flags, false, (DWORD) pid); - if (!ph) return JS_NewInt32(ctx, GetLastError()); - if (TerminateProcess(ph, 0)) return JS_NULL; + if (!ph) + return JS_NewInt32(ctx, GetLastError()); + if (TerminateProcess(ph, 0)) + return JS_NewInt32(ctx, 0); int err = GetLastError(); return JS_NewInt32(ctx,err); } -/* watchpid(pid, blocking) -> ret < 0 = -error/ ret = 0 still waiting/ ret = pid complete - hybrid clone of waitpid that will work with both systems except there's no status */ +/* watchpid(pid, blocking) -> ret: < 0 = -error/ ret: = 0 still waiting/ ret: = pid complete + hybrid clone of waitpid that will work with Win32 + GNU systems except there's no status */ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3757,31 +3720,39 @@ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, HANDLE ph; int block = 0, ret; DWORD options = 0, flags = PROCESS_QUERY_INFORMATION | SYNCHRONIZE; - if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; - if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; - if (block==1) options = INFINITE; + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if ( (argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) + return JS_EXCEPTION; + if (block==1) + options = INFINITE; ph = OpenProcess(flags, false, (DWORD) pid); - if (!ph) return JS_NewInt32(ctx, -GetLastError()); + if (!ph) + return JS_NewInt32(ctx, -GetLastError()); ret = WaitForSingleObject((HANDLE) ph, options); - if (ret == WAIT_TIMEOUT) return JS_NewInt32(ctx, 0); // timed out - if (ret != 0) return JS_NewInt32(ctx, -GetLastError()); + if (ret == WAIT_TIMEOUT) + return JS_NewInt32(ctx, 0); // timed out + if (ret != 0) + return JS_NewInt32(ctx, -GetLastError()); return JS_NewInt32(ctx, pid); } -#endif +#endif // _WIN32 #if !defined(_WIN32) && !defined(__wasi__) -/* watchpid(pid, blocking) -> ret < 0 = -error/ ret = 0 still waiting/ ret = pid complete - hybrid clone of waitpid that will work with both systems except there's no status */ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int pid, status, block = 0, options = 0, ret; - if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; - if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) return JS_EXCEPTION; + if (JS_ToInt32(ctx, &pid, argv[0])) + return JS_EXCEPTION; + if ((argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) + return JS_EXCEPTION; if (!block) options = WNOHANG; ret = waitpid(pid, &status, options); - if (ret == 0) return JS_NewInt32(ctx, 0); - if (ret==pid) return JS_NewInt32(ctx, pid); + if (ret == 0) + return JS_NewInt32(ctx, 0); + if (ret==pid) + return JS_NewInt32(ctx, pid); return JS_NewInt32(ctx, -errno); } From 714db530280fa4f6ae6c94a5b6c8bf0362f26762 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Mon, 2 Jun 2025 03:26:45 -0400 Subject: [PATCH 16/31] Update quickjs-libc.c I think I got everything! mostly comments and reordering of if statements. I'm not the only programmer in the world to put my then branches on the same line following an if statement. But I'm genX, I used 640x480 CRTs. It looks clearer, I do admit that. But this is definitely a 21st century practice, in the old days we couldn't afford the Tabs or the printer paper. --- quickjs-libc.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index b2c1d4aef..835f25457 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3105,9 +3105,9 @@ static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, #if !defined(__wasi__) #if defined(_WIN32) -#define MS_ENVP_BUFFSIZE 4096 // this will automatically double as needed static char *build_envp(JSContext *ctx, JSValue obj) { +#define MS_ENVP_BUFFSIZE 4096 // this does automatically double as needed size_t key_len, str_len, buff_len = MS_ENVP_BUFFSIZE; char *envp, *pair; #else @@ -3448,9 +3448,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, } } - cmd_ind = 0; cmd_strlen = 0; - if (file) cmd_strlen = strlen(file); - else cmd_strlen = 0; + cmd_ind = 0; + if (file) + cmd_strlen = strlen(file); + else + cmd_strlen = 0; if (cmd_strlen) { strncpy(&cmd_buff[cmd_ind], file, cmd_strlen); cmd_ind += cmd_strlen; @@ -3460,21 +3462,25 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, }; val = JS_GetPropertyStr(ctx, args, "length"); - if (JS_IsException(val)) goto exception; + if (JS_IsException(val)) + goto exception; ret = JS_ToUint32(ctx, &exec_argc, val); JS_FreeValue(ctx, val); - if (ret) goto exception; + if (ret) + goto exception; /* arbitrary limit to avoid overflow */ if (exec_argc < 1 || exec_argc > 65535) { JS_ThrowTypeError(ctx, "invalid number of arguments"); goto exception; } - for(i = 0; i < exec_argc; i++) { + for (i = 0; i < exec_argc; i++) { val = JS_GetPropertyUint32(ctx, args, i); - if (JS_IsException(val)) goto exception; + if (JS_IsException(val)) + goto exception; str = JS_ToCString(ctx, val); JS_FreeValue(ctx, val); - if (!str) goto exception; + if (!str) + goto exception; cmd_strlen = strlen(str); if (cmd_strlen) { if ((i != 0) || (file != 0)) { @@ -3532,7 +3538,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, pid = iproc.dwProcessId; HANDLE phandle = iproc.hProcess; HANDLE thandle = iproc.hThread; - int besafe = 0; + int besafe = 0; if (block_flag) { for(;;) { int dwait = WaitForSingleObject(phandle, INFINITE); @@ -3543,7 +3549,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, break; } else { besafe++; if (besafe==20) { - JS_ThrowPlainError(ctx, "exec process did not complete."); + JS_ThrowPlainError(ctx, "exec process did not complete after %d iterations.", besafe); goto exception; }; }; @@ -3552,7 +3558,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, CloseHandle(thandle); } else { excode = (int32_t) iproc.dwProcessId; - ret_val = JS_NewInt32(ctx, excode); //(int64_t) phandle); + ret_val = JS_NewInt32(ctx, excode); } #else // _WIN32 @@ -3693,7 +3699,7 @@ static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, return obj; } -/* (NULL | err) kill(pid) : works like standard c/linux kill except signal is ignored */ +/* kill(pid) -> (NULL | err) : works like standard c/linux kill except signal is ignored */ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -3711,7 +3717,7 @@ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, return JS_NewInt32(ctx,err); } -/* watchpid(pid, blocking) -> ret: < 0 = -error/ ret: = 0 still waiting/ ret: = pid complete +/* watchpid(pid, blocking) -> ret: < 0 = -error | ret: = 0 still waiting | ret: = pid complete hybrid clone of waitpid that will work with Win32 + GNU systems except there's no status */ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) From 9735e01fd969d9df4b9a6ad30388c0a3533c6317 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Mon, 2 Jun 2025 03:55:03 -0400 Subject: [PATCH 17/31] Update test_os_exec.js nothing functional, just tweaking the comments. I'm not happy leaving this undocumented function in the wild. and it's trivial! but there it is! it at least deserved 2 lines of description. It is more concise now .... just like Bellard would have wanted ... tidy and such. --- tests/test_os_exec.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index 0d5a876aa..a5b4798b0 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -27,22 +27,17 @@ function test_win_os_exec() { os.close(fds[1]); f = std.fdopen(fds[0], "r"); - /* \r artifact of windows compatibility between getline and cmd.exe echo*/ assert(f.getline(), "hello\r"); assert(f.getline(), null); f.close(); - /* I created watchpid to cross-compatibly notify if the PID is complete. - There's no windows equivalent of waitpid, and windows would prefer Handles. - watchpid returns negative error || 0 if still waiting || pid if complete. - watchpid returns no status. specify 1 in the 2nd param for blocking + /* watchpid is similar waitpid on WIN32 + GNU but lacks a status indicator watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ ret = os.watchpid(pid, 1); assert(ret, pid); pid = os.exec(["cat"], { block: false } ); assert(pid >= 0); - /* os.kill Just does TerminateProcess. There's no signal control in win32. - Windows does signal pid groups and killing a group requires research. */ + /* os.kill in WIN32 Just does TerminateProcess. There's no signal control in win32. */ ret = os.watchpid(pid, 0); assert(ret, 0); os.kill(pid, os.SIGTERM); From 688707eacbdb7a9219b82b0daade51ee5d00b2cc Mon Sep 17 00:00:00 2001 From: woodtygr Date: Mon, 2 Jun 2025 11:42:04 -0400 Subject: [PATCH 18/31] Update quickjs-libc.c PROCESS_QUERY_LIMITED_INFORMATION --- quickjs-libc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 835f25457..4d747d34d 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3725,7 +3725,7 @@ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, int pid; HANDLE ph; int block = 0, ret; - DWORD options = 0, flags = PROCESS_QUERY_INFORMATION | SYNCHRONIZE; + DWORD options = 0, flags = PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE; if (JS_ToInt32(ctx, &pid, argv[0])) return JS_EXCEPTION; if ( (argc > 1) && (JS_ToInt32(ctx, &block, argv[1]))) @@ -3736,6 +3736,7 @@ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, if (!ph) return JS_NewInt32(ctx, -GetLastError()); ret = WaitForSingleObject((HANDLE) ph, options); + CloseHandle(ph); if (ret == WAIT_TIMEOUT) return JS_NewInt32(ctx, 0); // timed out if (ret != 0) From 9943d5cd237e594d6d2bd5b045bdad11cfcc164c Mon Sep 17 00:00:00 2001 From: woodtygr Date: Mon, 2 Jun 2025 13:40:04 -0400 Subject: [PATCH 19/31] Update quickjs-libc.c CloseHandle after OpenProcess. The documentation wasn't so clear, but this works as it should, and I read that it wastes resources not to. Don't close HNDs to FDs, let them get closed by the user. But close HNDs from PIDs. Besides, this is a kill and an exit return value. It's stopped! --- quickjs-libc.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 4d747d34d..b138252c9 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3704,6 +3704,7 @@ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int pid; + BOOL ret; HANDLE ph; DWORD flags = PROCESS_TERMINATE; if (JS_ToInt32(ctx, &pid, argv[0])) @@ -3711,12 +3712,15 @@ static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, ph = OpenProcess(flags, false, (DWORD) pid); if (!ph) return JS_NewInt32(ctx, GetLastError()); - if (TerminateProcess(ph, 0)) - return JS_NewInt32(ctx, 0); + ret = TerminateProcess(ph, 0); + CloseHandle(ph); + if (ret) + JS_NewInt32(ctx, 0); int err = GetLastError(); return JS_NewInt32(ctx,err); } + /* watchpid(pid, blocking) -> ret: < 0 = -error | ret: = 0 still waiting | ret: = pid complete hybrid clone of waitpid that will work with Win32 + GNU systems except there's no status */ static JSValue js_os_watchpid(JSContext *ctx, JSValueConst this_val, From c626450a2e3415d021e95a604e211afad2df027b Mon Sep 17 00:00:00 2001 From: woodtygr Date: Tue, 3 Jun 2025 10:22:34 -0400 Subject: [PATCH 20/31] Update test_os_exec.js recombined the functions tested against: (linux) true exit 2 bad command -> 127 good command ->0 watchpid blocking environment inherited pipe waitpid blocking watchpid nonblocking pid running kill watchpid nonblocking pid ended waitpid WNOHANG I have uncovered that linux echoes to the stdout and I am going to revert my flags for win32, but that change isn't tested yet in this file. --- tests/test_os_exec.js | 142 ++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 81 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index a5b4798b0..5d26ceb74 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -5,121 +5,101 @@ import { assert } from "./assert.js"; const isWin = os.platform === 'win32'; const isCygwin = os.platform === 'cygwin'; -function test_win_os_exec() { - var ret, fds, pid, f, status; +function test_os_exec() { + var ret, fds, pid, f, status, amend, exe; - ret = os.exec(["bad command or filename @@"]); - assert(ret, 1); + // cmd.exe does leave a \r that getline doesn't trim + const osTrimLine = ( isWin ? "\r" : "" ); + const osShellCmd = ( isWin ? "cmd" : "/bin/sh" ); + const osShellFlag = ( isWin ? "/c" : "-c" ); + const osPathSeparator = ( isWin ? "\\" : "/" ); - ret = os.exec(["echo test 0"]); - assert(ret, 0); - - ret = os.exec(["cmd.exe", "/c", "exit 2"], { usePath: false }); - assert(ret, 2); - - fds = os.pipe(); - pid = os.exec(["echo %FOO%"], { - stdout: fds[1], - block: false, - env: { FOO: "hello" }, - } ); - assert(pid >= 0); - os.close(fds[1]); - f = std.fdopen(fds[0], "r"); - - assert(f.getline(), "hello\r"); - assert(f.getline(), null); - f.close(); - /* watchpid is similar waitpid on WIN32 + GNU but lacks a status indicator - watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ - ret = os.watchpid(pid, 1); - assert(ret, pid); - - pid = os.exec(["cat"], { block: false } ); - assert(pid >= 0); - /* os.kill in WIN32 Just does TerminateProcess. There's no signal control in win32. */ - ret = os.watchpid(pid, 0); - assert(ret, 0); - os.kill(pid, os.SIGTERM); - os.sleep(1); - ret = os.watchpid(pid, 0); - assert(ret, pid); - - pid = os.exec([os.exePath(),"-q"], { block: true } ); - assert(pid, 0); + if (!isWin) { - fds = os.pipe(); - pid = os.exec([os.exePath(),"thereisno.js"], { - block: true, - stderr: fds[1], - } ); - assert(pid, 1); - os.close(fds[1]); - f = std.fdopen(fds[0], "r"); - - assert(f.getline(), "thereisno.js: No such file or directory\r"); - assert(f.getline(), null); - f.close(); + ret = os.exec( ["true"] ); + assert(ret, 0); + } -}; + ret = os.exec( [osShellCmd, osShellFlag, "exit 2"], { usePath: false } ); + assert(ret, 2); -function test_os_exec() -{ - var ret, fds, pid, f, status; + ret = os.exec( ["bad command or filename @@"], ); + assert( ret, ( isWin ? 1 : 127 ) ); - ret = os.exec(["true"]); + ret = os.exec( [osShellCmd, osShellFlag, ": good commands return 0"] ); assert(ret, 0); - ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false }); - assert(ret, 1); + pid = os.exec( [osShellCmd, osShellFlag, ":"], { block: false} ); + /* watchpid is similar waitpid on WIN32 + GNU but lacks a status indicator + watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ + os.sleep(0); // watcpid is too fast! you have to sleep or check it again. + ret = os.watchpid(pid, 1); + assert(ret, pid); + const osShellEchoParam = ( isWin ? 'echo %FOO%' : 'echo $FOO' ); fds = os.pipe(); - pid = os.exec(["sh", "-c", "echo $FOO"], { + pid = os.exec( [osShellCmd, osShellFlag, osShellEchoParam ], { stdout: fds[1], block: false, env: { FOO: "hello" }, } ); assert(pid >= 0); - os.close(fds[1]); /* close the write end (as it is only in the child) */ + os.close(fds[1]); f = std.fdopen(fds[0], "r"); - assert(f.getline(), "hello"); + assert(f.getline(), "hello" + osTrimLine); assert(f.getline(), null); f.close(); - [ret, status] = os.waitpid(pid, 0); - assert(ret, pid); - assert(status & 0x7f, 0); /* exited */ - assert(status >> 8, 0); /* exit code */ + if (!isWin) { + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + assert(status & 0x7f, 0); /* exited */ + assert(status >> 8, 0); /* exit code */ + } pid = os.exec(["cat"], { block: false } ); - assert(pid >= 0) - /* watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ + assert(pid >= 0); ret = os.watchpid(pid, 0); assert(ret, 0); + /* os.kill in WIN32 Just does TerminateProcess & signal control is ignored. */ os.kill(pid, os.SIGTERM); - [ret, status] = os.waitpid(pid, 0); - assert(ret, pid); - // Flaky on cygwin for unclear reasons, see - // https://github.com/quickjs-ng/quickjs/issues/184 - if (!isCygwin) { - assert(status & 0x7f, os.SIGTERM); + os.sleep(0); + ret = os.watchpid(pid, 1); + assert(ret, pid); + + if (!isWin) { + pid = os.exec(["cat"], { block: false } ); + assert(pid >= 0); + [ret, status] = os.waitpid(pid, os.WNOHANG); + assert(ret, 0); + os.kill(pid, os.SIGTERM); + [ret, status] = os.waitpid(pid, 0); + assert(ret, pid); + // Flaky on cygwin for unclear reasons, see + // https://github.com/quickjs-ng/quickjs/issues/184 + if (!isCygwin) { + assert(status & 0x7f, os.SIGTERM); + } } - pid = os.exec([os.exePath(),"-q"], { block: true } ); - assert(pid, 0); + amend = os.exePath().split(osPathSeparator); + amend.pop(); + amend.push("qjs"); + exe = ( isWin ? amend.join("\\") : amend.join("/") ); + pid = os.exec( [ exe,"-q"], { block: true } ); + assert(pid, 0); fds = os.pipe(); - pid = os.exec([os.exePath(),"thereisno.js"], { + pid = os.exec( [exe,"thereisno.js"], { block: true, stderr: fds[1], } ); assert(pid, 1); os.close(fds[1]); f = std.fdopen(fds[0], "r"); - assert(f.getline(), "thereisno.js: No such file or directory"); + assert(f.getline(), "thereisno.js: No such file or directory" + osTrimLine); assert(f.getline(), null); f.close(); -} +}; -if (!isWin) test_os_exec(); -else test_win_os_exec(); +test_os_exec(); From 09c6620fb41d25752a667485cd06fe260eb71e8d Mon Sep 17 00:00:00 2001 From: woodtygr Date: Tue, 3 Jun 2025 10:29:57 -0400 Subject: [PATCH 21/31] Update test_os_exec.js don't sleep on blocking, moved comment too --- tests/test_os_exec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index 5d26ceb74..509777a1d 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -32,7 +32,6 @@ function test_os_exec() { pid = os.exec( [osShellCmd, osShellFlag, ":"], { block: false} ); /* watchpid is similar waitpid on WIN32 + GNU but lacks a status indicator watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ - os.sleep(0); // watcpid is too fast! you have to sleep or check it again. ret = os.watchpid(pid, 1); assert(ret, pid); @@ -62,7 +61,7 @@ function test_os_exec() { assert(ret, 0); /* os.kill in WIN32 Just does TerminateProcess & signal control is ignored. */ os.kill(pid, os.SIGTERM); - os.sleep(0); + os.sleep(0); // watcpid is too fast! you have to sleep or check it again. ret = os.watchpid(pid, 1); assert(ret, pid); From 81ad3e95cd05c038ba50f698d448b409616e4372 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Tue, 3 Jun 2025 17:31:10 -0400 Subject: [PATCH 22/31] Update quickjs-libc.c "shell commands come through" split the os_exec function back in half between the 2 versions. fixed the way handles inherit and text buffers FROM SHELLS! text does not buffer from system commands, especially bad ones. Error messages are not the same. verifying 7 permutations are the same between the two OSs --- quickjs-libc.c | 342 ++++++++++++++++++++++++++++--------------------- 1 file changed, 195 insertions(+), 147 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index b138252c9..52d274c71 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3264,10 +3264,8 @@ static void js_os_exec_once_init(void) *(void **) (&js_os_exec_closefrom) = dlsym(RTLD_DEFAULT, "closefrom"); } -#endif // !__wasi__ -#endif // !_WIN32 !__wasi__ +#endif // !defined(EMSCRIPTEN) && !defined(__wasi__) -#if !defined(__wasi__) /* exec(args[, options]) -> exitcode */ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -3280,13 +3278,6 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, bool block_flag = true, use_path = true; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; -#if defined(_WIN32) - bool specified_fd = false; -#define OS_EXEC_CMD_BUFFSIZE 2048 - char cmd_buff [OS_EXEC_CMD_BUFFSIZE]; - int cmd_ind, cmd_strlen;; - char *envp = 0; -#else char **envp = environ; const char **exec_argv; int status; @@ -3319,7 +3310,6 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, exec_argv[i] = str; } exec_argv[exec_argc] = NULL; -#endif for(i = 0; i < 3; i++) std_fds[i] = i; @@ -3365,13 +3355,9 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; std_fds[i] = fd; -#if defined(_WIN32) - specified_fd = true; -#endif } } -#if !defined(_WIN32) val = JS_GetPropertyStr(ctx, options, "env"); if (JS_IsException(val)) goto exception; @@ -3436,132 +3422,6 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, } } -#else - val = JS_GetPropertyStr(ctx, options, "env"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsUndefined(val)) { - envp = build_envp(ctx, val); - JS_FreeValue(ctx, val); - if (!envp) - goto exception; - } - } - - cmd_ind = 0; - if (file) - cmd_strlen = strlen(file); - else - cmd_strlen = 0; - if (cmd_strlen) { - strncpy(&cmd_buff[cmd_ind], file, cmd_strlen); - cmd_ind += cmd_strlen; - } else { - strncpy(&cmd_buff[cmd_ind], "cmd /C ", 7); - cmd_ind += 7; - }; - - val = JS_GetPropertyStr(ctx, args, "length"); - if (JS_IsException(val)) - goto exception; - ret = JS_ToUint32(ctx, &exec_argc, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - /* arbitrary limit to avoid overflow */ - if (exec_argc < 1 || exec_argc > 65535) { - JS_ThrowTypeError(ctx, "invalid number of arguments"); - goto exception; - } - for (i = 0; i < exec_argc; i++) { - val = JS_GetPropertyUint32(ctx, args, i); - if (JS_IsException(val)) - goto exception; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto exception; - cmd_strlen = strlen(str); - if (cmd_strlen) { - if ((i != 0) || (file != 0)) { - cmd_buff[cmd_ind] = ' '; - cmd_ind++; - }; - if (OS_EXEC_CMD_BUFFSIZE < (cmd_ind + cmd_strlen + 1)) { - JS_ThrowRangeError(ctx, "exec command line too long."); - goto exception; - }; - strncpy(&cmd_buff[cmd_ind], str, cmd_strlen); - cmd_ind += cmd_strlen; - }; - } - cmd_buff[cmd_ind] = 0; - -#endif // WIN32 - -#if defined(_WIN32) - STARTUPINFO istart; - PROCESS_INFORMATION iproc; - memset(&iproc, 0, sizeof(iproc)); - memset(&istart, 0, sizeof(istart) ); - istart.cb = sizeof(istart); - - if (specified_fd) { - istart.dwFlags = STARTF_USESTDHANDLES; - - istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); - if (istart.hStdInput == INVALID_HANDLE_VALUE) { - JS_ThrowInternalError(ctx, "failed to associate stdin of process"); - goto exception; - }; - istart.hStdOutput = (HANDLE) _get_osfhandle( std_fds[1] ); - if (istart.hStdOutput == INVALID_HANDLE_VALUE) { - JS_ThrowInternalError(ctx, "failed to associate stdout of process"); - goto exception; - }; - istart.hStdError = (HANDLE) _get_osfhandle( std_fds[2] ); - if (istart.hStdError == INVALID_HANDLE_VALUE) { - JS_ThrowInternalError(ctx, "failed to associate stderr of process"); - goto exception; - }; - }; - - int cflags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; // prevents output on stdout - if (!CreateProcessA(file, cmd_buff, 0, 0, true, cflags, envp, cwd, &istart, &iproc) ) - { - int e = GetLastError(); - JS_ThrowInternalError(ctx, "failed to start process with error: %d", e); - goto exception; - }; - - int32_t excode = 0; - pid = iproc.dwProcessId; - HANDLE phandle = iproc.hProcess; - HANDLE thandle = iproc.hThread; - int besafe = 0; - if (block_flag) { - for(;;) { - int dwait = WaitForSingleObject(phandle, INFINITE); - if (dwait == WAIT_OBJECT_0) { - GetExitCodeProcess (phandle, (DWORD*) &excode); - ret_val = JS_NewInt32(ctx, (int32_t) excode); - excode = GetLastError(); - break; - } else { - besafe++; if (besafe==20) { - JS_ThrowPlainError(ctx, "exec process did not complete after %d iterations.", besafe); - goto exception; - }; - }; - }; - CloseHandle(phandle); - CloseHandle(thandle); - } else { - excode = (int32_t) iproc.dwProcessId; - ret_val = JS_NewInt32(ctx, excode); - } - -#else // _WIN32 #if !defined(EMSCRIPTEN) && !defined(__wasi__) // should happen pre-fork because it calls dlsym() @@ -3635,11 +3495,9 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, ret = pid; } ret_val = JS_NewInt32(ctx, ret); -#endif // !_WIN32 done: JS_FreeCString(ctx, file); JS_FreeCString(ctx, cwd); -#if !defined(_WIN32) for(i = 0; i < exec_argc; i++) JS_FreeCString(ctx, exec_argv[i]); js_free(ctx, exec_argv); @@ -3653,17 +3511,207 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, js_free(ctx, envp); } return ret_val; -#else + exception: + ret_val = JS_EXCEPTION; + goto done; +} +#endif + +#if defined(_WIN32) +/* exec(args[, options]) -> exitcode */ +static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValueConst options, args = argv[0]; + JSValue val, ret_val; + const char *file = NULL, *str, *cwd = NULL; + uint32_t exec_argc, i, besafe; + int32_t excode, dwait; + int ret, pid; + bool block_flag = true, use_path = true; + static const char *std_name[3] = { "stdin", "stdout", "stderr" }; + int std_fds[3]; + bool specified_fd = false; +#define OS_EXEC_CMD_BUFFSIZE 2048 + char cmd_buff [OS_EXEC_CMD_BUFFSIZE]; + int cmd_ind, cmd_strlen;; + char *envp = 0; + + for(i = 0; i < 3; i++) + std_fds[i] = i; + + /* get the options, if any */ + if (argc >= 2) { + options = argv[1]; + + if (get_bool_option(ctx, &block_flag, options, "block")) + goto exception; + if (get_bool_option(ctx, &use_path, options, "usePath")) + goto exception; + + val = JS_GetPropertyStr(ctx, options, "file"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + file = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!file) + goto exception; + } + + val = JS_GetPropertyStr(ctx, options, "cwd"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + cwd = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!cwd) + goto exception; + } + + /* stdin/stdout/stderr handles */ + for(i = 0; i < 3; i++) { + val = JS_GetPropertyStr(ctx, options, std_name[i]); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + int fd; + ret = JS_ToInt32(ctx, &fd, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + std_fds[i] = fd; + specified_fd = true; + } + } + + val = JS_GetPropertyStr(ctx, options, "env"); + if (JS_IsException(val)) + goto exception; + if (!JS_IsUndefined(val)) { + envp = build_envp(ctx, val); + JS_FreeValue(ctx, val); + if (!envp) + goto exception; + } + } + + cmd_ind = 0; + if (file) + cmd_strlen = strlen(file); + else + cmd_strlen = 0; + if (cmd_strlen) { + strncpy(&cmd_buff[cmd_ind], file, cmd_strlen); + cmd_ind += cmd_strlen; + } else { + //strncpy(&cmd_buff[cmd_ind], "cmd /C ", 7); + //cmd_ind += 7; + }; + + val = JS_GetPropertyStr(ctx, args, "length"); + if (JS_IsException(val)) + goto exception; + ret = JS_ToUint32(ctx, &exec_argc, val); + JS_FreeValue(ctx, val); + if (ret) + goto exception; + /* arbitrary limit to avoid overflow */ + if (exec_argc < 1 || exec_argc > 65535) { + JS_ThrowTypeError(ctx, "invalid number of arguments"); + goto exception; + } + for (i = 0; i < exec_argc; i++) { + val = JS_GetPropertyUint32(ctx, args, i); + if (JS_IsException(val)) + goto exception; + str = JS_ToCString(ctx, val); + JS_FreeValue(ctx, val); + if (!str) + goto exception; + cmd_strlen = strlen(str); + if (cmd_strlen) { + if ((i != 0) || (file != 0)) { + cmd_buff[cmd_ind] = ' '; + cmd_ind++; + }; + if (OS_EXEC_CMD_BUFFSIZE < (cmd_ind + cmd_strlen + 1)) { + JS_ThrowRangeError(ctx, "exec command line too long."); + goto exception; + }; + strncpy(&cmd_buff[cmd_ind], str, cmd_strlen); + cmd_ind += cmd_strlen; + }; + } + cmd_buff[cmd_ind] = 0; + + int cflags = NORMAL_PRIORITY_CLASS; + STARTUPINFO istart; + PROCESS_INFORMATION iproc; + memset(&iproc, 0, sizeof(iproc)); + memset(&istart, 0, sizeof(istart) ); + istart.cb = sizeof(istart); + + if (specified_fd) + { + istart.dwFlags = STARTF_USESTDHANDLES; + cflags |= CREATE_NO_WINDOW; + }; + istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); + if (istart.hStdInput == INVALID_HANDLE_VALUE) + { + JS_ThrowInternalError(ctx, "failed to associate stdin of process"); + goto exception; + }; + istart.hStdOutput = (HANDLE) _get_osfhandle( std_fds[1] ); + if (istart.hStdOutput == INVALID_HANDLE_VALUE) + { + JS_ThrowInternalError(ctx, "failed to associate stdout of process"); + goto exception; + }; + istart.hStdError = (HANDLE) _get_osfhandle( std_fds[2] ); + if (istart.hStdError == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stderr of process"); + goto exception; + }; + + besafe = excode = 0; + if (!CreateProcessA(file, cmd_buff, 0, 0, specified_fd, cflags, envp, cwd, &istart, &iproc) ) { + excode = GetLastError(); + ret_val = JS_NewInt32(ctx, excode); + } else if (block_flag) { + while (block_flag) { + dwait = WaitForSingleObject(iproc.hProcess, INFINITE); + if (dwait == WAIT_OBJECT_0) { + if (!GetExitCodeProcess (iproc.hProcess, (DWORD*) &excode)) + excode = GetLastError(); + ret_val = JS_NewInt32(ctx, (int32_t) excode); + block_flag = 0; + } else { + besafe++; + if (besafe==20) { + JS_ThrowPlainError(ctx, "exec process did not complete after %d iterations.", besafe); + goto exception; + }; + }; + }; + CloseHandle(iproc.hProcess); + CloseHandle(iproc.hThread); + } else { + pid = iproc.dwProcessId; + ret_val = JS_NewInt32(ctx, pid); + }; + + done: + JS_FreeCString(ctx, file); + JS_FreeCString(ctx, cwd); if (envp) js_free(ctx, envp); return ret_val; -#endif exception: ret_val = JS_EXCEPTION; goto done; } -#endif -#if defined(_WIN32) /* pipe() -> [read_fd, write_fd] or null if error */ static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) From ed57be958badf1c962690dc1836ebf0a8f4956d8 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Tue, 3 Jun 2025 17:35:58 -0400 Subject: [PATCH 23/31] Update test_os_exec.js 7 permutations of direct commands and shell commands firing error codes and text through console. shells inherit stdio unless you pass a pipe. this is exec, bad programs don't execute. echo doesn't execute! there's text output now. it may be to set a flag to disable text output for testing, or else execute a script and verify the pipe text. --- tests/test_os_exec.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index 509777a1d..ddbb5ca2d 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -13,6 +13,7 @@ function test_os_exec() { const osShellCmd = ( isWin ? "cmd" : "/bin/sh" ); const osShellFlag = ( isWin ? "/c" : "-c" ); const osPathSeparator = ( isWin ? "\\" : "/" ); + const osStallProgram = ( isWin ? "cmd /k" : "cat" ); if (!isWin) { @@ -20,23 +21,32 @@ function test_os_exec() { assert(ret, 0); } + ret = os.exec( ["exit 1"], { usePath: false } ); + assert(ret, ( isWin ? 2 : 127 ) ); + ret = os.exec( [osShellCmd, osShellFlag, "exit 2"], { usePath: false } ); - assert(ret, 2); + assert(ret, 2 ); + + ret = os.exec( ["echo invalid filenames leave no text"], ); + assert( ret, ( isWin ? 2 : 127 ) ); - ret = os.exec( ["bad command or filename @@"], ); + ret = os.exec( [osShellCmd, osShellFlag, "bad command or filename @@"], ); assert( ret, ( isWin ? 1 : 127 ) ); ret = os.exec( [osShellCmd, osShellFlag, ": good commands return 0"] ); assert(ret, 0); + ret = os.exec( [osShellCmd, osShellFlag, "echo shell commands come through"] ); + assert(ret, 0); + pid = os.exec( [osShellCmd, osShellFlag, ":"], { block: false} ); /* watchpid is similar waitpid on WIN32 + GNU but lacks a status indicator watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ ret = os.watchpid(pid, 1); assert(ret, pid); - - const osShellEchoParam = ( isWin ? 'echo %FOO%' : 'echo $FOO' ); + fds = os.pipe(); + const osShellEchoParam = ( isWin ? 'echo %FOO%' : 'echo $FOO' ); pid = os.exec( [osShellCmd, osShellFlag, osShellEchoParam ], { stdout: fds[1], block: false, @@ -55,7 +65,7 @@ function test_os_exec() { assert(status >> 8, 0); /* exit code */ } - pid = os.exec(["cat"], { block: false } ); + pid = os.exec([osStallProgram], { block: false } ); assert(pid >= 0); ret = os.watchpid(pid, 0); assert(ret, 0); @@ -66,7 +76,7 @@ function test_os_exec() { assert(ret, pid); if (!isWin) { - pid = os.exec(["cat"], { block: false } ); + pid = os.exec([osStallProgram], { block: false } ); assert(pid >= 0); [ret, status] = os.waitpid(pid, os.WNOHANG); assert(ret, 0); From 4b317d3babf312747976e3dd92398ec5921653e0 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Tue, 3 Jun 2025 20:08:40 -0400 Subject: [PATCH 24/31] Update test_os_exec.js expanded 2 more functions and improved the test on file and inherit. nothing is yet tested on cwd. --- tests/test_os_exec.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index ddbb5ca2d..643399002 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -30,13 +30,23 @@ function test_os_exec() { ret = os.exec( ["echo invalid filenames leave no text"], ); assert( ret, ( isWin ? 2 : 127 ) ); - ret = os.exec( [osShellCmd, osShellFlag, "bad command or filename @@"], ); + ret = os.exec( [osShellCmd, osShellFlag, "errors return error codes"], ); + assert( ret, ( isWin ? 1 : 127 ) ); + + ret = os.exec( [osShellCmd, osShellFlag, "bad command or filename @@"], + { inherit: true } + ); assert( ret, ( isWin ? 1 : 127 ) ); ret = os.exec( [osShellCmd, osShellFlag, ": good commands return 0"] ); assert(ret, 0); - ret = os.exec( [osShellCmd, osShellFlag, "echo shell commands come through"] ); + ret = os.exec( [osShellCmd, osShellFlag, "echo stdio not inherited by default"] ); + assert(ret, 0); + + ret = os.exec( [osShellCmd, osShellFlag, "echo inherited shell text comes through"], + { inherit: true } + ); assert(ret, 0); pid = os.exec( [osShellCmd, osShellFlag, ":"], { block: false} ); From 229918d9d68dfc461608713264cac366c0fd0e2d Mon Sep 17 00:00:00 2001 From: woodtygr Date: Tue, 3 Jun 2025 20:18:46 -0400 Subject: [PATCH 25/31] Update quickjs-libc.c added inherit flag it became more obvious that stdio should default to silent. set the inherit flag or pass one pipe, and the child will inherit the stdio of the parent (except the pipe you set). I believe this includes stdin! --- quickjs-libc.c | 75 +++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/quickjs-libc.c b/quickjs-libc.c index 52d274c71..d8c48bfd8 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -3275,7 +3275,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, const char *file = NULL, *str, *cwd = NULL; uint32_t exec_argc, i; int ret, pid; - bool block_flag = true, use_path = true; + bool block_flag = true, use_path = true, inherit = false; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; char **envp = environ; @@ -3322,6 +3322,8 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, goto exception; if (get_bool_option(ctx, &use_path, options, "usePath")) goto exception; + if (get_bool_option(ctx, &inherit, options, "inherit")) + goto exception; val = JS_GetPropertyStr(ctx, options, "file"); if (JS_IsException(val)) @@ -3349,6 +3351,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (JS_IsException(val)) goto exception; if (!JS_IsUndefined(val)) { + inherit = true; int fd; ret = JS_ToInt32(ctx, &fd, val); JS_FreeValue(ctx, val); @@ -3439,9 +3442,15 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, /* remap the stdin/stdout/stderr handles if necessary */ for(i = 0; i < 3; i++) { if (std_fds[i] != i) { - if (dup2(std_fds[i], i) < 0) + if ( dup2(std_fds[i], i) < 0) _exit(127); - } + close (std_fds[i]); + } else if (!inherit) { + int fd = open("/dev/null", O_WRONLY); + if ( dup2(fd, i) < 0) + _exit(127); + close(fd); + }; } if (js_os_exec_closefrom) { @@ -3528,13 +3537,11 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, uint32_t exec_argc, i, besafe; int32_t excode, dwait; int ret, pid; - bool block_flag = true, use_path = true; + bool block_flag = true, use_path = true, inherit = false; static const char *std_name[3] = { "stdin", "stdout", "stderr" }; int std_fds[3]; - bool specified_fd = false; -#define OS_EXEC_CMD_BUFFSIZE 2048 - char cmd_buff [OS_EXEC_CMD_BUFFSIZE]; - int cmd_ind, cmd_strlen;; + int cmd_ind, cmd_strlen; + char cmd_buff [JS__PATH_MAX]; char *envp = 0; for(i = 0; i < 3; i++) @@ -3548,6 +3555,8 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, goto exception; if (get_bool_option(ctx, &use_path, options, "usePath")) goto exception; + if (get_bool_option(ctx, &inherit, options, "inherit")) + goto exception; val = JS_GetPropertyStr(ctx, options, "file"); if (JS_IsException(val)) @@ -3581,7 +3590,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (ret) goto exception; std_fds[i] = fd; - specified_fd = true; + inherit = true; } } @@ -3604,9 +3613,6 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, if (cmd_strlen) { strncpy(&cmd_buff[cmd_ind], file, cmd_strlen); cmd_ind += cmd_strlen; - } else { - //strncpy(&cmd_buff[cmd_ind], "cmd /C ", 7); - //cmd_ind += 7; }; val = JS_GetPropertyStr(ctx, args, "length"); @@ -3635,7 +3641,7 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, cmd_buff[cmd_ind] = ' '; cmd_ind++; }; - if (OS_EXEC_CMD_BUFFSIZE < (cmd_ind + cmd_strlen + 1)) { + if ( (cmd_ind + cmd_strlen + 1) > JS__PATH_MAX ) { JS_ThrowRangeError(ctx, "exec command line too long."); goto exception; }; @@ -3652,31 +3658,32 @@ static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, memset(&istart, 0, sizeof(istart) ); istart.cb = sizeof(istart); - if (specified_fd) + if (inherit) { istart.dwFlags = STARTF_USESTDHANDLES; - cflags |= CREATE_NO_WINDOW; - }; - istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); - if (istart.hStdInput == INVALID_HANDLE_VALUE) - { - JS_ThrowInternalError(ctx, "failed to associate stdin of process"); - goto exception; - }; - istart.hStdOutput = (HANDLE) _get_osfhandle( std_fds[1] ); - if (istart.hStdOutput == INVALID_HANDLE_VALUE) - { - JS_ThrowInternalError(ctx, "failed to associate stdout of process"); - goto exception; - }; - istart.hStdError = (HANDLE) _get_osfhandle( std_fds[2] ); - if (istart.hStdError == INVALID_HANDLE_VALUE) { - JS_ThrowInternalError(ctx, "failed to associate stderr of process"); - goto exception; - }; + + istart.hStdInput = (HANDLE) _get_osfhandle( std_fds[0] ); + if (istart.hStdInput == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stdin of process"); + goto exception; + } + + istart.hStdOutput = (HANDLE) _get_osfhandle( std_fds[1] ); + if (istart.hStdOutput == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stdout of process"); + goto exception; + } + + istart.hStdError = (HANDLE) _get_osfhandle( std_fds[2] ); + if (istart.hStdError == INVALID_HANDLE_VALUE) { + JS_ThrowInternalError(ctx, "failed to associate stderr of process"); + goto exception; + } + } else cflags |= CREATE_NO_WINDOW; + besafe = excode = 0; - if (!CreateProcessA(file, cmd_buff, 0, 0, specified_fd, cflags, envp, cwd, &istart, &iproc) ) { + if (!CreateProcessA(file, cmd_buff, 0, 0, true, cflags, envp, cwd, &istart, &iproc) ) { excode = GetLastError(); ret_val = JS_NewInt32(ctx, excode); } else if (block_flag) { From 6532352ab081a59dfa5d20bcd8d7fd35b25dcf37 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Wed, 4 Jun 2025 18:43:12 -0400 Subject: [PATCH 26/31] Add files via upload test_os_exec_child.js - executes the 8 basic permutative shell tests and verifies that only inherited handle text passes through stdout and stderr. test_os_exec.js - now builds the test folder path and executes test_os_exec_child.js; verifies that stderr sees 'bad' and stdout only sees the proper echo string. tests.conf will need update for all of this. It comes to my attention there is no return value when doing asynchronous shell commands. --- tests/test_os_exec.js | 73 +++++++++++++------------------------ tests/test_os_exec_child.js | 45 +++++++++++++++++++++++ 2 files changed, 71 insertions(+), 47 deletions(-) create mode 100644 tests/test_os_exec_child.js diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index 643399002..3f6cceb22 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -6,65 +6,31 @@ const isWin = os.platform === 'win32'; const isCygwin = os.platform === 'cygwin'; function test_os_exec() { - var ret, fds, pid, f, status, amend, exe; + var ret, fdout, fderr, pid, f, status, amend, exe, child; - // cmd.exe does leave a \r that getline doesn't trim + /* cmd.exe does leave a \r that getline doesn't trim */ const osTrimLine = ( isWin ? "\r" : "" ); const osShellCmd = ( isWin ? "cmd" : "/bin/sh" ); const osShellFlag = ( isWin ? "/c" : "-c" ); const osPathSeparator = ( isWin ? "\\" : "/" ); const osStallProgram = ( isWin ? "cmd /k" : "cat" ); - if (!isWin) { - - ret = os.exec( ["true"] ); - assert(ret, 0); - } - - ret = os.exec( ["exit 1"], { usePath: false } ); - assert(ret, ( isWin ? 2 : 127 ) ); - - ret = os.exec( [osShellCmd, osShellFlag, "exit 2"], { usePath: false } ); - assert(ret, 2 ); - - ret = os.exec( ["echo invalid filenames leave no text"], ); - assert( ret, ( isWin ? 2 : 127 ) ); - - ret = os.exec( [osShellCmd, osShellFlag, "errors return error codes"], ); - assert( ret, ( isWin ? 1 : 127 ) ); - - ret = os.exec( [osShellCmd, osShellFlag, "bad command or filename @@"], - { inherit: true } - ); - assert( ret, ( isWin ? 1 : 127 ) ); - - ret = os.exec( [osShellCmd, osShellFlag, ": good commands return 0"] ); - assert(ret, 0); - - ret = os.exec( [osShellCmd, osShellFlag, "echo stdio not inherited by default"] ); - assert(ret, 0); - - ret = os.exec( [osShellCmd, osShellFlag, "echo inherited shell text comes through"], - { inherit: true } - ); - assert(ret, 0); - pid = os.exec( [osShellCmd, osShellFlag, ":"], { block: false} ); /* watchpid is similar waitpid on WIN32 + GNU but lacks a status indicator watchpid(p,0) == waitpid(p, WNOHANG), watchpid(p,1) == waitpid(p,0) */ ret = os.watchpid(pid, 1); assert(ret, pid); - fds = os.pipe(); + fdout = os.pipe(); const osShellEchoParam = ( isWin ? 'echo %FOO%' : 'echo $FOO' ); pid = os.exec( [osShellCmd, osShellFlag, osShellEchoParam ], { - stdout: fds[1], + stdout: fdout[1], block: false, env: { FOO: "hello" }, } ); assert(pid >= 0); - os.close(fds[1]); - f = std.fdopen(fds[0], "r"); + os.close(fdout[1]); + f = std.fdopen(fdout[0], "r"); assert(f.getline(), "hello" + osTrimLine); assert(f.getline(), null); f.close(); @@ -104,20 +70,33 @@ function test_os_exec() { amend.pop(); amend.push("qjs"); exe = ( isWin ? amend.join("\\") : amend.join("/") ); + amend.pop(); + amend.pop(); + amend.push("tests"); + amend.push("test_os_exec_child.js"); + child = ( isWin ? amend.join("\\") : amend.join("/") ); pid = os.exec( [ exe,"-q"], { block: true } ); assert(pid, 0); - fds = os.pipe(); - pid = os.exec( [exe,"thereisno.js"], { + + fdout = os.pipe(); + fderr = os.pipe(); + pid = os.exec( [exe, child], { block: true, - stderr: fds[1], + stdout: fdout[1], + stderr: fderr[1], } ); - assert(pid, 1); - os.close(fds[1]); - f = std.fdopen(fds[0], "r"); - assert(f.getline(), "thereisno.js: No such file or directory" + osTrimLine); + assert(pid, 0); + os.close(fdout[1]); + os.close(fderr[1]); + f = std.fdopen(fdout[0], "r"); + assert(f.getline(), "shell text passes through inherited stdio" + osTrimLine); assert(f.getline(), null); f.close(); + f = std.fdopen(fderr[0], "r"); + ret = f.getline().indexOf('bad'); + assert(ret > 0); + f.close(); }; diff --git a/tests/test_os_exec_child.js b/tests/test_os_exec_child.js new file mode 100644 index 000000000..af7b33cbe --- /dev/null +++ b/tests/test_os_exec_child.js @@ -0,0 +1,45 @@ +import * as std from "qjs:std"; +import * as os from "qjs:os"; +import { assert } from "./assert.js"; + +const isWin = os.platform === 'win32'; + +function test_os_processes() { + var ret; + + const osShellCmd = ( isWin ? "cmd" : "/bin/sh" ); + const osShellFlag = ( isWin ? "/c" : "-c" ); + + ret = os.exec( [osShellCmd, osShellFlag, "exit 2"], { usePath: false } ); + assert(ret, 2 ); + + ret = os.exec( [osShellCmd, osShellFlag, ": good commands return 0"] ); + assert(ret, 0); + + ret = os.exec( [osShellCmd, osShellFlag, "errors return respective error codes"], ); + assert( ret, ( isWin ? 1 : 127 ) ); + + ret = os.exec( [osShellCmd, osShellFlag, "echo stdio not inherited by default"] ); + assert(ret, 0); + + ret = os.exec( [osShellCmd, osShellFlag, "bad command or filename @@ stdio inherited"], + { inherit: true } + ); + assert( ret, ( isWin ? 1 : 127 ) ); + + ret = os.exec( ["echo invalid process names fail and do not report to stdio"], + { inherit: true } + ); + assert( ret, ( isWin ? 2 : 127 ) ); + + ret = os.exec( ["exit 1"], { usePath: false } ); + assert(ret, ( isWin ? 2 : 127 ) ); + + ret = os.exec( [osShellCmd, osShellFlag, "echo shell text passes through inherited stdio"], + { inherit: true } + ); + assert(ret, 0); + +}; + +test_os_processes(); From d565213974d747fdac527e5763d77b06c51ab478 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Wed, 4 Jun 2025 18:44:34 -0400 Subject: [PATCH 27/31] Update tests.conf test_os_exec_child.js added to exclusions. there's no other way to silently verify stdout and stderr inheritance. --- tests.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/tests.conf b/tests.conf index b006e1666..9faea62d5 100644 --- a/tests.conf +++ b/tests.conf @@ -8,3 +8,4 @@ tests/empty.js tests/fixture_cyclic_import.js tests/microbench.js tests/test_worker_module.js +tests/test_os_exec_child.js From 2d859c6639df0944db922f3019d158565e70767d Mon Sep 17 00:00:00 2001 From: woodtygr Date: Wed, 4 Jun 2025 19:07:17 -0400 Subject: [PATCH 28/31] Update test_os_exec.js amended child execution to test cwd parameter of os.exec --- tests/test_os_exec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index 3f6cceb22..e88ffc150 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -6,7 +6,7 @@ const isWin = os.platform === 'win32'; const isCygwin = os.platform === 'cygwin'; function test_os_exec() { - var ret, fdout, fderr, pid, f, status, amend, exe, child; + var ret, fdout, fderr, pid, f, status, amend, exe, dir; /* cmd.exe does leave a \r that getline doesn't trim */ const osTrimLine = ( isWin ? "\r" : "" ); @@ -73,18 +73,18 @@ function test_os_exec() { amend.pop(); amend.pop(); amend.push("tests"); - amend.push("test_os_exec_child.js"); - child = ( isWin ? amend.join("\\") : amend.join("/") ); + dir = ( isWin ? amend.join("\\") : amend.join("/") ); pid = os.exec( [ exe,"-q"], { block: true } ); assert(pid, 0); fdout = os.pipe(); fderr = os.pipe(); - pid = os.exec( [exe, child], { + pid = os.exec( [exe, "test_os_exec_child.js"], { block: true, stdout: fdout[1], stderr: fderr[1], + cwd: dir, } ); assert(pid, 0); os.close(fdout[1]); From 61a8c5c8645d4f8844e29047f4fe43f09857e696 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Thu, 5 Jun 2025 15:09:02 -0400 Subject: [PATCH 29/31] cwd tested from getcwd I was building the script path, but I needed to test cwd. test262 is usually called from the make folder. manually I'm going into tests/ and calling from there so that assert.js is there. now I look and see if 'tests' is the end of the path, if not I insert it, and I use 'cwd: dir,' If someone executes test262 from a build folder, I don't know how they get assert.js, but this will then break. --- tests/test_os_exec.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index e88ffc150..ba2daa648 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -69,11 +69,13 @@ function test_os_exec() { amend = os.exePath().split(osPathSeparator); amend.pop(); amend.push("qjs"); - exe = ( isWin ? amend.join("\\") : amend.join("/") ); - amend.pop(); - amend.pop(); - amend.push("tests"); - dir = ( isWin ? amend.join("\\") : amend.join("/") ); + exe = amend.join(osPathSeparator); + [dir, ret] = os.getcwd(); + amend = dir.split(osPathSeparator); + if (amend[amend.length - 1] != "tests") { + amend.push("tests"); + dir = amend.join(osPathSeparator); + }; pid = os.exec( [ exe,"-q"], { block: true } ); assert(pid, 0); From 9f4758150944830b59deb49e84e58241724891d6 Mon Sep 17 00:00:00 2001 From: woodtygr Date: Thu, 5 Jun 2025 15:12:54 -0400 Subject: [PATCH 30/31] should work with test262 I need the cwd, and it needs testing. detects if you're in 'build' detects if you're in 'scripts' otherwise adds 'scripts' then it builds the cwd to use and test. --- tests/test_os_exec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_os_exec.js b/tests/test_os_exec.js index ba2daa648..e53918ec8 100644 --- a/tests/test_os_exec.js +++ b/tests/test_os_exec.js @@ -72,6 +72,8 @@ function test_os_exec() { exe = amend.join(osPathSeparator); [dir, ret] = os.getcwd(); amend = dir.split(osPathSeparator); + if (amend[amend.length - 1] == "build") + amend.pop(); if (amend[amend.length - 1] != "tests") { amend.push("tests"); dir = amend.join(osPathSeparator); From 2e7cab69c2e565cff7896e9fa204afc5c14338aa Mon Sep 17 00:00:00 2001 From: woodtygr Date: Thu, 5 Jun 2025 16:01:16 -0400 Subject: [PATCH 31/31] removed test_os_exec test_os_exec moved to other js --- tests/test_std.js | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/tests/test_std.js b/tests/test_std.js index 32648ef57..79018de7d 100644 --- a/tests/test_std.js +++ b/tests/test_std.js @@ -201,45 +201,6 @@ function test_os() assert(os.remove(fdir) === 0); } -function test_os_exec() -{ - var ret, fds, pid, f, status; - - ret = os.exec(["true"]); - assert(ret, 0); - - ret = os.exec(["/bin/sh", "-c", "exit 1"], { usePath: false }); - assert(ret, 1); - - fds = os.pipe(); - pid = os.exec(["sh", "-c", "echo $FOO"], { - stdout: fds[1], - block: false, - env: { FOO: "hello" }, - } ); - assert(pid >= 0); - os.close(fds[1]); /* close the write end (as it is only in the child) */ - f = std.fdopen(fds[0], "r"); - assert(f.getline(), "hello"); - assert(f.getline(), null); - f.close(); - [ret, status] = os.waitpid(pid, 0); - assert(ret, pid); - assert(status & 0x7f, 0); /* exited */ - assert(status >> 8, 0); /* exit code */ - - pid = os.exec(["cat"], { block: false } ); - assert(pid >= 0); - os.kill(pid, os.SIGTERM); - [ret, status] = os.waitpid(pid, 0); - assert(ret, pid); - // Flaky on cygwin for unclear reasons, see - // https://github.com/quickjs-ng/quickjs/issues/184 - if (!isCygwin) { - assert(status & 0x7f, os.SIGTERM); - } -} - function test_interval() { var t = os.setInterval(f, 1); @@ -293,7 +254,6 @@ test_file2(); test_getline(); test_popen(); test_os(); -!isWin && test_os_exec(); test_interval(); test_timeout(); test_timeout_order();