forked from WHC/Hermit
Windows: official port -- UCRT static build, UTF-8 throughout, Low IL sandbox, ncurses TUI, installer; v0.6.0
Build
* cross build switches to pkgsCross.ucrt64 (shell-win.nix); curl (Schannel),
winpthreads and ncurses 6.6 are linked statically, so the release is
cst-agent.exe + busybox.exe (busybox-w32 Unicode) with no extra DLLs
* windres embeds a manifest with activeCodePage=UTF-8 and a version resource
* ncurses 6.6 Windows console driver patches (mouse button state, A_DIM,
wide-char declarations): scripts/patches/
* scripts/bootstrap_msys2.sh builds the same release natively in MSYS2
UCRT64; PR #1's MINGW64 notes now point there
* vendor/tre-regex (musl's TRE) provides <regex.h> on MinGW
UTF-8
* paths cross the system call boundary as UTF-16 (*W APIs); console code
pages set to 65001 and restored on exit; files are read and written in
binary mode
* child output that is not valid UTF-8 is decoded from the console, OEM or
ANSI system code page
* read_file hides CR; edit_file, apply_patch and write_file keep CRLF line
endings and a UTF-8 BOM
OS layer and processes
* os_dir_* and cst_fnmatch replace the POSIX-only directory walks in
subagent, plugins, skills, undo and fs_tools; realpath / path_under and
symlink rejection work on Windows
* every child goes through CreateProcessW with its own environment block
inside a Job Object, so timeouts and Esc end the whole process tree
Sandbox (auto mode, "auto · lowil")
* run_shell runs with a restricted Low integrity token; the workspace gets
an inheritable Low label once, the sandbox TEMP is labeled Low, and the
credentials file gets a no-read-up label
* drive roots, the profile and system folders are never labeled
* network commands still ask, since Low IL does not block the network;
Access denied and MSYS2's 0xc0000142 offer a rerun outside the sandbox
* /sandbox lists labeled folders, /sandbox reset-labels restores them
TUI
* raster console fonts are switched to Consolas / NSimSun while running
* the home logo falls back to text when the console font lacks U+2580
* mouse drag copy goes to the Windows clipboard
Distribution
* scripts/install_windows.ps1 installs a Gitea release into
%LOCALAPPDATA%\Programs\cst-agent and adds it to the user PATH;
cst-agent-update.cmd reruns it; scripts/release_windows.sh uploads the
zip with the token taken from the environment
* scripts/run_windows.bat is gone; docs/README-windows.md is rewritten
Tests
* new test_lowil, test_win_utf8 and test_os_dir; tests made portable
(cst_test_util.h)
* scripts/win_test.sh and scripts/winvm/ run the suite on English and
Chinese Windows Server VMs as Administrator, a standard user and a
Chinese user name: 29/29 on all six
This commit is contained in:
+150
-345
@@ -10,6 +10,7 @@
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#include "os_compat.h"
|
||||
#include "os_internal.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
@@ -26,6 +27,7 @@
|
||||
#include <process.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
@@ -93,26 +95,10 @@ int os_path_join(char *out, size_t out_sz, const char *a, const char *b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
int os_realpath(const char *path, char *out, size_t out_sz) {
|
||||
if (!path || !out || out_sz < 2)
|
||||
return -1;
|
||||
#ifdef _WIN32
|
||||
char buf[OS_PATH_MAX];
|
||||
DWORD n = GetFullPathNameA(path, (DWORD)sizeof(buf), buf, NULL);
|
||||
if (n == 0 || n >= sizeof(buf))
|
||||
return -1;
|
||||
/* Prefer forward slashes internally for sandbox compares. */
|
||||
os_path_norm_slashes(buf);
|
||||
if (strlen(buf) + 1 > out_sz)
|
||||
return -1;
|
||||
memcpy(out, buf, strlen(buf) + 1);
|
||||
/* Lowercase drive letter for stable compares */
|
||||
if (out[0] && out[1] == ':') {
|
||||
if (out[0] >= 'A' && out[0] <= 'Z')
|
||||
out[0] = (char)(out[0] - 'A' + 'a');
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
char *r = realpath(path, NULL);
|
||||
if (!r)
|
||||
return -1;
|
||||
@@ -123,7 +109,6 @@ int os_realpath(const char *path, char *out, size_t out_sz) {
|
||||
memcpy(out, r, strlen(r) + 1);
|
||||
free(r);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int os_path_under(const char *root, const char *path) {
|
||||
@@ -132,14 +117,9 @@ int os_path_under(const char *root, const char *path) {
|
||||
size_t rl = strlen(root);
|
||||
if (strncmp(path, root, rl) != 0)
|
||||
return 0;
|
||||
if (path[rl] == '\0')
|
||||
return 1;
|
||||
#ifdef _WIN32
|
||||
return path[rl] == '/' || path[rl] == '\\';
|
||||
#else
|
||||
return path[rl] == '/';
|
||||
#endif
|
||||
return path[rl] == '\0' || path[rl] == '/';
|
||||
}
|
||||
#endif
|
||||
|
||||
int os_mkdir(const char *path, int mode) {
|
||||
if (!path)
|
||||
@@ -172,16 +152,93 @@ int os_mkdir_p(const char *path, int mode) {
|
||||
return os_mkdir(tmp, mode);
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
struct os_dir {
|
||||
DIR *d;
|
||||
char path[OS_PATH_MAX];
|
||||
os_dirent_t ent;
|
||||
};
|
||||
|
||||
os_dir_t *os_dir_open(const char *path) {
|
||||
if (!path)
|
||||
return NULL;
|
||||
os_dir_t *d = calloc(1, sizeof(*d));
|
||||
if (!d)
|
||||
return NULL;
|
||||
snprintf(d->path, sizeof(d->path), "%s", path);
|
||||
d->d = opendir(path);
|
||||
if (!d->d) {
|
||||
free(d);
|
||||
return NULL;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
const os_dirent_t *os_dir_next(os_dir_t *d) {
|
||||
struct dirent *de;
|
||||
while (d && (de = readdir(d->d)) != NULL) {
|
||||
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
||||
continue;
|
||||
d->ent.name = de->d_name;
|
||||
d->ent.is_dir = d->ent.is_link = 0;
|
||||
#ifdef DT_DIR
|
||||
if (de->d_type != DT_UNKNOWN) {
|
||||
d->ent.is_dir = de->d_type == DT_DIR;
|
||||
d->ent.is_link = de->d_type == DT_LNK;
|
||||
return &d->ent;
|
||||
}
|
||||
#endif
|
||||
char p[OS_PATH_MAX];
|
||||
struct stat st;
|
||||
if (snprintf(p, sizeof(p), "%s/%s", d->path, de->d_name) <
|
||||
(int)sizeof(p) &&
|
||||
lstat(p, &st) == 0) {
|
||||
d->ent.is_dir = S_ISDIR(st.st_mode);
|
||||
d->ent.is_link = S_ISLNK(st.st_mode);
|
||||
}
|
||||
return &d->ent;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void os_dir_close(os_dir_t *d) {
|
||||
if (!d)
|
||||
return;
|
||||
closedir(d->d);
|
||||
free(d);
|
||||
}
|
||||
|
||||
int os_lstat(const char *path, os_stat_t *st) {
|
||||
struct stat s;
|
||||
memset(st, 0, sizeof(*st));
|
||||
if (!path || lstat(path, &s) != 0)
|
||||
return -1;
|
||||
st->is_dir = S_ISDIR(s.st_mode);
|
||||
st->is_reg = S_ISREG(s.st_mode);
|
||||
st->is_link = S_ISLNK(s.st_mode);
|
||||
st->size = (long long)s.st_size;
|
||||
st->mtime = (double)s.st_mtime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int os_rename_replace(const char *from, const char *to) {
|
||||
return rename(from, to) == 0 ? 0 : -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int os_config_dir(char *out, size_t out_sz) {
|
||||
if (!out || out_sz < 8)
|
||||
return -1;
|
||||
#ifdef _WIN32
|
||||
const char *base = getenv("APPDATA");
|
||||
const char *base = getenv("XDG_CONFIG_HOME");
|
||||
if (!base || !os_path_is_abs(base))
|
||||
base = getenv("APPDATA");
|
||||
if (!base || !base[0])
|
||||
base = getenv("LOCALAPPDATA");
|
||||
if (!base || !base[0])
|
||||
return -1;
|
||||
int n = snprintf(out, out_sz, "%s\\cst-agent", base);
|
||||
int n = snprintf(out, out_sz, "%s/cst-agent", base);
|
||||
os_path_norm_slashes(out);
|
||||
#else
|
||||
const char *xdg = getenv("XDG_CONFIG_HOME");
|
||||
const char *home = getenv("HOME");
|
||||
@@ -389,7 +446,7 @@ void os_set_run_timeout(int seconds) { g_run_timeout_s = seconds; }
|
||||
|
||||
int os_get_run_timeout(void) { return g_run_timeout_s; }
|
||||
|
||||
static int run_timeout_s(void) {
|
||||
int os_run_timeout_effective(void) {
|
||||
if (g_run_timeout_s > 0)
|
||||
return g_run_timeout_s;
|
||||
const char *e = getenv("CST_SHELL_TIMEOUT");
|
||||
@@ -414,8 +471,8 @@ double os_now_ms(void) {
|
||||
}
|
||||
|
||||
/* Append to capture buffer, keeping at most max_out bytes. */
|
||||
static int cap_append(char **buf, size_t *len, size_t *cap, size_t max_out,
|
||||
const char *data, size_t n, int *truncated) {
|
||||
int os_cap_append(char **buf, size_t *len, size_t *cap, size_t max_out,
|
||||
const char *data, size_t n, int *truncated) {
|
||||
if (*len >= max_out) {
|
||||
*truncated = 1;
|
||||
return 0;
|
||||
@@ -440,7 +497,7 @@ static int cap_append(char **buf, size_t *len, size_t *cap, size_t max_out,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cap_note(char **buf, size_t *len, size_t *cap, const char *note) {
|
||||
void os_cap_note(char **buf, size_t *len, size_t *cap, const char *note) {
|
||||
size_t n = strlen(note);
|
||||
if (*len + n + 1 > *cap) {
|
||||
char *nb = realloc(*buf, *len + n + 1);
|
||||
@@ -453,154 +510,7 @@ static void cap_note(char **buf, size_t *len, size_t *cap, const char *note) {
|
||||
*len += n;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int os_run_capture(const char *cwd, const char *const argv[], size_t max_out,
|
||||
os_run_result_t *r) {
|
||||
if (!argv || !argv[0] || !r)
|
||||
return -1;
|
||||
if (max_out == 0)
|
||||
max_out = 512 * 1024;
|
||||
memset(r, 0, sizeof(*r));
|
||||
|
||||
char cmdline[8192];
|
||||
size_t pos = 0;
|
||||
cmdline[0] = 0;
|
||||
for (int i = 0; argv[i]; i++) {
|
||||
const char *a = argv[i];
|
||||
int need_q = strchr(a, ' ') || strchr(a, '\t') || strchr(a, '"');
|
||||
if (i && pos + 1 < sizeof(cmdline))
|
||||
cmdline[pos++] = ' ';
|
||||
if (need_q) {
|
||||
if (pos + 1 < sizeof(cmdline))
|
||||
cmdline[pos++] = '"';
|
||||
for (const char *p = a; *p && pos + 2 < sizeof(cmdline); p++) {
|
||||
if (*p == '"') {
|
||||
cmdline[pos++] = '\\';
|
||||
cmdline[pos++] = '"';
|
||||
} else {
|
||||
cmdline[pos++] = *p;
|
||||
}
|
||||
}
|
||||
if (pos + 1 < sizeof(cmdline))
|
||||
cmdline[pos++] = '"';
|
||||
} else {
|
||||
size_t L = strlen(a);
|
||||
if (pos + L >= sizeof(cmdline))
|
||||
break;
|
||||
memcpy(cmdline + pos, a, L);
|
||||
pos += L;
|
||||
}
|
||||
cmdline[pos] = 0;
|
||||
}
|
||||
|
||||
SECURITY_ATTRIBUTES sa = {0};
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
HANDLE rd = NULL, wr = NULL;
|
||||
if (!CreatePipe(&rd, &wr, &sa, 0))
|
||||
return -1;
|
||||
SetHandleInformation(rd, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
STARTUPINFOA si = {0};
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
si.hStdOutput = wr;
|
||||
si.hStdError = wr;
|
||||
HANDLE nul = CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
&sa, OPEN_EXISTING, 0, NULL);
|
||||
si.hStdInput = nul != INVALID_HANDLE_VALUE ? nul : GetStdHandle(STD_INPUT_HANDLE);
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
char *cmd_mut = _strdup(cmdline);
|
||||
if (!cmd_mut) {
|
||||
CloseHandle(rd);
|
||||
CloseHandle(wr);
|
||||
return -1;
|
||||
}
|
||||
BOOL ok = CreateProcessA(NULL, cmd_mut, NULL, NULL, TRUE, 0, NULL,
|
||||
cwd && cwd[0] ? cwd : NULL, &si, &pi);
|
||||
free(cmd_mut);
|
||||
CloseHandle(wr);
|
||||
if (nul != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(nul);
|
||||
if (!ok) {
|
||||
CloseHandle(rd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t cap = 4096, len = 0;
|
||||
char *buf = malloc(cap);
|
||||
if (!buf) {
|
||||
CloseHandle(rd);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return -1;
|
||||
}
|
||||
buf[0] = 0;
|
||||
int truncated = 0, killed = 0;
|
||||
double deadline = os_now_ms() + run_timeout_s() * 1000.0;
|
||||
for (;;) {
|
||||
DWORD avail = 0;
|
||||
if (!PeekNamedPipe(rd, NULL, 0, NULL, &avail, NULL))
|
||||
break; /* writer closed */
|
||||
if (avail > 0) {
|
||||
char tmp[4096];
|
||||
DWORD n = 0;
|
||||
if (!ReadFile(rd, tmp, sizeof(tmp), &n, NULL) || n == 0)
|
||||
break;
|
||||
cap_append(&buf, &len, &cap, max_out, tmp, n, &truncated);
|
||||
continue;
|
||||
}
|
||||
if (WaitForSingleObject(pi.hProcess, 50) == WAIT_OBJECT_0) {
|
||||
/* drain what is left */
|
||||
while (PeekNamedPipe(rd, NULL, 0, NULL, &avail, NULL) && avail > 0) {
|
||||
char tmp[4096];
|
||||
DWORD n = 0;
|
||||
if (!ReadFile(rd, tmp, sizeof(tmp), &n, NULL) || n == 0)
|
||||
break;
|
||||
cap_append(&buf, &len, &cap, max_out, tmp, n, &truncated);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((g_run_cancel && *g_run_cancel) || os_now_ms() > deadline) {
|
||||
killed = (g_run_cancel && *g_run_cancel) ? 2 : 1;
|
||||
TerminateProcess(pi.hProcess, killed == 1 ? 124 : 130);
|
||||
break;
|
||||
}
|
||||
}
|
||||
CloseHandle(rd);
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
DWORD code = 1;
|
||||
GetExitCodeProcess(pi.hProcess, &code);
|
||||
if (truncated)
|
||||
cap_note(&buf, &len, &cap, "\n...(output truncated)\n");
|
||||
if (killed == 1) {
|
||||
char note[96];
|
||||
snprintf(note, sizeof(note), "\n[cst: killed after %ds timeout]\n",
|
||||
run_timeout_s());
|
||||
cap_note(&buf, &len, &cap, note);
|
||||
code = 124;
|
||||
} else if (killed == 2) {
|
||||
cap_note(&buf, &len, &cap, "\n[cst: cancelled]\n");
|
||||
code = 130;
|
||||
}
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
buf[len] = 0;
|
||||
r->out = buf;
|
||||
r->out_len = len;
|
||||
r->exit_code = (int)code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int os_run_capture_ex(const char *cwd, const char *const argv[],
|
||||
size_t max_out, os_run_result_t *r,
|
||||
os_child_hook_fn child_hook, void *ud) {
|
||||
(void)child_hook;
|
||||
(void)ud;
|
||||
return os_run_capture(cwd, argv, max_out, r);
|
||||
}
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
static void cloexec(int fd) {
|
||||
int fl = fcntl(fd, F_GETFD);
|
||||
if (fl >= 0)
|
||||
@@ -685,7 +595,7 @@ int os_run_capture_ex(const char *cwd, const char *const argv[],
|
||||
int truncated = 0, killed = 0;
|
||||
int status = 0, reaped = 0, bg_killed = 0;
|
||||
double reaped_at = 0, killed_at = 0;
|
||||
double deadline = os_now_ms() + run_timeout_s() * 1000.0;
|
||||
double deadline = os_now_ms() + os_run_timeout_effective() * 1000.0;
|
||||
for (;;) {
|
||||
struct pollfd pfd = {pipefd[0], POLLIN, 0};
|
||||
int pr = poll(&pfd, 1, 100);
|
||||
@@ -697,7 +607,7 @@ int os_run_capture_ex(const char *cwd, const char *const argv[],
|
||||
if (n == 0)
|
||||
break; /* every writer closed */
|
||||
if (n > 0)
|
||||
cap_append(&buf, &len, &cap, max_out, tmp, (size_t)n, &truncated);
|
||||
os_cap_append(&buf, &len, &cap, max_out, tmp, (size_t)n, &truncated);
|
||||
else if (errno != EINTR && errno != EAGAIN)
|
||||
break;
|
||||
}
|
||||
@@ -733,9 +643,9 @@ int os_run_capture_ex(const char *cwd, const char *const argv[],
|
||||
waitpid(pid, &status, 0);
|
||||
}
|
||||
if (truncated)
|
||||
cap_note(&buf, &len, &cap, "\n...(output truncated)\n");
|
||||
os_cap_note(&buf, &len, &cap, "\n...(output truncated)\n");
|
||||
if (bg_killed)
|
||||
cap_note(&buf, &len, &cap,
|
||||
os_cap_note(&buf, &len, &cap,
|
||||
"\n[cst: background processes left running were stopped]\n");
|
||||
r->out = buf;
|
||||
r->out_len = len;
|
||||
@@ -743,11 +653,11 @@ int os_run_capture_ex(const char *cwd, const char *const argv[],
|
||||
if (killed == 1) {
|
||||
char note[96];
|
||||
snprintf(note, sizeof(note), "\n[cst: killed after %ds timeout]\n",
|
||||
run_timeout_s());
|
||||
cap_note(&r->out, &r->out_len, &cap, note);
|
||||
os_run_timeout_effective());
|
||||
os_cap_note(&r->out, &r->out_len, &cap, note);
|
||||
r->exit_code = 124;
|
||||
} else if (killed == 2) {
|
||||
cap_note(&r->out, &r->out_len, &cap, "\n[cst: cancelled]\n");
|
||||
os_cap_note(&r->out, &r->out_len, &cap, "\n[cst: cancelled]\n");
|
||||
r->exit_code = 130;
|
||||
}
|
||||
return 0;
|
||||
@@ -759,40 +669,64 @@ int os_run_shell_c(const char *cwd, const char *cmd, size_t max_out,
|
||||
return os_run_shell_ex(cwd, cmd, max_out, r, NULL, NULL);
|
||||
}
|
||||
|
||||
/* busybox: busybox sh -c cmd ; /bin/sh: sh -c cmd. argv needs 5 slots and
|
||||
* points into shell. */
|
||||
static int shell_argv(char *shell, size_t shell_sz, const char *cmd,
|
||||
const char *argv[5]) {
|
||||
if (!cmd || os_resolve_shell(shell, shell_sz) != 0)
|
||||
return -1;
|
||||
const char *base = shell;
|
||||
const char *s = strrchr(shell, '/');
|
||||
#ifdef _WIN32
|
||||
const char *s2 = strrchr(shell, '\\');
|
||||
if (s2 && (!s || s2 > s))
|
||||
s = s2;
|
||||
#endif
|
||||
if (s)
|
||||
base = s + 1;
|
||||
int i = 0;
|
||||
argv[i++] = shell;
|
||||
if (strncmp(base, "busybox", 7) == 0)
|
||||
argv[i++] = "sh";
|
||||
argv[i++] = "-c";
|
||||
argv[i++] = cmd;
|
||||
argv[i] = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int os_run_shell_ex(const char *cwd, const char *cmd, size_t max_out,
|
||||
os_run_result_t *r, os_child_hook_fn child_hook,
|
||||
void *ud) {
|
||||
if (!cmd || !r)
|
||||
return -1;
|
||||
char shell[OS_PATH_MAX];
|
||||
if (os_resolve_shell(shell, sizeof(shell)) != 0)
|
||||
const char *argv[5];
|
||||
if (!r || shell_argv(shell, sizeof(shell), cmd, argv) != 0)
|
||||
return -1;
|
||||
|
||||
/* busybox: busybox sh -c cmd ; /bin/sh: sh -c cmd */
|
||||
int is_busybox = 0;
|
||||
{
|
||||
const char *base = shell;
|
||||
const char *s = strrchr(shell, '/');
|
||||
#ifdef _WIN32
|
||||
const char *s2 = strrchr(shell, '\\');
|
||||
if (s2 && (!s || s2 > s))
|
||||
s = s2;
|
||||
#endif
|
||||
if (s)
|
||||
base = s + 1;
|
||||
if (strncmp(base, "busybox", 7) == 0)
|
||||
is_busybox = 1;
|
||||
}
|
||||
|
||||
if (is_busybox) {
|
||||
const char *argv[] = {shell, "sh", "-c", cmd, NULL};
|
||||
return os_run_capture_ex(cwd, argv, max_out, r, child_hook, ud);
|
||||
} else {
|
||||
const char *argv[] = {shell, "-c", cmd, NULL};
|
||||
return os_run_capture_ex(cwd, argv, max_out, r, child_hook, ud);
|
||||
}
|
||||
return os_run_capture_ex(cwd, argv, max_out, r, child_hook, ud);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int os_run_shell_token(const char *cwd, const char *cmd, size_t max_out,
|
||||
os_run_result_t *r, void *token,
|
||||
const char *const env_add[]) {
|
||||
char shell[OS_PATH_MAX];
|
||||
const char *argv[5];
|
||||
if (!r || shell_argv(shell, sizeof(shell), cmd, argv) != 0)
|
||||
return -1;
|
||||
return os_run_capture_token(cwd, argv, max_out, r, token, env_add);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
int os_console_init(void) { return 0; }
|
||||
|
||||
char *os_output_to_utf8(const char *s, size_t n, size_t *out_len) {
|
||||
(void)s;
|
||||
(void)n;
|
||||
(void)out_len;
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void os_localtime(const time_t *t, struct tm *out) {
|
||||
if (!t || !out)
|
||||
return;
|
||||
@@ -811,16 +745,13 @@ int os_getpid(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
int os_chmod_private(const char *path) {
|
||||
if (!path)
|
||||
return -1;
|
||||
#ifdef _WIN32
|
||||
(void)path;
|
||||
return 0;
|
||||
#else
|
||||
return chmod(path, 0600);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
os_task_fn fn;
|
||||
@@ -905,133 +836,7 @@ int os_cancel_requested(void) { return g_run_cancel && *g_run_cancel; }
|
||||
|
||||
volatile int *os_get_cancel_flag(void) { return g_run_cancel; }
|
||||
|
||||
#ifdef _WIN32
|
||||
static void win_quote_arg(char *out, size_t out_sz, size_t *pos,
|
||||
const char *a) {
|
||||
int need_q = !a[0] || strpbrk(a, " \t\"") != NULL;
|
||||
if (need_q && *pos + 1 < out_sz)
|
||||
out[(*pos)++] = '"';
|
||||
for (const char *p = a; *p && *pos + 2 < out_sz; p++) {
|
||||
if (*p == '"')
|
||||
out[(*pos)++] = '\\';
|
||||
out[(*pos)++] = *p;
|
||||
}
|
||||
if (need_q && *pos + 1 < out_sz)
|
||||
out[(*pos)++] = '"';
|
||||
out[*pos] = 0;
|
||||
}
|
||||
|
||||
int os_proc_spawn(const char *cwd, const char *const argv[],
|
||||
const char *const env_add[], const char *stderr_path,
|
||||
os_proc_t *p) {
|
||||
memset(p, 0, sizeof(*p));
|
||||
if (!argv || !argv[0])
|
||||
return -1;
|
||||
char cmdline[16384];
|
||||
size_t pos = 0;
|
||||
cmdline[0] = 0;
|
||||
for (int i = 0; argv[i]; i++) {
|
||||
if (i && pos + 1 < sizeof(cmdline))
|
||||
cmdline[pos++] = ' ';
|
||||
win_quote_arg(cmdline, sizeof(cmdline), &pos, argv[i]);
|
||||
}
|
||||
for (int i = 0; env_add && env_add[i]; i++)
|
||||
_putenv(env_add[i]); /* inherited by the child */
|
||||
SECURITY_ATTRIBUTES sa = {0};
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
HANDLE in_r = NULL, in_w = NULL, out_r = NULL, out_w = NULL;
|
||||
if (!CreatePipe(&in_r, &in_w, &sa, 0))
|
||||
return -1;
|
||||
if (!CreatePipe(&out_r, &out_w, &sa, 0)) {
|
||||
CloseHandle(in_r);
|
||||
CloseHandle(in_w);
|
||||
return -1;
|
||||
}
|
||||
SetHandleInformation(in_w, HANDLE_FLAG_INHERIT, 0);
|
||||
SetHandleInformation(out_r, HANDLE_FLAG_INHERIT, 0);
|
||||
HANDLE err = CreateFileA(stderr_path ? stderr_path : "NUL", GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,
|
||||
CREATE_ALWAYS, 0, NULL);
|
||||
STARTUPINFOA si = {0};
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
si.hStdInput = in_r;
|
||||
si.hStdOutput = out_w;
|
||||
si.hStdError = err != INVALID_HANDLE_VALUE ? err : out_w;
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
BOOL ok = CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, CREATE_NO_WINDOW,
|
||||
NULL, cwd && cwd[0] ? cwd : NULL, &si, &pi);
|
||||
CloseHandle(in_r);
|
||||
CloseHandle(out_w);
|
||||
if (err != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(err);
|
||||
if (!ok) {
|
||||
CloseHandle(in_w);
|
||||
CloseHandle(out_r);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(pi.hThread);
|
||||
p->proc = pi.hProcess;
|
||||
p->in_w = in_w;
|
||||
p->out_r = out_r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int os_proc_write(os_proc_t *p, const char *data, size_t n) {
|
||||
while (n > 0) {
|
||||
DWORD w = 0;
|
||||
if (!p->in_w || !WriteFile((HANDLE)p->in_w, data, (DWORD)n, &w, NULL) ||
|
||||
w == 0)
|
||||
return -1;
|
||||
data += w;
|
||||
n -= w;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
long os_proc_read(os_proc_t *p, char *buf, size_t n, int timeout_ms) {
|
||||
if (!p->out_r)
|
||||
return -1;
|
||||
double deadline = os_now_ms() + timeout_ms;
|
||||
for (;;) {
|
||||
DWORD avail = 0;
|
||||
if (!PeekNamedPipe((HANDLE)p->out_r, NULL, 0, NULL, &avail, NULL))
|
||||
return GetLastError() == ERROR_BROKEN_PIPE ? 0 : -1;
|
||||
if (avail > 0)
|
||||
break;
|
||||
if (os_now_ms() >= deadline)
|
||||
return -2;
|
||||
Sleep(10);
|
||||
}
|
||||
DWORD r = 0;
|
||||
if (!ReadFile((HANDLE)p->out_r, buf, (DWORD)n, &r, NULL))
|
||||
return GetLastError() == ERROR_BROKEN_PIPE ? 0 : -1;
|
||||
return (long)r;
|
||||
}
|
||||
|
||||
void os_proc_close_stdin(os_proc_t *p) {
|
||||
if (p->in_w) {
|
||||
CloseHandle((HANDLE)p->in_w);
|
||||
p->in_w = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void os_proc_kill(os_proc_t *p) {
|
||||
os_proc_close_stdin(p);
|
||||
if (p->proc) {
|
||||
if (WaitForSingleObject((HANDLE)p->proc, 500) != WAIT_OBJECT_0)
|
||||
TerminateProcess((HANDLE)p->proc, 1);
|
||||
WaitForSingleObject((HANDLE)p->proc, 2000);
|
||||
CloseHandle((HANDLE)p->proc);
|
||||
p->proc = NULL;
|
||||
}
|
||||
if (p->out_r) {
|
||||
CloseHandle((HANDLE)p->out_r);
|
||||
p->out_r = NULL;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
int os_proc_spawn(const char *cwd, const char *const argv[],
|
||||
const char *const env_add[], const char *stderr_path,
|
||||
os_proc_t *p) {
|
||||
|
||||
Reference in New Issue
Block a user