#define _DEFAULT_SOURCE #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "os_compat.h" #include #include #include #include #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include #else #include #include #include #include #include #endif void os_run_result_free(os_run_result_t *r) { if (!r) return; free(r->out); r->out = NULL; r->out_len = 0; } int os_path_is_abs(const char *p) { if (!p || !p[0]) return 0; #ifdef _WIN32 if (p[0] == '/' || p[0] == '\\') return 1; if (((p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z')) && p[1] == ':' && (p[2] == '/' || p[2] == '\\' || p[2] == '\0')) return 1; return 0; #else return p[0] == '/'; #endif } void os_path_norm_slashes(char *p) { if (!p) return; for (; *p; p++) { if (*p == '\\') *p = '/'; } } int os_path_join(char *out, size_t out_sz, const char *a, const char *b) { if (!out || out_sz < 2 || !a) return -1; if (!b || !b[0]) { snprintf(out, out_sz, "%s", a); return 0; } if (os_path_is_abs(b)) { snprintf(out, out_sz, "%s", b); return 0; } size_t al = strlen(a); while (al > 0 && (a[al - 1] == '/' || a[al - 1] == '\\')) al--; #ifdef _WIN32 int n = snprintf(out, out_sz, "%.*s\\%s", (int)al, a, b); #else int n = snprintf(out, out_sz, "%.*s/%s", (int)al, a, b); #endif if (n < 0 || (size_t)n >= out_sz) return -1; return 0; } int os_realpath(const char *path, char *out, size_t out_sz) { if (!path || !out || out_sz < 2) return -1; #ifdef _WIN32 char buf[OS_PATH_MAX]; DWORD n = GetFullPathNameA(path, (DWORD)sizeof(buf), buf, NULL); if (n == 0 || n >= sizeof(buf)) return -1; /* Prefer forward slashes internally for sandbox compares. */ os_path_norm_slashes(buf); if (strlen(buf) + 1 > out_sz) return -1; memcpy(out, buf, strlen(buf) + 1); /* Lowercase drive letter for stable compares */ if (out[0] && out[1] == ':') { if (out[0] >= 'A' && out[0] <= 'Z') out[0] = (char)(out[0] - 'A' + 'a'); } return 0; #else char *r = realpath(path, NULL); if (!r) return -1; if (strlen(r) + 1 > out_sz) { free(r); return -1; } memcpy(out, r, strlen(r) + 1); free(r); return 0; #endif } int os_path_under(const char *root, const char *path) { if (!root || !path) return 0; size_t rl = strlen(root); if (strncmp(path, root, rl) != 0) return 0; if (path[rl] == '\0') return 1; #ifdef _WIN32 return path[rl] == '/' || path[rl] == '\\'; #else return path[rl] == '/'; #endif } int os_mkdir(const char *path, int mode) { if (!path) return -1; #ifdef _WIN32 (void)mode; if (_mkdir(path) != 0 && errno != EEXIST) return -1; return 0; #else if (mkdir(path, (mode_t)mode) != 0 && errno != EEXIST) return -1; return 0; #endif } int os_config_dir(char *out, size_t out_sz) { if (!out || out_sz < 8) return -1; #ifdef _WIN32 const char *base = getenv("APPDATA"); if (!base || !base[0]) base = getenv("LOCALAPPDATA"); if (!base || !base[0]) return -1; int n = snprintf(out, out_sz, "%s\\cst-agent", base); #else const char *home = getenv("HOME"); if (!home || !home[0]) return -1; int n = snprintf(out, out_sz, "%s/.config/cst-agent", home); #endif if (n < 0 || (size_t)n >= out_sz) return -1; return 0; } int os_temp_dir(char *out, size_t out_sz) { if (!out || out_sz < 2) return -1; #ifdef _WIN32 DWORD n = GetTempPathA((DWORD)out_sz, out); if (n == 0 || n >= out_sz) return -1; os_path_norm_slashes(out); size_t L = strlen(out); while (L > 0 && out[L - 1] == '/') out[--L] = 0; return 0; #else const char *t = getenv("TMPDIR"); if (!t || !t[0]) t = "/tmp"; snprintf(out, out_sz, "%s", t); return 0; #endif } int os_mkstemp(char *out, size_t out_sz, const char *prefix) { if (!out || out_sz < 16) return -1; char dir[OS_PATH_MAX]; if (os_temp_dir(dir, sizeof(dir)) != 0) return -1; if (!prefix) prefix = "cst"; #ifdef _WIN32 char name[MAX_PATH]; if (GetTempFileNameA(dir, prefix, 0, name) == 0) return -1; os_path_norm_slashes(name); if (strlen(name) + 1 > out_sz) return -1; memcpy(out, name, strlen(name) + 1); int fd = _open(out, _O_RDWR | _O_BINARY); return fd; #else int n = snprintf(out, out_sz, "%s/%s-XXXXXX", dir, prefix); if (n < 0 || (size_t)n >= out_sz) return -1; int fd = mkstemp(out); return fd; #endif } /* Optional: main() may set this for Termux / argv0 fallback when * /proc/self/exe is unavailable. */ static char g_argv0_dir[OS_PATH_MAX]; void os_set_prog_path(const char *argv0) { g_argv0_dir[0] = 0; if (!argv0 || !argv0[0]) return; char buf[OS_PATH_MAX]; snprintf(buf, sizeof(buf), "%s", argv0); os_path_norm_slashes(buf); char *slash = strrchr(buf, '/'); if (!slash) return; *slash = 0; /* Relative "./foo" → keep as-is; caller may pass absolute. */ snprintf(g_argv0_dir, sizeof(g_argv0_dir), "%s", buf); } int os_exe_dir(char *out, size_t out_sz) { if (!out || out_sz < 2) return -1; const char *env = getenv("CST_EXE_DIR"); if (env && env[0]) { snprintf(out, out_sz, "%s", env); return 0; } #ifdef _WIN32 char buf[OS_PATH_MAX]; DWORD n = GetModuleFileNameA(NULL, buf, (DWORD)sizeof(buf)); if (n == 0 || n >= sizeof(buf)) return -1; os_path_norm_slashes(buf); char *slash = strrchr(buf, '/'); if (!slash) return -1; *slash = 0; if (strlen(buf) + 1 > out_sz) return -1; memcpy(out, buf, strlen(buf) + 1); return 0; #else char buf[OS_PATH_MAX]; ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1); if (n >= 0) { buf[n] = 0; char *slash = strrchr(buf, '/'); if (slash) { *slash = 0; if (strlen(buf) + 1 <= out_sz) { memcpy(out, buf, strlen(buf) + 1); return 0; } } } /* Termux / restricted Android: fall back to argv0 dir. */ if (g_argv0_dir[0]) { snprintf(out, out_sz, "%s", g_argv0_dir); return 0; } return -1; #endif } static int file_exists(const char *p) { #ifdef _WIN32 DWORD a = GetFileAttributesA(p); return a != INVALID_FILE_ATTRIBUTES && !(a & FILE_ATTRIBUTE_DIRECTORY); #else return access(p, X_OK) == 0 || access(p, F_OK) == 0; #endif } int os_resolve_shell(char *out, size_t out_sz) { if (!out || out_sz < 2) return -1; const char *env = getenv("CST_SHELL"); if (env && env[0]) { snprintf(out, out_sz, "%s", env); return 0; } char dir[OS_PATH_MAX]; if (os_exe_dir(dir, sizeof(dir)) == 0) { #ifdef _WIN32 char cand[OS_PATH_MAX]; if (os_path_join(cand, sizeof(cand), dir, "busybox.exe") == 0 && file_exists(cand)) { snprintf(out, out_sz, "%s", cand); return 0; } #else char cand[OS_PATH_MAX]; if (os_path_join(cand, sizeof(cand), dir, "busybox") == 0 && file_exists(cand)) { snprintf(out, out_sz, "%s", cand); return 0; } #endif } #ifdef _WIN32 snprintf(out, out_sz, "%s", "busybox.exe"); return 0; #else /* Termux: $PREFIX/bin/sh */ { const char *prefix = getenv("PREFIX"); if (prefix && prefix[0]) { char cand[OS_PATH_MAX]; if (os_path_join(cand, sizeof(cand), prefix, "bin/sh") == 0 && file_exists(cand)) { snprintf(out, out_sz, "%s", cand); return 0; } } } if (file_exists("/bin/sh")) { snprintf(out, out_sz, "%s", "/bin/sh"); return 0; } if (file_exists("/system/bin/sh")) { snprintf(out, out_sz, "%s", "/system/bin/sh"); return 0; } snprintf(out, out_sz, "%s", "sh"); return 0; #endif } #ifdef _WIN32 int os_run_capture(const char *cwd, const char *const argv[], size_t max_out, os_run_result_t *r) { if (!argv || !argv[0] || !r) return -1; if (max_out == 0) max_out = 512 * 1024; memset(r, 0, sizeof(*r)); char cmdline[8192]; size_t pos = 0; cmdline[0] = 0; for (int i = 0; argv[i]; i++) { const char *a = argv[i]; int need_q = strchr(a, ' ') || strchr(a, '\t') || strchr(a, '"'); if (i && pos + 1 < sizeof(cmdline)) cmdline[pos++] = ' '; if (need_q) { if (pos + 1 < sizeof(cmdline)) cmdline[pos++] = '"'; for (const char *p = a; *p && pos + 2 < sizeof(cmdline); p++) { if (*p == '"') { cmdline[pos++] = '\\'; cmdline[pos++] = '"'; } else { cmdline[pos++] = *p; } } if (pos + 1 < sizeof(cmdline)) cmdline[pos++] = '"'; } else { size_t L = strlen(a); if (pos + L >= sizeof(cmdline)) break; memcpy(cmdline + pos, a, L); pos += L; } cmdline[pos] = 0; } SECURITY_ATTRIBUTES sa = {0}; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; HANDLE rd = NULL, wr = NULL; if (!CreatePipe(&rd, &wr, &sa, 0)) return -1; SetHandleInformation(rd, HANDLE_FLAG_INHERIT, 0); STARTUPINFOA si = {0}; si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; si.hStdOutput = wr; si.hStdError = wr; si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); PROCESS_INFORMATION pi = {0}; char *cmd_mut = _strdup(cmdline); if (!cmd_mut) { CloseHandle(rd); CloseHandle(wr); return -1; } BOOL ok = CreateProcessA(NULL, cmd_mut, NULL, NULL, TRUE, 0, NULL, cwd && cwd[0] ? cwd : NULL, &si, &pi); free(cmd_mut); CloseHandle(wr); if (!ok) { CloseHandle(rd); return -1; } size_t cap = 4096, len = 0; char *buf = malloc(cap); if (!buf) { CloseHandle(rd); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return -1; } for (;;) { char tmp[1024]; DWORD n = 0; if (!ReadFile(rd, tmp, sizeof(tmp), &n, NULL) || n == 0) break; if (len + n + 1 > cap) { size_t nc = cap * 2; if (nc < len + n + 1) nc = len + n + 1; char *nb = realloc(buf, nc); if (!nb) break; buf = nb; cap = nc; } memcpy(buf + len, tmp, n); len += n; if (len >= max_out) break; } CloseHandle(rd); WaitForSingleObject(pi.hProcess, INFINITE); DWORD code = 1; GetExitCodeProcess(pi.hProcess, &code); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); buf[len] = 0; r->out = buf; r->out_len = len; r->exit_code = (int)code; return 0; } #else int os_run_capture(const char *cwd, const char *const argv[], size_t max_out, os_run_result_t *r) { if (!argv || !argv[0] || !r) return -1; if (max_out == 0) max_out = 512 * 1024; memset(r, 0, sizeof(*r)); int pipefd[2]; if (pipe(pipefd) != 0) return -1; pid_t pid = fork(); if (pid < 0) { close(pipefd[0]); close(pipefd[1]); return -1; } if (pid == 0) { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); close(pipefd[1]); if (cwd && cwd[0] && chdir(cwd) != 0) _exit(127); execvp(argv[0], (char *const *)argv); _exit(127); } close(pipefd[1]); size_t cap = 4096, len = 0; char *buf = malloc(cap); if (!buf) { close(pipefd[0]); waitpid(pid, NULL, 0); return -1; } for (;;) { if (len + 1024 > cap) { cap *= 2; char *nb = realloc(buf, cap); if (!nb) break; buf = nb; } ssize_t n = read(pipefd[0], buf + len, 1024); if (n < 0) { if (errno == EINTR) continue; break; } if (n == 0) break; len += (size_t)n; if (len >= max_out) break; } close(pipefd[0]); int status = 0; waitpid(pid, &status, 0); buf[len] = 0; r->out = buf; r->out_len = len; r->exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : 1; return 0; } #endif int os_run_shell_c(const char *cwd, const char *cmd, size_t max_out, os_run_result_t *r) { if (!cmd || !r) return -1; char shell[OS_PATH_MAX]; if (os_resolve_shell(shell, sizeof(shell)) != 0) return -1; /* busybox: busybox sh -c cmd ; /bin/sh: sh -c cmd */ int is_busybox = 0; { const char *base = shell; const char *s = strrchr(shell, '/'); #ifdef _WIN32 const char *s2 = strrchr(shell, '\\'); if (s2 && (!s || s2 > s)) s = s2; #endif if (s) base = s + 1; if (strncmp(base, "busybox", 7) == 0) is_busybox = 1; } if (is_busybox) { const char *argv[] = {shell, "sh", "-c", cmd, NULL}; return os_run_capture(cwd, argv, max_out, r); } else { const char *argv[] = {shell, "-c", cmd, NULL}; return os_run_capture(cwd, argv, max_out, r); } } void os_localtime(const time_t *t, struct tm *out) { if (!t || !out) return; #ifdef _WIN32 localtime_s(out, t); #else localtime_r(t, out); #endif } int os_getpid(void) { #ifdef _WIN32 return (int)GetCurrentProcessId(); #else return (int)getpid(); #endif } int os_chmod_private(const char *path) { if (!path) return -1; #ifdef _WIN32 (void)path; return 0; #else return chmod(path, 0600); #endif }