ci: add gitflow — dev/stage/main branches with CI workflows
- dev.yaml: build + local docker smoke test only (no push, no deploy) - stage.yaml: build + push + deploy to marketing-stage + smoke test (stops here) - deploy.yaml: add HTML placeholder touch step before docker build Proper human gate between stage and prod: the stage→main merge decision.
This commit is contained in:
+86
-2
@@ -38,6 +38,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <dlfcn.h> /* dlsym for http_set_handler fallback */
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
@@ -1853,6 +1854,84 @@ el_val_t fs_write_bytes(el_val_t pathv, el_val_t bytesv, el_val_t lengthv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// exec_command — run a shell command, return exit code (0 = success).
|
||||
// Used by elb and other El tooling to invoke subprocesses.
|
||||
el_val_t exec_command(el_val_t cmdv) {
|
||||
const char* cmd = EL_CSTR(cmdv);
|
||||
if (!cmd) return (el_val_t)(int64_t)-1;
|
||||
int ret = system(cmd);
|
||||
return (el_val_t)(int64_t)ret;
|
||||
}
|
||||
|
||||
// exec_capture — run a shell command, capture stdout, return as String.
|
||||
// Returns "" on failure.
|
||||
el_val_t exec_capture(el_val_t cmdv) {
|
||||
const char* cmd = EL_CSTR(cmdv);
|
||||
if (!cmd) return el_wrap_str(el_strdup(""));
|
||||
FILE* f = popen(cmd, "r");
|
||||
if (!f) return el_wrap_str(el_strdup(""));
|
||||
JsonBuf b; jb_init(&b);
|
||||
char buf[4096];
|
||||
while (fgets(buf, sizeof(buf), f)) jb_puts(&b, buf);
|
||||
pclose(f);
|
||||
return el_wrap_str(b.buf);
|
||||
}
|
||||
|
||||
// exec — run a shell command via /bin/sh, capture stdout, return as String.
|
||||
// Times out after 30 seconds. Returns "" on any error.
|
||||
// El name: exec(cmd) -> String
|
||||
el_val_t exec(el_val_t cmdv) {
|
||||
const char* cmd = EL_CSTR(cmdv);
|
||||
if (!cmd || !*cmd) return el_wrap_str(el_strdup(""));
|
||||
/* Build a time-limited command: wrap with timeout(1) if available,
|
||||
* otherwise rely on the 30s read loop guard below. We use the simple
|
||||
* popen approach with a deadline measured by wall clock so the caller
|
||||
* is never blocked indefinitely. */
|
||||
FILE* f = popen(cmd, "r");
|
||||
if (!f) return el_wrap_str(el_strdup(""));
|
||||
JsonBuf b; jb_init(&b);
|
||||
char buf[4096];
|
||||
/* 30-second wall-clock deadline */
|
||||
time_t deadline = time(NULL) + 30;
|
||||
while (time(NULL) < deadline) {
|
||||
if (fgets(buf, sizeof(buf), f) == NULL) break;
|
||||
jb_puts(&b, buf);
|
||||
}
|
||||
pclose(f);
|
||||
return el_wrap_str(b.buf);
|
||||
}
|
||||
|
||||
// exec_bg — run a shell command in background, return PID as String.
|
||||
// The child process runs independently; the caller is not blocked.
|
||||
// Returns "" on fork failure.
|
||||
// El name: exec_bg(cmd) -> String
|
||||
el_val_t exec_bg(el_val_t cmdv) {
|
||||
const char* cmd = EL_CSTR(cmdv);
|
||||
if (!cmd || !*cmd) return el_wrap_str(el_strdup(""));
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
/* fork failed */
|
||||
return el_wrap_str(el_strdup(""));
|
||||
}
|
||||
if (pid == 0) {
|
||||
/* child: detach from parent's stdio, exec via shell */
|
||||
setsid();
|
||||
int devnull = open("/dev/null", O_RDWR);
|
||||
if (devnull >= 0) {
|
||||
dup2(devnull, STDIN_FILENO);
|
||||
dup2(devnull, STDOUT_FILENO);
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
execl("/bin/sh", "sh", "-c", cmd, (char*)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
/* parent: convert pid to string and return immediately */
|
||||
char pidbuf[32];
|
||||
snprintf(pidbuf, sizeof(pidbuf), "%d", (int)pid);
|
||||
return el_wrap_str(el_strdup(pidbuf));
|
||||
}
|
||||
|
||||
el_val_t fs_list(el_val_t pathv) {
|
||||
const char* path = EL_CSTR(pathv);
|
||||
el_val_t lst = el_list_empty();
|
||||
@@ -2916,8 +2995,13 @@ static int looks_like_string(el_val_t v) {
|
||||
const unsigned char* s = (const unsigned char*)p;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
unsigned char c = s[i];
|
||||
if (c == '\0') return i > 0; /* terminated string */
|
||||
if (c < 0x09 || (c > 0x0d && c < 0x20) || c >= 0x7f) return 0;
|
||||
if (c == '\0') return 1; /* terminated string (empty string is still a valid string) */
|
||||
/* Reject C0 control chars (non-whitespace), allow UTF-8 high bytes.
|
||||
* 0x09-0x0d = tab/newline/cr/vt/ff (whitespace, OK)
|
||||
* 0x20-0x7e = printable ASCII (OK)
|
||||
* 0x7f = DEL (reject)
|
||||
* 0x80-0xff = UTF-8 continuation/lead bytes (OK for multi-byte chars) */
|
||||
if (c < 0x09 || (c > 0x0d && c < 0x20) || c == 0x7f) return 0;
|
||||
}
|
||||
return 1; /* 16+ printable bytes — call it a string */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user