diff options
Diffstat (limited to 'src/util.cpp')
| -rw-r--r-- | src/util.cpp | 628 |
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); +} |