build: finish cross-platform release tooling
This commit is contained in:
+61
-29
@@ -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] == '/')
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user