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:
+209
@@ -4,6 +4,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#include <wctype.h>
|
||||
|
||||
static int buf_reserve(cst_buf_t *b, size_t extra) {
|
||||
if (b->len + extra + 1 <= b->cap)
|
||||
@@ -153,6 +155,213 @@ char *cst_utf8_sanitize(const char *s, size_t n, size_t *out_len) {
|
||||
return out;
|
||||
}
|
||||
|
||||
int cst_utf8_valid(const char *s, size_t n) {
|
||||
const unsigned char *p = (const unsigned char *)s;
|
||||
for (size_t i = 0; i < n;) {
|
||||
size_t k = utf8_seq_ok(p + i, n - i);
|
||||
if (k == 0)
|
||||
return 0;
|
||||
i += k;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t cst_bom_len(const char *s, size_t n) {
|
||||
return n >= 3 && memcmp(s, "\xEF\xBB\xBF", 3) == 0 ? 3 : 0;
|
||||
}
|
||||
|
||||
void cst_strip_bom(char *s, size_t *len) {
|
||||
if (!s)
|
||||
return;
|
||||
size_t n = len ? *len : strlen(s);
|
||||
if (!cst_bom_len(s, n))
|
||||
return;
|
||||
memmove(s, s + 3, n - 3 + 1);
|
||||
if (len)
|
||||
*len = n - 3;
|
||||
}
|
||||
|
||||
size_t cst_crlf_to_lf(char *s, size_t n) {
|
||||
size_t o = 0;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
if (!(s[i] == '\r' && i + 1 < n && s[i + 1] == '\n'))
|
||||
s[o++] = s[i];
|
||||
if (o < n)
|
||||
s[o] = 0;
|
||||
return o;
|
||||
}
|
||||
|
||||
int cst_text_is_crlf(const char *s, size_t n) {
|
||||
int crlf = 0;
|
||||
for (size_t i = 0; s && i < n; i++) {
|
||||
if (s[i] != '\n')
|
||||
continue;
|
||||
if (i == 0 || s[i - 1] != '\r')
|
||||
return 0;
|
||||
crlf = 1;
|
||||
}
|
||||
return crlf;
|
||||
}
|
||||
|
||||
char *cst_text_restyle(const char *s, size_t n, int crlf, int bom,
|
||||
size_t *out_len) {
|
||||
size_t skip = cst_bom_len(s, n), extra = bom ? 3 : 0;
|
||||
for (size_t i = skip; crlf && i < n; i++)
|
||||
extra += s[i] == '\n' && (i == skip || s[i - 1] != '\r');
|
||||
char *o = malloc(n - skip + extra + 1);
|
||||
if (!o)
|
||||
return NULL;
|
||||
size_t k = 0;
|
||||
if (bom) {
|
||||
memcpy(o, "\xEF\xBB\xBF", 3);
|
||||
k = 3;
|
||||
}
|
||||
for (size_t i = skip; i < n; i++) {
|
||||
if (crlf && s[i] == '\n' && (i == skip || s[i - 1] != '\r'))
|
||||
o[k++] = '\r';
|
||||
o[k++] = s[i];
|
||||
}
|
||||
o[k] = 0;
|
||||
if (out_len)
|
||||
*out_len = k;
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Next code point of a UTF-8 string; a stray byte stands for itself. */
|
||||
static int utf8_next(const char **ps) {
|
||||
const unsigned char *p = (const unsigned char *)*ps;
|
||||
size_t left = 0;
|
||||
while (left < 4 && p[left])
|
||||
left++;
|
||||
size_t k = utf8_seq_ok(p, left);
|
||||
if (k <= 1) {
|
||||
*ps += 1;
|
||||
return p[0];
|
||||
}
|
||||
int c = p[0] & (0x7F >> k);
|
||||
for (size_t i = 1; i < k; i++)
|
||||
c = (c << 6) | (p[i] & 0x3F);
|
||||
*ps += k;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int fold_cp(int c) {
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return c + 32;
|
||||
if (c > 0x7F && c <= 0xFFFF)
|
||||
return (int)towlower((wint_t)c);
|
||||
return c;
|
||||
}
|
||||
|
||||
/* p points just past '['. 1/0 = (no) match with *pp moved past ']';
|
||||
* -1 = no closing ']' (the '[' is then a literal). */
|
||||
static int fnm_bracket(const char **pp, int c, int fold) {
|
||||
const char *p = *pp;
|
||||
int neg = 0, hit = 0;
|
||||
if (*p == '!' || *p == '^') {
|
||||
neg = 1;
|
||||
p++;
|
||||
}
|
||||
int fc = fold ? fold_cp(c) : c;
|
||||
for (int first = 1; *p && (*p != ']' || first); first = 0) {
|
||||
if (p[0] == '[' && p[1] == ':') {
|
||||
const char *e = strstr(p + 2, ":]");
|
||||
if (e) {
|
||||
char name[16];
|
||||
size_t nl = (size_t)(e - (p + 2));
|
||||
if (nl < sizeof(name)) {
|
||||
memcpy(name, p + 2, nl);
|
||||
name[nl] = 0;
|
||||
wctype_t t = wctype(name);
|
||||
if (t && c <= 0xFFFF && iswctype((wint_t)c, t))
|
||||
hit = 1;
|
||||
}
|
||||
p = e + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (*p == '\\' && p[1])
|
||||
p++;
|
||||
int lo = utf8_next(&p), hi = lo;
|
||||
if (p[0] == '-' && p[1] && p[1] != ']') {
|
||||
p++;
|
||||
if (*p == '\\' && p[1])
|
||||
p++;
|
||||
hi = utf8_next(&p);
|
||||
}
|
||||
if ((c >= lo && c <= hi) ||
|
||||
(fold && fc >= fold_cp(lo) && fc <= fold_cp(hi)))
|
||||
hit = 1;
|
||||
}
|
||||
if (*p != ']')
|
||||
return -1;
|
||||
*pp = p + 1;
|
||||
return hit != neg;
|
||||
}
|
||||
|
||||
int cst_fnmatch(const char *pat, const char *str, int flags) {
|
||||
#ifdef _WIN32
|
||||
flags |= CST_FNM_CASEFOLD;
|
||||
#endif
|
||||
int fold = (flags & CST_FNM_CASEFOLD) != 0;
|
||||
const char *p = pat, *s = str;
|
||||
const char *star_p = NULL, *star_s = NULL;
|
||||
if ((flags & CST_FNM_PERIOD) && s[0] == '.' && p[0] != '.' &&
|
||||
!(p[0] == '\\' && p[1] == '.'))
|
||||
return 1;
|
||||
while (*s) {
|
||||
if (*p == '*') {
|
||||
while (*p == '*')
|
||||
p++;
|
||||
if (!*p)
|
||||
return 0;
|
||||
star_p = p;
|
||||
star_s = s;
|
||||
continue;
|
||||
}
|
||||
const char *sn = s;
|
||||
int c = utf8_next(&sn);
|
||||
if (*p == '?') {
|
||||
p++;
|
||||
s = sn;
|
||||
continue;
|
||||
}
|
||||
if (*p == '[') {
|
||||
const char *q = p + 1;
|
||||
int r = fnm_bracket(&q, c, fold);
|
||||
if (r == 1) {
|
||||
p = q;
|
||||
s = sn;
|
||||
continue;
|
||||
}
|
||||
if (r == 0)
|
||||
goto backtrack;
|
||||
}
|
||||
{
|
||||
const char *q = p;
|
||||
if (*q == '\\' && q[1])
|
||||
q++;
|
||||
if (*q) {
|
||||
int pc = utf8_next(&q);
|
||||
if (pc == c || (fold && fold_cp(pc) == fold_cp(c))) {
|
||||
p = q;
|
||||
s = sn;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
backtrack:
|
||||
if (!star_p)
|
||||
return 1;
|
||||
utf8_next(&star_s);
|
||||
s = star_s;
|
||||
p = star_p;
|
||||
}
|
||||
while (*p == '*')
|
||||
p++;
|
||||
return *p ? 1 : 0;
|
||||
}
|
||||
|
||||
uint64_t cst_fnv1a64(uint64_t h, const void *data, size_t n) {
|
||||
const unsigned char *p = data;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
|
||||
Reference in New Issue
Block a user