/* * delpkg.c - remove an installed package * https://rawnix.org * * Copyright (c) 2025 zorz@gmx.com * SPDX-License-Identifier: BSD-2-Clause */ #include "db.h" #include "meta.h" #include "util.h" #include #include #include #include #include #include #include static void error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fputs("delpkg: error: ", stderr); vfprintf(stderr, fmt, ap); fputc('\n', stderr); va_end(ap); } static void usage(void) { fprintf(stderr, "usage: delpkg [-f] [-r root] \n" " -r alternative installation root\n" " -f override the kernel refusals\n" ); exit(1); } int main(int argc, char **argv) { const char *opt_root = NULL, *opt_pkg = NULL; int opt_force = 0; const char *root; pkgdb_t *db; pkg_t *pkg; pkg_meta_t *meta; char *meta_path; size_t i; int c; while ((c = getopt(argc, argv, "fr:vh")) != -1) { switch (c) { case 'r': opt_root = optarg; break; case 'f': opt_force = 1; break; case 'v': printf("delpkg (mkpkg) %s\n", MKPKG_VERSION); return 0; case 'h': usage(); /* FALLTHROUGH */ default: usage(); } } if (optind >= argc) errx(1, "no package specified — try -h"); if (optind < argc - 1) errx(1, "too many arguments"); opt_pkg = argv[optind]; if (getuid() != 0) errx(1, "must be root to remove packages"); root = opt_root ? opt_root : (getenv("PKG_ROOT") ? getenv("PKG_ROOT") : "/"); db = db_open(root, 1); pkg = db_find(db, opt_pkg); if (!pkg) errx(1, "%s is not installed", opt_pkg); /* * Two refusals, both answered from the db. A kernel is not declared * anywhere -- it is a package that happens to own lib/modules//, * so both checks are derived from the file list, not annotated in the * MAKEPKG. -f overrides; nothing else does. */ if (!opt_force) { struct utsname un; char running_pfx[PATH_MAX_LEN]; int have_running = 0, owns_modules = 0, others_own = 0; pkg_t *p; /* * uname() reports the kernel of the machine running delpkg, not * of the tree being modified. Under -r that is the builder's * kernel and the comparison is meaningless, so restrict the * running-kernel refusal to the live system. */ if (strcmp(root, "/") == 0 && uname(&un) == 0) { snprintf(running_pfx, sizeof(running_pfx), "lib/modules/%s/", un.release); have_running = 1; } for (i = 0; i < pkg->files->count; i++) { const char *f = pkg->files->items[i]; if (strncmp(f, "lib/modules/", 12) != 0) continue; owns_modules = 1; if (have_running && strncmp(f, running_pfx, strlen(running_pfx)) == 0) errx(1, "%s owns the running kernel's modules " "(lib/modules/%s) -- boot another kernel first, " "or -f to override", opt_pkg, un.release); } if (owns_modules) { for (p = db->head; p && !others_own; p = p->next) { if (strcmp(p->name, opt_pkg) == 0) continue; for (i = 0; i < p->files->count; i++) if (strncmp(p->files->items[i], "lib/modules/", 12) == 0) { others_own = 1; break; } } if (!others_own) errx(1, "%s owns the last module tree under lib/modules/ " "-- removing it leaves no bootable kernel, " "or -f to override", opt_pkg); } } /* read package metadata before touching anything */ { char *metadir = path_join(root, PKG_METADIR); meta_path = path_join(metadir, opt_pkg); free(metadir); } meta = meta_read(meta_path); free(meta_path); /* disable services before removing files */ meta_del_services(meta, root); /* remove package files — triggers still need pkg->files alive */ remove_pkg_files(root, pkg->files, db, opt_pkg, NULL); /* remove .new config files left from upgrades */ for (i = 0; i < pkg->files->count; i++) { const char *f = pkg->files->items[i]; size_t len = strlen(f); if (strncmp(f, "etc/", 4) != 0) continue; if (len > 0 && f[len - 1] == '/') continue; { char *fp = path_join(root, f); char newpath[PATH_MAX_LEN]; snprintf(newpath, sizeof(newpath), "%s.new", fp); unlink(newpath); free(fp); } } /* service dirs are only empty now that the payload is gone */ meta_prune_service_dirs(meta, root); run_ldconfig(root); run_triggers(root, pkg->files, 0); /* remove groups no longer needed — after triggers, before db update */ meta_del_groups(meta, root, opt_pkg); meta_free(meta); /* remove from database — db_remove frees pkg */ db_remove(db, opt_pkg); if (db_write(db) < 0) { error("files removed but database write failed — " "package may still appear as installed"); db_close(db); return 1; } db_close(db); return 0; }