summaryrefslogtreecommitdiffstats
path: root/src/util.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/util.cpp')
-rw-r--r--src/util.cpp628
1 files changed, 628 insertions, 0 deletions
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..4721e11
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,628 @@
+#include "util.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+/* ------------------------------------------------------------------ */
+/* 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);
+}