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
217 lines
9.0 KiB
C
217 lines
9.0 KiB
C
#ifndef CST_OS_COMPAT_H
|
|
#define CST_OS_COMPAT_H
|
|
|
|
#include <stddef.h>
|
|
#include <time.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#ifndef OS_PATH_MAX
|
|
#define OS_PATH_MAX 4096
|
|
#endif
|
|
|
|
/* --- path --- */
|
|
int os_path_is_abs(const char *p);
|
|
/* Join with native separator; out always uses '/' on Unix and '\\' on Win for
|
|
* the joiner, but os_path_norm_slashes converts to '/' for comparisons. */
|
|
int os_path_join(char *out, size_t out_sz, const char *a, const char *b);
|
|
void os_path_norm_slashes(char *p);
|
|
int os_realpath(const char *path, char *out, size_t out_sz);
|
|
/* True if path is under root (after realpath/norm). Trailing slash rules apply. */
|
|
int os_path_under(const char *root, const char *path);
|
|
|
|
/* --- directory listing / file type --- */
|
|
typedef struct os_dir os_dir_t;
|
|
typedef struct {
|
|
const char *name; /* UTF-8; valid until the next os_dir_next */
|
|
int is_dir;
|
|
int is_link; /* symlink, or a junction on Windows (not followed) */
|
|
} os_dirent_t;
|
|
os_dir_t *os_dir_open(const char *path);
|
|
/* Next entry, skipping "." and ".."; NULL at the end. */
|
|
const os_dirent_t *os_dir_next(os_dir_t *d);
|
|
void os_dir_close(os_dir_t *d);
|
|
|
|
typedef struct {
|
|
int is_dir, is_reg, is_link;
|
|
long long size;
|
|
double mtime; /* seconds since the epoch */
|
|
} os_stat_t;
|
|
/* Like lstat(2): a link is reported as a link, not followed. */
|
|
int os_lstat(const char *path, os_stat_t *st);
|
|
/* Replace `to` if it exists (atomic where the OS allows). */
|
|
int os_rename_replace(const char *from, const char *to);
|
|
|
|
/* --- dirs / config --- */
|
|
int os_mkdir(const char *path, int mode);
|
|
int os_mkdir_p(const char *path, int mode);
|
|
int os_config_dir(char *out, size_t out_sz);
|
|
int os_temp_dir(char *out, size_t out_sz);
|
|
/* Create unique temp file; writes path into out; returns fd or -1. Caller closes. */
|
|
int os_mkstemp(char *out, size_t out_sz, const char *prefix);
|
|
|
|
/* Directory containing the running executable (for bundled busybox).
|
|
* Call os_set_prog_path(argv[0]) early on Unix/Termux as fallback. */
|
|
void os_set_prog_path(const char *argv0);
|
|
int os_exe_dir(char *out, size_t out_sz);
|
|
|
|
/* Resolve shell interpreter path into out.
|
|
* Order: CST_SHELL → <exe_dir>/busybox[.exe] → /bin/sh (Unix). */
|
|
int os_resolve_shell(char *out, size_t out_sz);
|
|
|
|
/* --- process --- */
|
|
typedef struct {
|
|
char *out;
|
|
size_t out_len;
|
|
int exit_code;
|
|
} os_run_result_t;
|
|
|
|
void os_run_result_free(os_run_result_t *r);
|
|
|
|
/* Run argv[0..] (NULL-terminated) with cwd; capture stdout+stderr.
|
|
* max_out caps captured bytes (0 = default 512KiB). */
|
|
int os_run_capture(const char *cwd, const char *const argv[], size_t max_out,
|
|
os_run_result_t *r);
|
|
|
|
/* Convenience: shell -c cmd via os_resolve_shell. */
|
|
int os_run_shell_c(const char *cwd, const char *cmd, size_t max_out,
|
|
os_run_result_t *r);
|
|
|
|
/* Same, with child_hook(ud) run in the child just before exec (Unix only;
|
|
* ignored on Windows). A non-zero return aborts the child with exit 126. */
|
|
typedef int (*os_child_hook_fn)(void *ud);
|
|
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);
|
|
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);
|
|
|
|
#ifdef _WIN32
|
|
/* Windows: run under `token` (a primary token, e.g. the Low-IL sandbox
|
|
* token; NULL = ours) with env_add "KEY=VALUE" entries layered on the
|
|
* inherited environment. Every child runs in a kill-on-close Job Object, so
|
|
* timeouts and cancels end the whole process tree. */
|
|
int os_run_capture_token(const char *cwd, const char *const argv[],
|
|
size_t max_out, os_run_result_t *r, void *token,
|
|
const char *const env_add[]);
|
|
/* os_run_shell_c under token, with env_add. */
|
|
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[]);
|
|
/* "Windows 10.0.26100 x86_64" (the real version, not the compat shim's). */
|
|
void os_windows_version(char *out, size_t n);
|
|
/* Sets the mandatory-label SACL, e.g. "S:(ML;OICI;NW;;;LW)"; 0 / -1. */
|
|
int os_win_set_label(const char *path, const char *sddl);
|
|
/* The explicit (not inherited) label as SDDL, "S:" when there is none. */
|
|
int os_win_get_label(const char *path, char *out, size_t n);
|
|
/* CF_UNICODE_TEXT clipboard (the console may not do OSC 52). */
|
|
int os_clipboard_set(const char *utf8, size_t n);
|
|
#endif
|
|
|
|
/* Child processes get stdin=/dev/null, their own process group, and are
|
|
* killed after the timeout (CST_SHELL_TIMEOUT seconds, default 120) or when
|
|
* *flag becomes non-zero. Exit code 124 = timeout, 130 = cancelled. */
|
|
void os_set_cancel_flag(volatile int *flag);
|
|
volatile int *os_get_cancel_flag(void);
|
|
void os_set_run_timeout(int seconds); /* this thread; 0 = default */
|
|
int os_get_run_timeout(void);
|
|
|
|
/* Long-lived child with pipes on stdin/stdout (MCP servers). stderr goes
|
|
* to stderr_path (NULL = discarded). env_add: "KEY=VALUE" entries added to
|
|
* the inherited environment (NULL-terminated, may be NULL). */
|
|
typedef struct {
|
|
#ifdef _WIN32
|
|
void *proc, *in_w, *out_r, *job;
|
|
#else
|
|
int pid, in_fd, out_fd;
|
|
#endif
|
|
} os_proc_t;
|
|
int os_proc_spawn(const char *cwd, const char *const argv[],
|
|
const char *const env_add[], const char *stderr_path,
|
|
os_proc_t *p);
|
|
int os_proc_write(os_proc_t *p, const char *data, size_t n); /* 0 / -1 */
|
|
/* Bytes read, 0 at EOF, -1 on error, -2 when nothing came within
|
|
* timeout_ms. */
|
|
long os_proc_read(os_proc_t *p, char *buf, size_t n, int timeout_ms);
|
|
void os_proc_close_stdin(os_proc_t *p);
|
|
/* Close the pipes, end the process (group) and reap it. */
|
|
void os_proc_kill(os_proc_t *p);
|
|
/* This thread's cancel flag (os_set_cancel_flag) is raised. */
|
|
int os_cancel_requested(void);
|
|
|
|
/* Run fn(args[i]) for every i on its own thread and wait for all of them
|
|
* (falls back to running inline when a thread cannot start). */
|
|
typedef void (*os_task_fn)(void *arg);
|
|
void os_run_parallel(os_task_fn fn, void *const args[], int n);
|
|
|
|
/* Monotonic milliseconds (arbitrary epoch). */
|
|
double os_now_ms(void);
|
|
|
|
/* --- text encoding ---
|
|
* cst-agent is UTF-8 throughout. On Windows the manifest makes UTF-8 the
|
|
* process code page; os_console_init() also switches the console to UTF-8
|
|
* (restored at exit), sets a UTF-8 C locale and asks Python children for
|
|
* UTF-8. Returns -1 when the process code page is not UTF-8 (pre-1903
|
|
* Windows); no-op returning 0 elsewhere. */
|
|
int os_console_init(void);
|
|
/* Child output that is not valid UTF-8, decoded from the console/OEM/ANSI
|
|
* code page: malloc'd UTF-8, or NULL when s is already UTF-8 (or not on
|
|
* Windows). */
|
|
char *os_output_to_utf8(const char *s, size_t n, size_t *out_len);
|
|
#ifdef _WIN32
|
|
#include <wchar.h>
|
|
/* malloc'd conversions; NULL on invalid input or allocation failure */
|
|
wchar_t *os_utf8_to_wide(const char *s);
|
|
char *os_wide_to_utf8(const wchar_t *w);
|
|
#endif
|
|
|
|
/* --- misc --- */
|
|
void os_localtime(const time_t *t, struct tm *out);
|
|
int os_getpid(void);
|
|
/* 0600; on Windows a Medium no-read-up label, so Low-IL children (the
|
|
* shell sandbox) cannot read the file. */
|
|
int os_chmod_private(const char *path);
|
|
|
|
/* --- UTF-8 --------------------------------------------------------------
|
|
* Encode one code point as UTF-8, independent of the C locale.
|
|
*
|
|
* wcrtomb() follows setlocale(). On Linux the environment normally selects a
|
|
* UTF-8 locale, but on Windows the C runtime maps setlocale(LC_ALL, "") to the
|
|
* ANSI code page -- 936 (GBK) on a Chinese system -- so wcrtomb() hands back
|
|
* GBK bytes where the protocol requires UTF-8, and the model receives
|
|
* mojibake. Windows' C runtime has no ".UTF-8" locale to switch to.
|
|
*
|
|
* Returns the number of bytes written (1..4), or 0 if the code point cannot be
|
|
* encoded. Surrogate halves (as they arrive on 16-bit wchar_t platforms for
|
|
* non-BMP code points) return 0 and are skipped. */
|
|
int os_utf8_encode(unsigned long cp, char out[4]);
|
|
|
|
/* Decode one UTF-8 sequence from s (at most avail bytes).
|
|
* Returns the number of bytes consumed (1..4) and stores the code point in
|
|
* *cp, or 0 if the bytes are not a valid, complete, non-overlong sequence.
|
|
*
|
|
* The counterpart of the encoder above, and needed for the same reason:
|
|
* mbrtowc() also follows setlocale(), so on Windows it would decode UTF-8
|
|
* text as GBK and the screen would show mojibake. */
|
|
int os_utf8_decode(const char *s, size_t avail, unsigned long *cp);
|
|
|
|
/* --- libc gaps on Windows ------------------------------------------------
|
|
* MinGW-w64 provides neither strcasestr() (a GNU extension) nor
|
|
* setenv()/unsetenv() (POSIX): they are simply absent from its headers.
|
|
* src/tui.c uses all three. Implemented in os_compat.c for _WIN32 only,
|
|
* so the Unix builds keep using the libc versions. */
|
|
#if defined(_WIN32)
|
|
char *strcasestr(const char *haystack, const char *needle);
|
|
int setenv(const char *name, const char *value, int overwrite);
|
|
int unsetenv(const char *name);
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* CST_OS_COMPAT_H */
|