#ifndef CST_OS_COMPAT_H #define CST_OS_COMPAT_H #include #include #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); /* Point TMPDIR at os_temp_dir() so child processes use the same writable * directory. Call once at startup, before any threads. */ void os_temp_dir_export(void); /* 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 → /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 and their own process group. They run * with no time limit unless one is asked for (CST_SHELL_TIMEOUT seconds, or * os_set_run_timeout()); either way a non-zero *flag cancels them. * Exit code 124 = killed by that 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 /* 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 */