/* * pkg - Dependency database * https://rawnix.org * * Copyright (c) 2025 zorz@gmx.com * SPDX-License-Identifier: BSD-2-Clause */ #include "depdb.h" #include "paths.h" #include #include #include #define DB_VERSION "1" dep_entry_t *dep_entry_new(const char *name, const char *version, const char *release, install_reason_t reason) { dep_entry_t *e; if (!name) return NULL; e = xmalloc(sizeof(dep_entry_t)); e->name = xstrdup(name); e->version = version ? xstrdup(version) : NULL; e->release = release ? xstrdup(release) : NULL; e->reason = reason; e->install_time = time(NULL); e->depends = xmalloc(sizeof(strarray_t)); strarray_init(e->depends); return e; } void dep_entry_free(dep_entry_t *e) { if (!e) return; free(e->name); free(e->version); free(e->release); if (e->depends) { strarray_free(e->depends); free(e->depends); } free(e); } depdb_t *depdb_open(const char *root) { depdb_t *db; FILE *fp; char line[MAX_LINE]; char *stripped, *eq, *key, *val; dep_entry_t *cur = NULL; size_t i; db = xmalloc(sizeof(depdb_t)); db->bucket_count = HASH_BUCKETS; db->size = 0; db->buckets = xmalloc(db->bucket_count * sizeof(depdb_hash_t *)); for (i = 0; i < db->bucket_count; i++) db->buckets[i] = NULL; db->path = build_root_path(root, DEPDB_FILE); fp = fopen(db->path, "r"); if (!fp) return db; /* empty db is acceptable */ while (fgets(line, sizeof(line), fp)) { char *hash = strchr(line, '#'); if (hash) *hash = '\0'; stripped = str_trim(line); if (!stripped || !*stripped) continue; /* section header [package] */ if (stripped[0] == '[') { char *end = strchr(stripped, ']'); if (end) { *end = '\0'; if (cur) depdb_add(db, cur); cur = dep_entry_new(stripped + 1, NULL, NULL, REASON_MANUAL); } continue; } eq = strchr(stripped, '='); if (!eq || !cur) continue; *eq = '\0'; key = str_trim(stripped); val = str_trim(eq + 1); if (strcmp(key, "version") == 0) { /* split at the LAST dash: versions may contain dashes, * the release never does (matches depdb_initdb) */ char *dash = strrchr(val, '-'); if (dash) { *dash = '\0'; free(cur->version); free(cur->release); cur->version = xstrdup(val); cur->release = xstrdup(dash + 1); } else { free(cur->version); cur->version = xstrdup(val); } } else if (strcmp(key, "reason") == 0) { cur->reason = str_reason(val); } else if (strcmp(key, "installed") == 0) { cur->install_time = (time_t)atol(val); } else if (strcmp(key, "depends") == 0) { char *tok, *save; for (tok = strtok_r(val, " \t", &save); tok; tok = strtok_r(NULL, " \t", &save)) strarray_add(cur->depends, tok); } /* makedeps: silently ignored — field dropped */ } if (cur) depdb_add(db, cur); fclose(fp); return db; } void depdb_close(depdb_t *db) { size_t i; depdb_hash_t *e, *next; if (!db) return; for (i = 0; i < db->bucket_count; i++) { e = db->buckets[i]; while (e) { next = e->next; free(e->key); dep_entry_free(e->entry); free(e); e = next; } } free(db->buckets); free(db->path); free(db); } int depdb_add(depdb_t *db, dep_entry_t *entry) { unsigned long hash; size_t idx; depdb_hash_t *e, *existing; if (!db || !entry || !entry->name) return -1; hash = hash_djb2(entry->name); idx = hash % db->bucket_count; for (existing = db->buckets[idx]; existing; existing = existing->next) { if (strcmp(existing->key, entry->name) == 0) { dep_entry_free(existing->entry); existing->entry = entry; return 0; } } e = xmalloc(sizeof(depdb_hash_t)); e->key = xstrdup(entry->name); e->entry = entry; e->next = db->buckets[idx]; db->buckets[idx] = e; db->size++; return 0; } dep_entry_t *depdb_find(depdb_t *db, const char *name) { unsigned long hash; size_t idx; depdb_hash_t *e; if (!db || !name) return NULL; hash = hash_djb2(name); idx = hash % db->bucket_count; for (e = db->buckets[idx]; e; e = e->next) if (strcmp(e->key, name) == 0) return e->entry; return NULL; } int depdb_remove(depdb_t *db, const char *name) { unsigned long hash; size_t idx; depdb_hash_t *e, *prev = NULL; if (!db || !name) return -1; hash = hash_djb2(name); idx = hash % db->bucket_count; for (e = db->buckets[idx]; e; prev = e, e = e->next) { if (strcmp(e->key, name) == 0) { if (prev) prev->next = e->next; else db->buckets[idx] = e->next; free(e->key); dep_entry_free(e->entry); free(e); db->size--; return 0; } } return -1; } /* * Atomic write: write to tmp, fsync, rename. * If interrupted before rename, the old db is intact. */ int depdb_save(depdb_t *db) { char tmp[MAX_LINE]; FILE *fp; int fd; size_t i, j; depdb_hash_t *e; if (!db || !db->path) return -1; ensure_parent_dir(db->path); snprintf(tmp, sizeof(tmp), "%s.tmp", db->path); fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) return -1; fp = fdopen(fd, "w"); if (!fp) { close(fd); unlink(tmp); return -1; } fprintf(fp, "FORMAT_VERSION %s\n\n", DB_VERSION); for (i = 0; i < db->bucket_count; i++) { for (e = db->buckets[i]; e; e = e->next) { dep_entry_t *d = e->entry; fprintf(fp, "[%s]\n", d->name); if (d->version && d->release) fprintf(fp, "version = %s-%s\n", d->version, d->release); else if (d->version) fprintf(fp, "version = %s\n", d->version); fprintf(fp, "reason = %s\n", reason_str(d->reason)); fprintf(fp, "installed = %ld\n", (long)d->install_time); if (d->depends && d->depends->size > 0) { fprintf(fp, "depends ="); for (j = 0; j < d->depends->size; j++) fprintf(fp, " %s", d->depends->data[j]); fprintf(fp, "\n"); } fprintf(fp, "\n"); } } if (file_close_checked(fp) != 0) { unlink(tmp); return -1; } if (rename(tmp, db->path) != 0) { unlink(tmp); return -1; } return 0; } strarray_t *depdb_list(depdb_t *db) { strarray_t *arr; size_t i; depdb_hash_t *e; arr = xmalloc(sizeof(strarray_t)); strarray_init(arr); if (!db) return arr; for (i = 0; i < db->bucket_count; i++) for (e = db->buckets[i]; e; e = e->next) strarray_add(arr, e->key); return arr; } strarray_t *depdb_rdeps(depdb_t *db, const char *pkg) { strarray_t *arr; size_t i, j; depdb_hash_t *e; arr = xmalloc(sizeof(strarray_t)); strarray_init(arr); if (!db || !pkg) return arr; for (i = 0; i < db->bucket_count; i++) { for (e = db->buckets[i]; e; e = e->next) { if (!e->entry->depends) continue; for (j = 0; j < e->entry->depends->size; j++) { if (strcmp(e->entry->depends->data[j], pkg) == 0) { strarray_add(arr, e->key); break; } } } } return arr; } strarray_t *depdb_auto_installed(depdb_t *db) { strarray_t *arr; size_t i; depdb_hash_t *e; arr = xmalloc(sizeof(strarray_t)); strarray_init(arr); if (!db) return arr; for (i = 0; i < db->bucket_count; i++) for (e = db->buckets[i]; e; e = e->next) if (e->entry->reason == REASON_AUTO) strarray_add(arr, e->key); return arr; } const char *reason_str(install_reason_t r) { return r == REASON_AUTO ? "auto" : "manual"; } install_reason_t str_reason(const char *s) { if (s && strcmp(s, "auto") == 0) return REASON_AUTO; return REASON_MANUAL; } /* * Bootstrap the dependency database from the system package database. * For every package in pkgdb that is NOT already in depdb, create * a REASON_MANUAL entry with the installed version. * Existing entries are never touched — safe to run repeatedly. * Returns the number of entries added. */ int depdb_initdb(depdb_t *db, pkgdb_t *pkgdb) { strarray_t *installed; size_t i; int added = 0; if (!db || !pkgdb) return 0; installed = pkgdb_list(pkgdb); if (!installed) return 0; for (i = 0; i < installed->size; i++) { const char *name = installed->data[i]; installed_pkg_t *ip; dep_entry_t *existing; existing = depdb_find(db, name); if (existing) continue; ip = pkgdb_find(pkgdb, name); if (!ip) continue; /* * Split the installed version string at the last '-' to separate * version and release (mkpkg stores "version-release"). */ { char *ver_copy = xstrdup(ip->version); char *dash = strrchr(ver_copy, '-'); dep_entry_t *e; if (dash) { *dash = '\0'; e = dep_entry_new(name, ver_copy, dash + 1, REASON_MANUAL); } else { e = dep_entry_new(name, ver_copy, "1", REASON_MANUAL); } free(ver_copy); if (e) { depdb_add(db, e); added++; } } } strarray_free(installed); free(installed); return added; }