build: finish cross-platform release tooling

This commit is contained in:
WHC
2026-10-10 16:21:00 +08:00
parent 9bf7707912
commit 50abcf1a91
29 changed files with 973 additions and 230 deletions
+61 -29
View File
@@ -22,43 +22,75 @@
#define ERRBODY_MAX 4096
#define LLM_MAX_RETRIES 3
/* Point libcurl at a CA bundle on Windows.
static int ca_file_ok(const char *path) {
if (!path || !path[0])
return 0;
FILE *f = fopen(path, "rb");
if (!f)
return 0;
fclose(f);
return 1;
}
/* Point libcurl at a CA bundle.
*
* An OpenSSL-based libcurl keeps the path it was built with -- MSYS2's is
* "/mingw64/etc/ssl/certs/ca-bundle.crt", which resolves to nothing once the
* binary runs outside an MSYS2 shell, and that build ignores CURL_CA_BUNDLE.
* Every HTTPS request then dies with CURLE_SSL_CACERT_BADFILE (77).
* libcurl keeps the bundle path it was built with. On Windows an OpenSSL
* build carries MSYS2's "/mingw64/etc/ssl/certs/ca-bundle.crt"; the static
* Linux release is built on one distro and runs on all of them, whose bundles
* live in different places. A path that does not exist fails every HTTPS
* request with CURLE_SSL_CACERT_BADFILE (77).
*
* Order: $CST_CA_BUNDLE, then ca-bundle.crt beside the executable. If neither
* exists nothing is set, so a libcurl linked against Schannel (the Windows
* certificate store) behaves exactly as before. */
static void llm_apply_ca_bundle(CURL *curl) {
#if defined(_WIN32)
* Order: $CST_CA_BUNDLE; on Windows ca-bundle.crt beside the executable (if
* absent nothing is set, so Schannel uses the certificate store); elsewhere
* $SSL_CERT_FILE, libcurl's own default if that file exists, then the usual
* distro locations. Decided once per process. */
void cst_curl_apply_ca(void *curl) {
static char ca[OS_PATH_MAX];
static int checked;
if (!checked) {
checked = 1;
static atomic_int state; /* 0 unchecked, 1 checking, 2 done */
int expect = 0;
if (atomic_compare_exchange_strong(&state, &expect, 1)) {
const char *env = getenv("CST_CA_BUNDLE");
if (env && env[0]) {
if (ca_file_ok(env)) {
snprintf(ca, sizeof(ca), "%s", env);
} else {
char dir[OS_PATH_MAX];
if (os_exe_dir(dir, sizeof(dir)) == 0)
os_path_join(ca, sizeof(ca), dir, "ca-bundle.crt");
}
if (ca[0]) {
FILE *f = fopen(ca, "rb");
if (f)
fclose(f);
#if defined(_WIN32)
char dir[OS_PATH_MAX], p[OS_PATH_MAX];
if (os_exe_dir(dir, sizeof(dir)) == 0 &&
os_path_join(p, sizeof(p), dir, "ca-bundle.crt") == 0 &&
ca_file_ok(p))
snprintf(ca, sizeof(ca), "%s", p);
#else
static const char *const known[] = {
"/etc/ssl/certs/ca-certificates.crt", /* Debian, Ubuntu, Arch, Alpine */
"/etc/pki/tls/certs/ca-bundle.crt", /* Fedora, RHEL */
"/etc/ssl/cert.pem", /* macOS, BSD, Alpine */
"/etc/ssl/ca-bundle.pem", /* openSUSE */
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
};
const char *pick = NULL;
const char *sslenv = getenv("SSL_CERT_FILE");
const curl_version_info_data *vi = curl_version_info(CURLVERSION_NOW);
if (ca_file_ok(sslenv))
pick = sslenv;
else if (vi && ca_file_ok(vi->cainfo))
pick = NULL; /* libcurl's built-in default already works */
else
ca[0] = '\0';
for (size_t i = 0; i < sizeof(known) / sizeof(known[0]); i++)
if (ca_file_ok(known[i])) {
pick = known[i];
break;
}
if (pick)
snprintf(ca, sizeof(ca), "%s", pick);
#endif
}
atomic_store(&state, 2);
} else {
while (atomic_load(&state) != 2)
;
}
if (ca[0])
curl_easy_setopt(curl, CURLOPT_CAINFO, ca);
#else
(void)curl;
#endif
curl_easy_setopt((CURL *)curl, CURLOPT_CAINFO, ca);
}
#include "cst_attach.h"
@@ -988,7 +1020,7 @@ int cst_llm_chat_stream(const cst_llm_request_t *req, char *err,
ret = -1;
break;
}
llm_apply_ca_bundle(curl);
cst_curl_apply_ca(curl);
sse_reset(&s);
s.curl = curl;
if (rec_dir && rec_dir[0]) {
@@ -1233,7 +1265,7 @@ int cst_llm_list_models_meta(const char *api_base, const char *api_key,
snprintf(err, err_sz, "curl init failed");
return -1;
}
llm_apply_ca_bundle(curl);
cst_curl_apply_ca(curl);
char url[1024];
size_t bl = strlen(api_base);
while (bl && api_base[bl - 1] == '/')
+4 -5
View File
@@ -37,11 +37,6 @@
#define NET_SEND_TIMEOUT_MS 20000
static void set_err(char *err, size_t err_sz, const char *what) {
if (err && err_sz)
snprintf(err, err_sz, "%s", what);
}
#ifdef _WIN32
static void set_err_win(char *err, size_t err_sz, const char *what) {
if (err && err_sz)
@@ -59,6 +54,10 @@ static int net_last_conn_reset(void) {
return e == WSAECONNRESET || e == WSAECONNABORTED || e == WSAENOTSOCK;
}
#else
static void set_err(char *err, size_t err_sz, const char *what) {
if (err && err_sz)
snprintf(err, err_sz, "%s", what);
}
#define SET_ERR(e, n, w) set_err(e, n, w)
#define NET_FD(s) ((int)(s))
static void net_close_raw(os_socket_t s) { close(s); }
+71 -12
View File
@@ -13,6 +13,7 @@
#include "os_internal.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -503,19 +504,74 @@ int os_resolve_shell(char *out, size_t out_sz) {
#endif
}
/* Per thread: parallel tool runs and sub-agents each watch their own flag. */
static _Thread_local volatile int *g_run_cancel;
static _Thread_local int g_run_timeout_s;
/* Per thread: parallel tool runs and sub-agents each watch their own flag.
* Thread keys rather than _Thread_local, which tcc does not implement; each
* value fits in the key's pointer slot, so nothing is allocated. */
#ifdef _WIN32
static INIT_ONCE g_run_once = INIT_ONCE_STATIC_INIT;
static DWORD g_key_cancel = TLS_OUT_OF_INDEXES, g_key_timeout = TLS_OUT_OF_INDEXES;
static BOOL CALLBACK run_keys_init(PINIT_ONCE once, PVOID param, PVOID *ctx) {
(void)once;
(void)param;
(void)ctx;
g_key_cancel = TlsAlloc();
g_key_timeout = TlsAlloc();
return TRUE;
}
static void *run_key_get(DWORD *key) {
InitOnceExecuteOnce(&g_run_once, run_keys_init, NULL, NULL);
if (*key == TLS_OUT_OF_INDEXES)
return NULL;
DWORD err = GetLastError(); /* TlsGetValue clears it */
void *v = TlsGetValue(*key);
SetLastError(err);
return v;
}
static void run_key_set(DWORD *key, void *v) {
InitOnceExecuteOnce(&g_run_once, run_keys_init, NULL, NULL);
if (*key != TLS_OUT_OF_INDEXES)
TlsSetValue(*key, v);
}
#else
static pthread_once_t g_run_once = PTHREAD_ONCE_INIT;
static pthread_key_t g_key_cancel, g_key_timeout;
static int g_run_keys_ok;
static void run_keys_init(void) {
g_run_keys_ok = pthread_key_create(&g_key_cancel, NULL) == 0 &&
pthread_key_create(&g_key_timeout, NULL) == 0;
}
static void *run_key_get(pthread_key_t *key) {
pthread_once(&g_run_once, run_keys_init);
return g_run_keys_ok ? pthread_getspecific(*key) : NULL;
}
static void run_key_set(pthread_key_t *key, void *v) {
pthread_once(&g_run_once, run_keys_init);
if (g_run_keys_ok)
pthread_setspecific(*key, v);
}
#endif
void os_set_cancel_flag(volatile int *flag) { g_run_cancel = flag; }
static volatile int *run_cancel(void) {
return (volatile int *)run_key_get(&g_key_cancel);
}
static int run_timeout(void) {
return (int)(intptr_t)run_key_get(&g_key_timeout);
}
void os_set_run_timeout(int seconds) { g_run_timeout_s = seconds; }
void os_set_cancel_flag(volatile int *flag) {
run_key_set(&g_key_cancel, (void *)(uintptr_t)flag);
}
int os_get_run_timeout(void) { return g_run_timeout_s; }
void os_set_run_timeout(int seconds) {
run_key_set(&g_key_timeout, (void *)(intptr_t)seconds);
}
int os_get_run_timeout(void) { return run_timeout(); }
int os_run_timeout_effective(void) {
if (g_run_timeout_s > 0)
return g_run_timeout_s;
int t = run_timeout();
if (t > 0)
return t;
const char *e = getenv("CST_SHELL_TIMEOUT");
if (e && atoi(e) > 0)
return atoi(e);
@@ -693,8 +749,8 @@ int os_run_capture_ex(const char *cwd, const char *const argv[],
}
/* Checked on every pass, so a chatty command still stops promptly. */
if (!reaped && !killed &&
((g_run_cancel && *g_run_cancel) || (deadline > 0 && now > deadline))) {
killed = (g_run_cancel && *g_run_cancel) ? 2 : 1;
(os_cancel_requested() || (deadline > 0 && now > deadline))) {
killed = os_cancel_requested() ? 2 : 1;
killed_at = now;
kill(-pid, SIGTERM);
}
@@ -900,9 +956,12 @@ void os_run_parallel(os_task_fn fn, void *const args[], int n) {
free(tasks);
}
int os_cancel_requested(void) { return g_run_cancel && *g_run_cancel; }
int os_cancel_requested(void) {
volatile int *c = run_cancel();
return c && *c;
}
volatile int *os_get_cancel_flag(void) { return g_run_cancel; }
volatile int *os_get_cancel_flag(void) { return run_cancel(); }
#ifndef _WIN32
int os_proc_spawn(const char *cwd, const char *const argv[],
+8 -2
View File
@@ -28,11 +28,17 @@ int cst_settings_load(cst_settings_t *s) {
FILE *fp = fopen(path, "rb");
if (!fp)
return -1;
static _Thread_local char buf[65536];
size_t n = fread(buf, 1, sizeof(buf) - 1, fp);
enum { SETTINGS_MAX = 65536 };
char *buf = malloc(SETTINGS_MAX);
if (!buf) {
fclose(fp);
return -1;
}
size_t n = fread(buf, 1, SETTINGS_MAX - 1, fp);
fclose(fp);
buf[n] = 0;
cJSON *root = cJSON_Parse(buf + cst_bom_len(buf, n));
free(buf);
if (!cJSON_IsObject(root)) {
cJSON_Delete(root);
return -1;
+3 -2
View File
@@ -659,9 +659,10 @@ static int plan_patch(const char *ws, const char *patch, patch_plan_t *pl,
pl->full[i] = malloc(OS_PATH_MAX);
if (!pl->full[i] ||
cst_sandbox_resolve(ws, target, pl->full[i], OS_PATH_MAX) != 0) {
if (dry && pl->full[i] && !pl->del[i] && is_new) {
if (dry && pl->full[i] && !pl->del[i] && is_new &&
strlen(target) < OS_PATH_MAX) {
/* parent dirs do not exist yet; the write would create them */
snprintf(pl->full[i], OS_PATH_MAX, "%s", target);
memcpy(pl->full[i], target, strlen(target) + 1);
} else {
snprintf(err, err_sz, "error: path outside workspace: %.200s",
target);
+1
View File
@@ -215,6 +215,7 @@ int cst_tool_web_fetch(const cst_tool_ctx_t *ctx, const ctl_tool_call_t *c,
memset(&f, 0, sizeof(f));
char errbuf[CURL_ERROR_SIZE];
errbuf[0] = 0;
cst_curl_apply_ca(curl);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
+2
View File
@@ -152,6 +152,7 @@ static int cst_clock_gettime_mono(struct timespec *ts) {
#ifdef NCURSES_VERSION
static int g_kitty_on;
#ifndef _WIN32
/* Bind the reports and ask whether the terminal speaks the protocol; the
* flags are pushed once the reply arrives (see tui_wget_wch). */
static void kitty_init(void) {
@@ -182,6 +183,7 @@ static void kitty_restore(void) {
g_kitty_on = 0;
}
}
#endif /* !_WIN32 */
#endif
/* wget_wch() that turns kitty key reports back into legacy input. */