diff --git a/libc/calls/mkntcmdline.c b/libc/calls/mkntcmdline.c index 1ece520f4..c1a4dfc5b 100644 --- a/libc/calls/mkntcmdline.c +++ b/libc/calls/mkntcmdline.c @@ -23,6 +23,7 @@ #include "libc/mem/mem.h" #include "libc/nt/files.h" #include "libc/proc/ntspawn.h" +#include "libc/stdio/sysparam.h" #include "libc/str/str.h" #include "libc/str/thompike.h" #include "libc/str/utf16.h" @@ -31,16 +32,14 @@ #define APPEND(c) \ do { \ - if (k == 32766) { \ - return e2big(); \ - } \ - cmdline[k++] = c; \ + if (k < size) \ + cmdline[k] = c; \ + ++k; \ } while (0) -static bool NeedsQuotes(const char *s) { - if (!*s) { +static textwindows bool NeedsQuotes(const char *s) { + if (!*s) return true; - } do { switch (*s) { case '"': @@ -60,7 +59,7 @@ static inline int IsAlpha(int c) { return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); } -static bool LooksLikeCosmoDrivePath(const char *s) { +static textwindows bool LooksLikeCosmoDrivePath(const char *s) { return s[0] == '/' && // IsAlpha(s[1]) && // s[2] == '/'; @@ -74,12 +73,13 @@ static bool LooksLikeCosmoDrivePath(const char *s) { // // @param cmdline is output buffer // @param argv is an a NULL-terminated array of UTF-8 strings -// @return 0 on success, or -1 w/ errno -// @raise E2BIG if everything is too huge +// @param size is number of characters in cmdline buffer +// @return length on success, which is >=size on truncation // @see "Everyone quotes command line arguments the wrong way" MSDN // @see libc/runtime/getdosargv.c // @asyncsignalsafe -textwindows int mkntcmdline(char16_t cmdline[32767], char *const argv[]) { +textwindows size_t mkntcmdline(char16_t *cmdline, char *const argv[], + size_t size) { char *arg; int slashes, n; bool needsquote; @@ -95,9 +95,8 @@ textwindows int mkntcmdline(char16_t cmdline[32767], char *const argv[]) { } else { arg = argv[i]; } - if ((needsquote = NeedsQuotes(arg))) { + if ((needsquote = NeedsQuotes(arg))) APPEND(u'"'); - } for (slashes = j = 0;;) { wint_t x = arg[j++] & 255; if (x >= 0300) { @@ -122,9 +121,8 @@ textwindows int mkntcmdline(char16_t cmdline[32767], char *const argv[]) { APPEND(u'"'); APPEND(u'"'); } else { - for (s = 0; s < slashes; ++s) { + for (s = 0; s < slashes; ++s) APPEND(u'\\'); - } slashes = 0; uint32_t w = EncodeUtf16(x); do @@ -132,13 +130,12 @@ textwindows int mkntcmdline(char16_t cmdline[32767], char *const argv[]) { while ((w >>= 16)); } } - for (s = 0; s < (slashes << needsquote); ++s) { + for (s = 0; s < (slashes << needsquote); ++s) APPEND(u'\\'); - } - if (needsquote) { + if (needsquote) APPEND(u'"'); - } } - cmdline[k] = 0; - return 0; + if (size) + cmdline[MIN(k, size - 1)] = 0; + return k; } diff --git a/libc/calls/ntspawn.c b/libc/calls/ntspawn.c index 305950b25..2f2367e88 100644 --- a/libc/calls/ntspawn.c +++ b/libc/calls/ntspawn.c @@ -20,9 +20,15 @@ #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/intrin/strace.internal.h" +#include "libc/nt/createfile.h" +#include "libc/nt/enum/accessmask.h" +#include "libc/nt/enum/creationdisposition.h" +#include "libc/nt/enum/fileflagandattributes.h" +#include "libc/nt/enum/filesharemode.h" #include "libc/nt/enum/processaccess.h" #include "libc/nt/enum/processcreationflags.h" #include "libc/nt/errors.h" +#include "libc/nt/events.h" #include "libc/nt/files.h" #include "libc/nt/memory.h" #include "libc/nt/process.h" @@ -44,14 +50,167 @@ struct SpawnBlock { char envbuf[32767]; }; -static void *ntspawn_malloc(size_t size) { +static textwindows void *ntspawn_malloc(size_t size) { return HeapAlloc(GetProcessHeap(), 0, size); } -static void ntspawn_free(void *ptr) { +static textwindows void ntspawn_free(void *ptr) { HeapFree(GetProcessHeap(), 0, ptr); } +static textwindows ssize_t ntspawn_read(intptr_t fh, char *buf, size_t len) { + bool ok; + uint32_t got; + struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 0, 0, 0)}; + ok = (ReadFile(fh, buf, len, 0, &overlap) || + GetLastError() == kNtErrorIoPending) && + GetOverlappedResult(fh, &overlap, &got, true); + CloseHandle(overlap.hEvent); + return ok ? got : -1; +} + +static textwindows int ntspawn2(struct NtSpawnArgs *a, struct SpawnBlock *sb) { + + // make executable path + if (__mkntpathath(a->dirhand, a->prog, 0, sb->path) == -1) + return -1; + + // open executable + char *p = sb->envbuf; + char *pe = p + sizeof(sb->envbuf); + intptr_t fh = CreateFile( + sb->path, kNtFileGenericRead, + kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, 0, + kNtOpenExisting, kNtFileAttributeNormal | kNtFileFlagBackupSemantics, 0); + if (fh == -1) + return -1; + ssize_t got = ntspawn_read(fh, p, pe - p); + CloseHandle(fh); + if (got < 3) + return enoexec(); + pe = p + got; + + // handle shebang + size_t i = 0; // represents space of sb->cmdline consumed + if (p[0] == 'M' && p[1] == 'Z') { + // it's a windows executable + } else if (p[0] == '#' && p[1] == '!') { + p += 2; + // make sure we got a complete first line + pe = memchr(p, '\n', pe - p); + if (!pe) + return enoexec(); + *pe = 0; + int argc = 0; + char *argv[4]; + // it's legal to say "#! /bin/sh" + while (p < pe && (*p == ' ' || *p == '\t')) + ++p; + if (p == pe) + return enoexec(); + argv[argc++] = p; + // find the optional argument + while (p < pe && !(*p == ' ' || *p == '\t')) + ++p; + if (p < pe) { + *p++ = 0; + while (p < pe && (*p == ' ' || *p == '\t')) + ++p; + if (p < pe) { + argv[argc++] = p; + } + } + // now add the prog + argv[argc++] = (char *)a->prog; + argv[argc] = 0; + // ignore argv[0] + if (*a->argv) + ++a->argv; + // prepend arguments + if ((i += mkntcmdline(sb->cmdline + i, argv, 32767 - i)) >= 32767 - 1) + return e2big(); + sb->cmdline[i++] = ' '; + sb->cmdline[i] = 0; + // setup the true executable path + if (__mkntpathath(a->dirhand, argv[0], 0, sb->path) == -1) + return -1; + } else { + // it's something else + return enoexec(); + } + + // setup arguments and environment + if ((i += mkntcmdline(sb->cmdline + i, a->argv, 32767 - i)) >= 32767) + return e2big(); + if (mkntenvblock(sb->envblock, a->envp, a->extravars, sb->envbuf) == -1) + return -1; + + // create attribute list + // this code won't call malloc in practice + bool32 ok; + void *freeme = 0; + _Alignas(16) char memory[128]; + size_t size = sizeof(memory); + struct NtProcThreadAttributeList *alist = (void *)memory; + uint32_t items = !!a->opt_hParentProcess + !!a->opt_lpExplicitHandleList; + ok = InitializeProcThreadAttributeList(alist, items, 0, &size); + if (!ok && GetLastError() == kNtErrorInsufficientBuffer) { + ok = !!(alist = freeme = ntspawn_malloc(size)); + if (ok) { + ok = InitializeProcThreadAttributeList(alist, items, 0, &size); + } + } + if (ok && a->opt_hParentProcess) { + ok = UpdateProcThreadAttribute( + alist, 0, kNtProcThreadAttributeParentProcess, &a->opt_hParentProcess, + sizeof(a->opt_hParentProcess), 0, 0); + } + if (ok && a->opt_lpExplicitHandleList) { + ok = UpdateProcThreadAttribute( + alist, 0, kNtProcThreadAttributeHandleList, a->opt_lpExplicitHandleList, + a->dwExplicitHandleCount * sizeof(*a->opt_lpExplicitHandleList), 0, 0); + } + + // create the process + int rc; + if (ok) { + struct NtStartupInfoEx info = { + .StartupInfo = *a->lpStartupInfo, + .StartupInfo.cb = sizeof(info), + .lpAttributeList = alist, + }; + if (ok) { + if (CreateProcess(sb->path, sb->cmdline, 0, 0, true, + a->dwCreationFlags | kNtCreateUnicodeEnvironment | + kNtExtendedStartupinfoPresent | + kNtInheritParentAffinity | + GetPriorityClass(GetCurrentProcess()), + sb->envblock, a->opt_lpCurrentDirectory, + &info.StartupInfo, a->opt_out_lpProcessInformation)) { + rc = 0; + } else { + rc = -1; + STRACE("CreateProcess() failed w/ %d", GetLastError()); + if (GetLastError() == kNtErrorSharingViolation) { + etxtbsy(); + } else if (GetLastError() == kNtErrorInvalidName) { + enoent(); + } + } + rc = __fix_enotdir(rc, sb->path); + } + } else { + rc = __winerr(); + } + + // clean up resources + if (alist) + DeleteProcThreadAttributeList(alist); + if (freeme) + ntspawn_free(freeme); + return rc; +} + /** * Spawns process on Windows NT. * @@ -72,93 +231,15 @@ static void ntspawn_free(void *ptr) { * @see spawnve() which abstracts this function * @asyncsignalsafe */ -textwindows int ntspawn( - int64_t dirhand, const char *prog, char *const argv[], char *const envp[], - char *const extravars[], uint32_t dwCreationFlags, - const char16_t *opt_lpCurrentDirectory, int64_t opt_hParentProcess, - int64_t *opt_lpExplicitHandleList, uint32_t dwExplicitHandleCount, - const struct NtStartupInfo *lpStartupInfo, - struct NtProcessInformation *opt_out_lpProcessInformation) { - int rc = -1; +textwindows int ntspawn(struct NtSpawnArgs *args) { + int rc; struct SpawnBlock *sb; BLOCK_SIGNALS; - if ((sb = ntspawn_malloc(sizeof(*sb))) && - __mkntpathath(dirhand, prog, 0, sb->path) != -1) { - if (!mkntcmdline(sb->cmdline, argv) && - !mkntenvblock(sb->envblock, envp, extravars, sb->envbuf)) { - bool32 ok; - int64_t dp = GetCurrentProcess(); - - // create attribute list - // this code won't call malloc in practice - void *freeme = 0; - _Alignas(16) char memory[128]; - size_t size = sizeof(memory); - struct NtProcThreadAttributeList *alist = (void *)memory; - uint32_t items = !!opt_hParentProcess + !!opt_lpExplicitHandleList; - ok = InitializeProcThreadAttributeList(alist, items, 0, &size); - if (!ok && GetLastError() == kNtErrorInsufficientBuffer) { - ok = !!(alist = freeme = ntspawn_malloc(size)); - if (ok) { - ok = InitializeProcThreadAttributeList(alist, items, 0, &size); - } - } - if (ok && opt_hParentProcess) { - ok = UpdateProcThreadAttribute( - alist, 0, kNtProcThreadAttributeParentProcess, &opt_hParentProcess, - sizeof(opt_hParentProcess), 0, 0); - } - if (ok && opt_lpExplicitHandleList) { - ok = UpdateProcThreadAttribute( - alist, 0, kNtProcThreadAttributeHandleList, - opt_lpExplicitHandleList, - dwExplicitHandleCount * sizeof(*opt_lpExplicitHandleList), 0, 0); - } - - // create the process - if (ok) { - struct NtStartupInfoEx info; - bzero(&info, sizeof(info)); - info.StartupInfo = *lpStartupInfo; - info.StartupInfo.cb = sizeof(info); - info.lpAttributeList = alist; - if (ok) { - if (CreateProcess(sb->path, sb->cmdline, 0, 0, true, - dwCreationFlags | kNtCreateUnicodeEnvironment | - kNtExtendedStartupinfoPresent | - kNtInheritParentAffinity | - GetPriorityClass(GetCurrentProcess()), - sb->envblock, opt_lpCurrentDirectory, - &info.StartupInfo, opt_out_lpProcessInformation)) { - rc = 0; - } else { - STRACE("CreateProcess() failed w/ %d", GetLastError()); - if (GetLastError() == kNtErrorSharingViolation) { - etxtbsy(); - } else if (GetLastError() == kNtErrorInvalidName) { - enoent(); - } - } - rc = __fix_enotdir(rc, sb->path); - } - } else { - rc = __winerr(); - } - - // clean up resources - if (alist) { - DeleteProcThreadAttributeList(alist); - } - if (freeme) { - ntspawn_free(freeme); - } - if (dp && dp != GetCurrentProcess()) { - CloseHandle(dp); - } - } + if ((sb = ntspawn_malloc(sizeof(*sb)))) { + rc = ntspawn2(args, sb); + } else { + rc = -1; } - if (sb) - ntspawn_free(sb); ALLOW_SIGNALS; return rc; } diff --git a/libc/proc/execve-nt.greg.c b/libc/proc/execve-nt.greg.c index 133e4f8d0..03d8590c1 100644 --- a/libc/proc/execve-nt.greg.c +++ b/libc/proc/execve-nt.greg.c @@ -91,9 +91,9 @@ textwindows int sys_execve_nt(const char *program, char *const argv[], // launch the process struct NtProcessInformation pi; - int rc = ntspawn(AT_FDCWD, program, argv, envp, - (char *[]){fdspec, maskvar, 0}, 0, 0, hParentProcess, - lpExplicitHandles, dwExplicitHandleCount, &si, &pi); + int rc = ntspawn(&(struct NtSpawnArgs){ + AT_FDCWD, program, argv, envp, (char *[]){fdspec, maskvar, 0}, 0, 0, + hParentProcess, lpExplicitHandles, dwExplicitHandleCount, &si, &pi}); __undescribe_fds(hParentProcess, lpExplicitHandles, dwExplicitHandleCount); if (rc == -1) { free(fdspec); diff --git a/libc/proc/fork-nt.c b/libc/proc/fork-nt.c index 4ef781c2c..66fd16a50 100644 --- a/libc/proc/fork-nt.c +++ b/libc/proc/fork-nt.c @@ -344,9 +344,10 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) { } #endif NTTRACE("STARTING SPAWN"); - int spawnrc = ntspawn(AT_FDCWD, GetProgramExecutableName(), args, environ, - (char *[]){forkvar, 0}, dwCreationFlags, 0, 0, 0, 0, - &startinfo, &procinfo); + int spawnrc = ntspawn(&(struct NtSpawnArgs){ + AT_FDCWD, GetProgramExecutableName(), args, environ, + (char *[]){forkvar, 0}, dwCreationFlags, 0, 0, 0, 0, &startinfo, + &procinfo}); if (spawnrc != -1) { CloseHandle(procinfo.hThread); ok = WriteAll(writer, jb, sizeof(jb)) && diff --git a/libc/proc/ntspawn.h b/libc/proc/ntspawn.h index 5a4de12b5..463c883ae 100644 --- a/libc/proc/ntspawn.h +++ b/libc/proc/ntspawn.h @@ -4,12 +4,25 @@ #include "libc/nt/struct/startupinfo.h" COSMOPOLITAN_C_START_ -void mungentpath(char *); -int mkntcmdline(char16_t[32767], char *const[]); +struct NtSpawnArgs { + int64_t dirhand; + const char *prog; + char *const *argv; + char *const *envp; + char *const *extravars; + uint32_t dwCreationFlags; + const char16_t *opt_lpCurrentDirectory; + int64_t opt_hParentProcess; + int64_t *opt_lpExplicitHandleList; + uint32_t dwExplicitHandleCount; + const struct NtStartupInfo *lpStartupInfo; + struct NtProcessInformation *opt_out_lpProcessInformation; +}; + int mkntenvblock(char16_t[32767], char *const[], char *const[], char[32767]); -int ntspawn(int64_t, const char *, char *const[], char *const[], char *const[], - uint32_t, const char16_t *, int64_t, int64_t *, uint32_t, - const struct NtStartupInfo *, struct NtProcessInformation *); +int ntspawn(struct NtSpawnArgs *); +size_t mkntcmdline(char16_t *, char *const[], size_t); +void mungentpath(char *); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_NTSPAWN_H_ */ diff --git a/libc/proc/posix_spawn.c b/libc/proc/posix_spawn.c index a99ef8dc0..6cf8439af 100644 --- a/libc/proc/posix_spawn.c +++ b/libc/proc/posix_spawn.c @@ -382,9 +382,10 @@ static textwindows errno_t posix_spawn_nt_impl( envp = environ; if ((fdspec = __describe_fds(fds.p, fds.n, &startinfo, hCreatorProcess, &lpExplicitHandles, &dwExplicitHandleCount))) { - rc = ntspawn(dirhand, path, argv, envp, (char *[]){fdspec, maskvar, 0}, - dwCreationFlags, lpCurrentDirectory, 0, lpExplicitHandles, - dwExplicitHandleCount, &startinfo, &procinfo); + rc = ntspawn(&(struct NtSpawnArgs){ + dirhand, path, argv, envp, (char *[]){fdspec, maskvar, 0}, + dwCreationFlags, lpCurrentDirectory, 0, lpExplicitHandles, + dwExplicitHandleCount, &startinfo, &procinfo}); } if (rc == -1) { err = errno; diff --git a/libc/testlib/ezbenchreport.c b/libc/testlib/ezbenchreport.c index f73e04950..04ad5facd 100644 --- a/libc/testlib/ezbenchreport.c +++ b/libc/testlib/ezbenchreport.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/intrin/kprintf.h" +#include "libc/intrin/safemacros.internal.h" #include "libc/math.h" #include "libc/runtime/runtime.h" @@ -32,7 +33,7 @@ void __testlib_ezbenchreport_n(const char *form, char z, size_t n, double c) { char msg[128]; __warn_if_powersave(); ksnprintf(msg, sizeof(msg), "%s %c=%d", form, z, n); - cn = lrint(c / 3); + cn = max(lrint(c / 3), 1); if (!n) { kprintf("\n"); kprintf(" * %-28s", msg); diff --git a/test/libc/calls/mkntcmdline_test.c b/test/libc/calls/mkntcmdline_test.c index 3a645ed3a..cb85156e9 100644 --- a/test/libc/calls/mkntcmdline_test.c +++ b/test/libc/calls/mkntcmdline_test.c @@ -27,39 +27,53 @@ char16_t cmdline[32767]; +TEST(mkntcmdline, empty) { + char16_t buf2[2]; + EXPECT_EQ(0, mkntcmdline(buf2, (char *[]){0}, 2)); + EXPECT_STREQ(u"", buf2); +} + +TEST(mkntcmdline, truncation) { + char *argv[] = {"foo", NULL}; + EXPECT_EQ(3, mkntcmdline(0, argv, 0)); + char16_t buf2[2]; + EXPECT_EQ(3, mkntcmdline(buf2, argv, 2)); + EXPECT_STREQ(u"f", buf2); +} + TEST(mkntcmdline, emptyArgvList_cantBeEmptyOnWindows) { char *argv[] = {"foo", NULL}; - EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv, 32767)); EXPECT_STREQ(u"foo", cmdline); } TEST(mkntcmdline, emptyArgvListWithProg_isEmpty) { char *argv[] = {NULL}; - EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv, 32767)); EXPECT_STREQ(u"", cmdline); } TEST(mkntcmdline, emptyArg_getsQuoted) { char *argv[] = {"", NULL}; - EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv, 32767)); EXPECT_STREQ(u"\"\"", cmdline); } TEST(mkntcmdline, ignoranceIsBliss) { char *argv[] = {"echo", "hello", "world", NULL}; - EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv, 32767)); EXPECT_STREQ(u"echo hello world", cmdline); } TEST(mkntcmdline, spaceInArgument_getQuotesWrappedAround) { char *argv[] = {"echo", "hello there", "world", NULL}; - EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv, 32767)); EXPECT_STREQ(u"echo \"hello there\" world", cmdline); } TEST(mkntcmdline, justSlash) { char *argv[] = {"\\", NULL}; - EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv, 32767)); EXPECT_STREQ(u"\\", cmdline); } @@ -69,7 +83,7 @@ TEST(mkntcmdline, testUnicode) { gc(strdup("要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非")), NULL, }; - EXPECT_NE(-1, mkntcmdline(cmdline, argv1)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv1, 32767)); EXPECT_STREQ(u"(╯°□°)╯ \"要依法治国是赞美那些谁是公义的和惩罚恶人。 - 韩非\"", cmdline); } @@ -80,13 +94,13 @@ TEST(mkntcmdline, fixit) { "--version", NULL, }; - EXPECT_NE(-1, mkntcmdline(cmdline, argv1)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv1, 32767)); EXPECT_STREQ(u"\"C:\\Program Files\\doom\\doom.exe\" --version", cmdline); } TEST(mkntcmdline, testWut) { char *argv[] = {"C:\\Users\\jart\\𝑟𝑒𝑑𝑏𝑒𝑎𝑛", "--strace", NULL}; - EXPECT_NE(-1, mkntcmdline(cmdline, argv)); + EXPECT_NE(-1, mkntcmdline(cmdline, argv, 32767)); EXPECT_STREQ(u"C:\\Users\\jart\\𝑟𝑒𝑑𝑏𝑒𝑎𝑛 --strace", cmdline); } @@ -95,5 +109,5 @@ BENCH(mkntcmdline, lotsOfArgs) { for (int i = 0; i < 999; ++i) { argv[i] = "hello there hello there"; } - EZBENCH2("mkntcmdline", donothing, unassert(!mkntcmdline(cmdline, argv))); + EZBENCH2("mkntcmdline", donothing, mkntcmdline(cmdline, argv, 32767)); }