/* * revdep - reverse dependency checker for rawnix * pkg.c - package database and configuration * * Copyright (c) 2025 zorz@gmx.com * Licensed under the BSD 2-Clause License. See revdep.h. */ #define _POSIX_C_SOURCE 200809L #define _DEFAULT_SOURCE #include "revdep.h" #include #include #include #include #include /* --- pkg ------------------------------------------------------------- */ pkg_t * pkg_new(const char *name, const char *version) { pkg_t *p; if (!name || !version) return NULL; p = calloc(1, sizeof(*p)); if (!p) return NULL; p->name = xstrdup(name); p->version = xstrdup(version); if (!p->name || !p->version) { pkg_free(p); return NULL; } strvec_init(&p->files); strvec_init(&p->dirs); p->ignore = false; return p; } void pkg_free(pkg_t *p) { if (!p) return; free(p->name); free(p->version); strvec_free(&p->files); strvec_free(&p->dirs); free(p); } /* --- pkgvec ---------------------------------------------------------- */ void pkgvec_init(pkgvec_t *v) { v->items = NULL; v->count = 0; v->capacity = 0; } void pkgvec_free(pkgvec_t *v) { size_t i; if (!v) return; for (i = 0; i < v->count; i++) pkg_free(v->items[i]); free(v->items); v->items = NULL; v->count = 0; v->capacity = 0; } bool pkgvec_push(pkgvec_t *v, pkg_t *p) { size_t newcap; pkg_t **tmp; if (!v || !p) return false; if (v->count >= v->capacity) { newcap = v->capacity == 0 ? 64 : v->capacity * 2; tmp = realloc(v->items, newcap * sizeof(pkg_t *)); if (!tmp) return false; v->items = tmp; v->capacity = newcap; } v->items[v->count++] = p; return true; } pkg_t * pkgvec_find(const pkgvec_t *v, const char *name) { size_t i; if (!v || !name) return NULL; for (i = 0; i < v->count; i++) { if (strcmp(v->items[i]->name, name) == 0) return v->items[i]; } return NULL; } /* --- database reader ------------------------------------------------- */ /* * /var/lib/pkg/db format: * * package-name * version * path/to/file1 * path/to/file2 * * next-package * ... * * File paths may or may not have a leading slash. */ bool db_read(const char *path, pkgvec_t *pkgs) { FILE *fp; char line[PATH_MAX]; pkg_t *cur = NULL; int field = 0; size_t len; size_t before; if (!path || !pkgs) return false; fp = fopen(path, "r"); if (!fp) { fprintf(stderr, "revdep: cannot open %s\n", path); return false; } before = pkgs->count; while (fgets(line, sizeof(line), fp)) { len = strlen(line); if (len > 0 && line[len - 1] == '\n') { line[--len] = '\0'; } /* blank line = end of package entry */ if (len == 0) { if (cur && field >= 2) { pkgvec_push(pkgs, cur); cur = NULL; } else if (cur) { pkg_free(cur); cur = NULL; } field = 0; continue; } field++; if (field == 1) { /* package name */ cur = pkg_new(line, ""); } else if (field == 2 && cur) { /* version */ free(cur->version); cur->version = xstrdup(line); } else if (field > 2 && cur) { /* file path */ if (line[0] == '/') { strvec_push(&cur->files, line); } else { char full[PATH_MAX]; full[0] = '/'; memcpy(full + 1, line, len + 1); strvec_push(&cur->files, full); } } } /* handle last entry if no trailing blank line */ if (cur && field >= 2) pkgvec_push(pkgs, cur); else if (cur) pkg_free(cur); fclose(fp); return pkgs->count > before; } /* --- per-package extra directories ----------------------------------- */ /* * /etc/revdep.d/: * /path/to/extra/libdir * /another/path * * Lines starting with # are comments. */ static bool read_conf_file(const char *path, strvec_t *dirs) { FILE *fp; char line[PATH_MAX]; size_t len; fp = fopen(path, "r"); if (!fp) return false; while (fgets(line, sizeof(line), fp)) { len = strlen(line); if (len > 0 && line[len - 1] == '\n') line[--len] = '\0'; if (len == 0 || line[0] == '#') continue; strvec_push(dirs, line); } fclose(fp); return true; } bool db_read_dirs(const char *path, pkgvec_t *pkgs) { DIR *dir; struct dirent *ent; pkg_t *pkg; char *conf; if (!path || !pkgs) return false; dir = opendir(path); if (!dir) return false; while ((ent = readdir(dir)) != NULL) { if (ent->d_name[0] == '.') continue; pkg = pkgvec_find(pkgs, ent->d_name); if (!pkg) continue; conf = xpathjoin(path, ent->d_name); if (conf) { read_conf_file(conf, &pkg->dirs); free(conf); } } closedir(dir); return true; } /* * The package database stores "version-release" (5.40.1-1); the paths * a package installs carry only the version. Strip the release: the * text after the last '-'. NULL for an empty version. */ char * pkg_upstream_version(const char *dbversion) { char *v, *dash; if (!dbversion || !*dbversion) return NULL; v = xstrdup(dbversion); dash = strrchr(v, '-'); if (dash && dash != v) *dash = '\0'; return v; }