#include "util.h" #include #include #include #include #include #include #include #include #include #include /* ------------------------------------------------------------------ */ /* Allocation */ /* ------------------------------------------------------------------ */ void *xmalloc(size_t n) { void *p = malloc(n ? n : 1); if (!p) { fprintf(stderr, "llsh: out of memory\n"); abort(); } return p; } void *xrealloc(void *p, size_t n) { void *q = realloc(p, n ? n : 1); if (!q) { fprintf(stderr, "llsh: out of memory\n"); abort(); } return q; } /* ------------------------------------------------------------------ */ /* xbuf */ /* ------------------------------------------------------------------ */ void xbuf_init(xbuf *b) { b->data = NULL; b->len = 0; b->cap = 0; } void xbuf_free(xbuf *b) { free(b->data); b->data = NULL; b->len = b->cap = 0; } void xbuf_reset(xbuf *b) { b->len = 0; } static void xbuf_reserve(xbuf *b, size_t extra) { if (b->len + extra + 1 > b->cap) { size_t cap = b->cap ? b->cap : 64; while (cap < b->len + extra + 1) cap *= 2; b->data = (char *) xrealloc(b->data, cap); b->cap = cap; } } void xbuf_putn(xbuf *b, const char *s, size_t n) { xbuf_reserve(b, n); memcpy(b->data + b->len, s, n); b->len += n; b->data[b->len] = '\0'; } void xbuf_put(xbuf *b, const char *s) { if (s) xbuf_putn(b, s, strlen(s)); } void xbuf_push(xbuf *b, char c) { xbuf_reserve(b, 1); b->data[b->len++] = c; b->data[b->len] = '\0'; } int xbuf_fmt(xbuf *b, const char *fmt, ...) { va_list ap, ap2; if (!fmt) return 0; va_start(ap, fmt); va_copy(ap2, ap); int n = vsnprintf(NULL, 0, fmt, ap2); va_end(ap2); if (n < 0) { va_end(ap); return -1; } xbuf_reserve(b, (size_t)n); vsnprintf(b->data + b->len, (size_t)n + 1, fmt, ap); va_end(ap); b->len += (size_t)n; return n; } char *xbuf_steal(xbuf *b) { if (!b->data) { b->data = (char *) xmalloc(1); } b->data[b->len] = '\0'; char *s = b->data; b->data = NULL; b->len = b->cap = 0; return s; } size_t xbuf_curl_write(char *data, size_t sz, size_t nm, void *ud) { xbuf_putn((xbuf *)ud, data, sz * nm); return sz * nm; } /* ------------------------------------------------------------------ */ /* Strings */ /* ------------------------------------------------------------------ */ char *xstrdup(const char *s) { size_t n = s ? strlen(s) : 0; char *p = (char *) xmalloc(n + 1); if (s) memcpy(p, s, n); p[n] = '\0'; return p; } char *xstrndup(const char *s, size_t n) { char *p = (char *) xmalloc(n + 1); memcpy(p, s, n); p[n] = '\0'; return p; } int xstreq(const char *a, const char *b) { return a && b && strcmp(a, b) == 0; } const char *xtrim_span(const char *s, size_t n, size_t *out_len) { size_t a = 0, b = n; while (a < b && isascii((unsigned char)s[a]) && isspace((unsigned char)s[a])) a++; while (b > a && isascii((unsigned char)s[b - 1]) && isspace((unsigned char)s[b - 1])) b--; *out_len = b - a; return s + a; } bool utf8_is_ws(uint32_t c) { return c == 0x09 || c == 0x0A || c == 0x0B || c == 0x0C || c == 0x0D || c == 0x20 || c == 0x85 || c == 0xA0 || c == 0x1680 || (c >= 0x2000 && c <= 0x200A) || c == 0x2028 || c == 0x2029 || c == 0x202F || c == 0x205F || c == 0x3000; } static uint32_t decode_utf8(const char *p, const char *end, size_t *out_len); char *xstr_ltrim_dup(const char *s) { if (!s) return xstrdup(""); size_t len = strlen(s); const char *p = s, *end = s + len; while (p < end) { size_t l; uint32_t cp = decode_utf8(p, end, &l); if (!utf8_is_ws(cp)) break; p += l; } return xstrndup(p, end - p); } char *xstr_rtrim_dup(const char *s) { if (!s) return xstrdup(""); size_t len = strlen(s); const char *p = s, *end = s + len, *last = s; while (p < end) { size_t l; uint32_t cp = decode_utf8(p, end, &l); if (!utf8_is_ws(cp)) last = p + l; p += l; } return xstrndup(s, last - s); } char *xstr_trim_dup(const char *s) { if (!s) return xstrdup(""); size_t len = strlen(s); const char *p = s, *end = s + len; while (p < end) { size_t l; uint32_t cp = decode_utf8(p, end, &l); if (!utf8_is_ws(cp)) break; p += l; } if (p == end) return xstrdup(""); const char *start = p; const char *last = p; while (p < end) { size_t l; uint32_t cp = decode_utf8(p, end, &l); if (!utf8_is_ws(cp)) last = p + l; p += l; } return xstrndup(start, (size_t)(last - start)); } char *xstrjoin2(const char *a, const char *b) { xbuf b_; xbuf_init(&b_); xbuf_put(&b_, a); xbuf_put(&b_, b); return xbuf_steal(&b_); } char *xvsnprintf(const char *fmt, va_list ap) { va_list ap2; va_copy(ap2, ap); int n = vsnprintf(NULL, 0, fmt, ap2); va_end(ap2); if (n < 0) return xstrdup(""); char *p = (char *) xmalloc((size_t)n + 1); vsnprintf(p, (size_t)n + 1, fmt, ap); return p; } char *xstrfmt(const char *fmt, ...) { va_list ap; va_start(ap, fmt); char *s = xvsnprintf(fmt, ap); va_end(ap); return s; } /* ------------------------------------------------------------------ */ /* UTF-8 */ /* ------------------------------------------------------------------ */ static uint32_t decode_utf8(const char *p, const char *end, size_t *out_len) { unsigned char c = (unsigned char)p[0]; if (c < 0x80) { *out_len = 1; return c; } if (end - p >= 2 && (c & 0xE0) == 0xC0 && (p[1] & 0xC0) == 0x80) { *out_len = 2; return ((uint32_t)(c & 0x1F) << 6) | (p[1] & 0x3F); } if (end - p >= 3 && (c & 0xF0) == 0xE0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) { *out_len = 3; return ((uint32_t)(c & 0x0F) << 12) | ((uint32_t)(p[1] & 0x3F) << 6) | (p[2] & 0x3F); } if (end - p >= 4 && (c & 0xF8) == 0xF0 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80 && (p[3] & 0xC0) == 0x80) { *out_len = 4; return ((uint32_t)(c & 0x07) << 18) | ((uint32_t)(p[1] & 0x3F) << 12) | ((uint32_t)(p[2] & 0x3F) << 6) | (p[3] & 0x3F); } /* invalid: consume one byte */ *out_len = 1; return 0xFFFD; } size_t utf8_len(const char *s) { size_t n = 0; const char *p = s, *end = s + strlen(s); while (p < end) { size_t l; decode_utf8(p, end, &l); p += l; n++; } return n; } size_t utf8_byte_len(const char *s, size_t max_points) { size_t n = 0; const char *p = s, *end = s + strlen(s); while (p < end && n < max_points) { size_t l; decode_utf8(p, end, &l); p += l; n++; } return (size_t)(p - s); } const char *utf8_next(const char *p, const char *end, size_t *cp) { size_t l; uint32_t c = decode_utf8(p, end, &l); if (cp) *cp = c; return p + l; } static int is_wide(uint32_t c) { return (c >= 0x1100 && c <= 0x115F) || (c >= 0x2E80 && c <= 0x303E) || (c >= 0x3041 && c <= 0x33FF) || (c >= 0x3400 && c <= 0x4DBF) || (c >= 0x4E00 && c <= 0x9FFF) || (c >= 0xA000 && c <= 0xA4CF) || (c >= 0xAC00 && c <= 0xD7A3) || (c >= 0xF900 && c <= 0xFAFF) || (c >= 0xFE30 && c <= 0xFE4F) || (c >= 0xFF00 && c <= 0xFF60) || (c >= 0xFFE0 && c <= 0xFFE6) || (c >= 0x1F300 && c <= 0x1F64F) || (c >= 0x1F900 && c <= 0x1F9FF) || (c >= 0x20000 && c <= 0x3FFFD); } size_t utf8_display_width(const char *s) { size_t w = 0; const char *p = s, *end = s + strlen(s); while (p < end) { size_t l; uint32_t c = decode_utf8(p, end, &l); if (c >= 0x200B && c <= 0x200F) { /* zero-width joiners etc. */ p += l; continue; } if (c == 0) { p += l; continue; } w += is_wide(c) ? 2 : 1; p += l; } return w; } bool utf8_is_valid(const char *s, size_t len) { const unsigned char *p = (const unsigned char *)s; const unsigned char *end = p + len; while (p < end) { unsigned char c = *p; int n; if (c < 0x80) { n = 1; } else if ((c & 0xE0) == 0xC0) { n = 2; } else if ((c & 0xF0) == 0xE0) { n = 3; } else if ((c & 0xF8) == 0xF0) { n = 4; } else { return false; } if ((size_t)(end - p) < (size_t)n) return false; for (int i = 1; i < n; i++) if ((p[i] & 0xC0) != 0x80) return false; if ((n == 2 && c < 0xC2) || (n == 3 && ((c == 0xE0 && p[1] < 0xA0) || (c == 0xED && p[1] >= 0xA0))) || (n == 4 && (c > 0xF4 || (c == 0xF0 && p[1] < 0x90) || (c == 0xF4 && p[1] >= 0x90)))) return false; p += (size_t)n; } return true; } /* ------------------------------------------------------------------ */ /* strlist */ /* ------------------------------------------------------------------ */ void sl_init(strlist *l) { l->items = NULL; l->len = 0; l->cap = 0; } void sl_free(strlist *l) { for (size_t i = 0; i < l->len; i++) free(l->items[i]); free(l->items); sl_init(l); } void sl_push(strlist *l, char *owned) { if (l->len + 1 > l->cap) { l->cap = l->cap ? l->cap * 2 : 8; l->items = (char* * ) xrealloc(l->items, l->cap * sizeof(char *)); } l->items[l->len++] = owned; } void sl_push_copy(strlist *l, const char *s) { sl_push(l, xstrdup(s)); } char *sl_pop_front(strlist *l) { if (l->len == 0) return NULL; char *p = l->items[0]; memmove(l->items, l->items + 1, (l->len - 1) * sizeof(*l->items)); l->len--; return p; } /* ------------------------------------------------------------------ */ /* Files */ /* ------------------------------------------------------------------ */ int read_file_to_buf(const char *path, char **out, size_t *out_len, char **err) { FILE *f = fopen(path, "rb"); if (!f) { if (err) *err = xstrdup(strerror(errno)); return -1; } xbuf b; xbuf_init(&b); char chunk[8192]; size_t r; while ((r = fread(chunk, 1, sizeof chunk, f)) > 0) xbuf_putn(&b, chunk, r); if (ferror(f)) { fclose(f); xbuf_free(&b); if (err) *err = xstrdup("read error"); return -1; } fclose(f); size_t total = b.len; if (out) *out = xbuf_steal(&b); if (out_len) *out_len = total; return 0; } int read_file_to_buf_limited(const char *path, size_t max_bytes, char **out, size_t *out_len, char **err) { if (out) *out = NULL; if (err) *err = NULL; int fd = open(path, O_RDONLY | O_CLOEXEC); if (fd < 0) { if (err) *err = xstrdup(strerror(errno)); return -1; } struct stat st; int stat_rc = fstat(fd, &st); if (stat_rc != 0 || !S_ISREG(st.st_mode)) { int e = stat_rc != 0 ? errno : EINVAL; close(fd); errno = e; if (err) *err = xstrdup(stat_rc != 0 ? strerror(e) : "not a regular file"); return -1; } xbuf b; xbuf_init(&b); char chunk[8192]; int rc = 0; for (;;) { size_t room = max_bytes - b.len; size_t want = room < sizeof chunk ? room + 1 : sizeof chunk; ssize_t n = read(fd, chunk, want); if (n > 0) { if ((size_t)n > room) { if (err) *err = xstrdup("file exceeds configured size limit"); rc = -1; break; } xbuf_putn(&b, chunk, (size_t)n); } else if (n == 0) { break; } else if (errno != EINTR) { if (err) *err = xstrdup(strerror(errno)); rc = -1; break; } } close(fd); if (rc != 0) { xbuf_free(&b); return -1; } size_t total = b.len; if (out) *out = xbuf_steal(&b); if (out_len) *out_len = total; xbuf_free(&b); return 0; } int write_file_from_buf(const char *path, const char *data, size_t len, char **err) { FILE *f = fopen(path, "wb"); if (!f) { if (err) *err = xstrdup(strerror(errno)); return -1; } if (len && fwrite(data, 1, len, f) != len) { fclose(f); if (err) *err = xstrdup("write error"); return -1; } if (fclose(f) != 0) { if (err) *err = xstrdup("close error"); return -1; } return 0; } bool path_exists(const char *path) { struct stat st; return stat(path, &st) == 0; } const char *llsh_ca_bundle(void) { const char *env = getenv("SSL_CERT_FILE"); if (env && env[0] && access(env, R_OK) == 0) return env; env = getenv("CURL_CA_BUNDLE"); if (env && env[0] && access(env, R_OK) == 0) return env; static const char *const candidates[] = { "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", /* RHEL/Fedora */ "/etc/ssl/cert.pem", /* -> the above */ "/etc/pki/tls/certs/ca-bundle.crt", /* older RHEL */ "/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */ "/etc/ssl/certs/ca-bundle.crt", }; for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) if (access(candidates[i], R_OK) == 0) return candidates[i]; return NULL; } int mkdir_p(const char *path) { char *tmp = xstrdup(path); int rc = 0; for (char *p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; if (mkdir(tmp, 0755) != 0 && errno != EEXIST) { rc = -1; break; } *p = '/'; } } if (rc == 0 && mkdir(tmp, 0755) != 0 && errno != EEXIST) rc = -1; free(tmp); return rc; } char *expand_tilde(const char *s) { if (strcmp(s, "~") == 0) { const char *home = getenv("HOME"); return xstrdup(home ? home : "/"); } if (strncmp(s, "~/", 2) == 0) { const char *home = getenv("HOME"); if (home) return xstrjoin2(home, s + 1); return xstrdup(s + 1); /* shell fallback: ~/x -> /x */ } return xstrdup(s); } /* ------------------------------------------------------------------ */ /* Misc */ /* ------------------------------------------------------------------ */ double now_secs(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; } void sleep_ms(unsigned long ms) { struct timespec ts; ts.tv_sec = ms / 1000; ts.tv_nsec = (ms % 1000) * 1000000L; while (nanosleep(&ts, &ts) == -1) { /* EINTR: retry with remaining time */ } } char *itoa_dup(long long v) { char tmp[32]; snprintf(tmp, sizeof tmp, "%lld", v); return xstrdup(tmp); } char *dtoa_dup(double v) { char tmp[64]; /* Only cast to long long when v is a finite, in-range whole number; the * cast itself is undefined for NaN/inf or out-of-range values. */ int is_int = (v == v) && (v > -9.0e18) && (v < 9.0e18) && (v == (long long)v); if (is_int) { snprintf(tmp, sizeof tmp, "%lld", (long long)v); } else { snprintf(tmp, sizeof tmp, "%.15g", v); } return xstrdup(tmp); }