/* * util.c - shared utility layer for rawnix.org package tools * https://rawnix.org * * Copyright (c) 2025 zorz@gmx.com * SPDX-License-Identifier: BSD-2-Clause */ #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include /* ── Allocation ──────────────────────────────────────────────────────── */ void *xmalloc(size_t size) { void *p = malloc(size); if (!p) err(1, "malloc(%zu)", size); return p; } void *xrealloc(void *ptr, size_t size) { void *p = realloc(ptr, size); if (!p) err(1, "realloc(%zu)", size); return p; } char *xstrdup(const char *s) { char *p; if (!s) return NULL; p = strdup(s); if (!p) err(1, "strdup"); return p; } /* ── String utilities ────────────────────────────────────────────────── */ char *str_trim(char *s) { char *end; if (!s) return s; /* Left trim */ while (isspace((unsigned char)*s)) s++; if (*s == '\0') return s; /* Right trim */ end = s + strlen(s) - 1; while (end > s && isspace((unsigned char)*end)) end--; end[1] = '\0'; return s; } int str_starts_with(const char *s, const char *pfx) { if (!s || !pfx) return 0; return strncmp(s, pfx, strlen(pfx)) == 0; } int str_ends_with(const char *s, const char *sfx) { size_t slen, sfxlen; if (!s || !sfx) return 0; slen = strlen(s); sfxlen = strlen(sfx); if (sfxlen > slen) return 0; return strcmp(s + slen - sfxlen, sfx) == 0; } /* ── Path utilities ──────────────────────────────────────────────────── */ char *path_join(const char *dir, const char *file) { char *path; size_t dlen, flen, len; if (!dir || !dir[0]) return xstrdup(file ? file : ""); if (!file || !file[0]) return xstrdup(dir); dlen = strlen(dir); flen = strlen(file); /* +2: one for '/', one for '\0' */ len = dlen + flen + 2; path = xmalloc(len); if (dir[dlen - 1] == '/' || file[0] == '/') snprintf(path, len, "%s%s", dir, file); else snprintf(path, len, "%s/%s", dir, file); return path; } int mkdirp(const char *path, mode_t mode) { char *tmp, *p; size_t len; if (!path || !path[0]) { errno = EINVAL; return -1; } tmp = xstrdup(path); len = strlen(tmp); /* Strip trailing slash */ if (len > 1 && tmp[len - 1] == '/') tmp[len - 1] = '\0'; /* Create each component */ for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; if (mkdir(tmp, mode) < 0 && errno != EEXIST) { free(tmp); return -1; } *p = '/'; } } if (mkdir(tmp, mode) < 0 && errno != EEXIST) { free(tmp); return -1; } free(tmp); return 0; } int file_exists(const char *path) { struct stat st; return lstat(path, &st) == 0; } /* ── rmrf ────────────────────────────────────────────────────────────── */ /* * nftw callback: remove each entry depth-first. * FTW_DEPTH ensures directories are visited after their contents. */ static int rmrf_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { (void)sb; (void)ftwbuf; if (typeflag == FTW_DP || typeflag == FTW_D) return rmdir(path) == 0 || errno == ENOENT ? 0 : -1; return remove(path) == 0 || errno == ENOENT ? 0 : -1; } int rmrf(const char *path) { struct stat st; int ret; if (!path || !path[0]) return 0; if (lstat(path, &st) != 0) { if (errno == ENOENT) return 0; return -1; } if (!S_ISDIR(st.st_mode)) return remove(path); ret = nftw(path, rmrf_cb, 64, FTW_DEPTH | FTW_PHYS); if (ret != 0) { /* fallback: fork rm -rf for large/complex trees */ pid_t pid = fork(); if (pid == 0) { execl("/bin/rm", "rm", "-rf", path, (char *)NULL); _exit(1); } else if (pid > 0) { int status; waitpid(pid, &status, 0); ret = (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : -1; } } return ret; } /* ── String array ────────────────────────────────────────────────────── */ strarray_t *strarray_new(void) { strarray_t *a = xmalloc(sizeof(strarray_t)); a->count = 0; a->capacity = 32; a->sorted = 1; /* empty is trivially sorted */ a->items = xmalloc(a->capacity * sizeof(char *)); return a; } void strarray_free(strarray_t *a) { size_t i; if (!a) return; for (i = 0; i < a->count; i++) free(a->items[i]); free(a->items); free(a); } int strarray_add(strarray_t *a, const char *s) { if (a->count >= a->capacity) { a->capacity *= 2; a->items = xrealloc(a->items, a->capacity * sizeof(char *)); } a->items[a->count++] = xstrdup(s); a->sorted = 0; return 0; } /* * Binary search when sorted (>16 items), linear otherwise. * Caller must call strarray_sort() before relying on binary search. */ int strarray_contains(const strarray_t *a, const char *s) { size_t lo, hi, mid; int cmp; if (!a || !s) return 0; if (a->sorted && a->count > 16) { lo = 0; hi = a->count; while (lo < hi) { mid = lo + (hi - lo) / 2; cmp = strcmp(a->items[mid], s); if (cmp == 0) return 1; if (cmp < 0) lo = mid + 1; else hi = mid; } return 0; } for (lo = 0; lo < a->count; lo++) if (strcmp(a->items[lo], s) == 0) return 1; return 0; } /* * Remove by swapping with last element. * Clears sorted flag — caller must re-sort if needed. */ int strarray_remove(strarray_t *a, const char *s) { size_t i; for (i = 0; i < a->count; i++) { if (strcmp(a->items[i], s) == 0) { free(a->items[i]); a->items[i] = a->items[--a->count]; a->sorted = 0; return 0; } } return -1; } static int strcmp_wrap(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } void strarray_sort(strarray_t *a) { if (!a || a->sorted) return; if (a->count > 1) qsort(a->items, a->count, sizeof(char *), strcmp_wrap); a->sorted = 1; } /* ── Output ──────────────────────────────────────────────────────────── */ void info(const char *fmt, ...) { va_list ap; fputs("mkpkg: ", stdout); va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); fputc('\n', stdout); fflush(stdout); } void warning(const char *fmt, ...) { va_list ap; fputs("mkpkg: warning: ", stderr); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); } void die(const char *fmt, ...) { va_list ap; fputs("mkpkg: error: ", stderr); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); exit(1); } /* ── signify trust anchor ─────────────────────────────────────────────── */ /* * The base64 line of a signify public key: the first non-empty line * that is not the "untrusted comment:" line, trimmed. 0 or -1. */ static int signify_key_line(const char *text, char *out, size_t outsz) { const char *p = text; while (*p) { const char *e = strchr(p, '\n'); size_t len = e ? (size_t)(e - p) : strlen(p); const char *s = p; while (len && isspace((unsigned char)*s)) { s++; len--; } while (len && isspace((unsigned char)s[len - 1])) len--; if (len && strncmp(s, "untrusted comment:", 18) != 0) { if (len >= outsz) return -1; memcpy(out, s, len); out[len] = '\0'; return 0; } if (!e) break; p = e + 1; } return -1; } /* * Why this exists: the key that verified a port used to be the one in * the MAKEPKG being verified, and both arrive over `ports -u` — rsync's * daemon protocol, plaintext, no server authentication. Whoever could * alter that stream could swap the key and sign with their own. The * anchor is local: a key is only good if root put its .pub here. */ int signify_trusted_pubkey(const char *keytext, char *path, size_t pathsz) { char want[512], have[512], buf[2048]; char fpath[PATH_MAX_LEN]; DIR *dp; struct dirent *de; struct stat st; int found = -1; if (!keytext || signify_key_line(keytext, want, sizeof(want)) != 0) return -1; dp = opendir(TRUSTED_KEYS_DIR); if (!dp) return -1; while (found != 0 && (de = readdir(dp)) != NULL) { size_t n = strlen(de->d_name); FILE *fp; size_t r; if (n < 5 || strcmp(de->d_name + n - 4, ".pub") != 0) continue; snprintf(fpath, sizeof(fpath), "%s/%s", TRUSTED_KEYS_DIR, de->d_name); if (stat(fpath, &st) != 0 || !S_ISREG(st.st_mode)) continue; fp = fopen(fpath, "r"); if (!fp) continue; r = fread(buf, 1, sizeof(buf) - 1, fp); fclose(fp); buf[r] = '\0'; if (signify_key_line(buf, have, sizeof(have)) == 0 && strcmp(want, have) == 0) { snprintf(path, pathsz, "%s", fpath); found = 0; } } closedir(dp); return found; }