/* * pkg - Package builder/installer * https://rawnix.org * * Copyright (c) 2025 zorz@gmx.com * SPDX-License-Identifier: BSD-2-Clause */ #include "builder.h" #include #include "paths.h" #include #include #include /* * The two mkpkg.conf settings that decide where mkpkg puts a package * and what it is called: MKPKG_PACKAGE_DIR and MKPKG_COMPRESSION_MODE. * Read the way mkpkg reads them -- an optional leading `export`, * quotes stripped, a trailing comment dropped, the last setting wins -- * so pkg looks for exactly the file mkpkg wrote. */ static char conf_ext[16] = "gz"; static char conf_pkgdir[PATH_MAX] = ""; static void load_mkpkg_conf(void) { static bool loaded = false; FILE *fp; char line[PATH_MAX + 64]; if (loaded) return; loaded = true; fp = fopen(MKPKG_CONF, "r"); if (!fp) return; while (fgets(line, sizeof(line), fp)) { char *p = line, *val, *end; size_t n; line[strcspn(line, "\n")] = '\0'; while (*p == ' ' || *p == '\t') p++; if (strncmp(p, "export", 6) == 0 && (p[6] == ' ' || p[6] == '\t')) { p += 6; while (*p == ' ' || *p == '\t') p++; } if (strncmp(p, "MKPKG_", 6) != 0 || !(val = strchr(p, '='))) continue; *val++ = '\0'; /* value: quoted, or up to white space / comment */ if (*val == '"' || *val == '\'') { char q = *val++; end = strchr(val, q); if (end) *end = '\0'; } else { end = val + strcspn(val, " \t#"); *end = '\0'; } if (strcmp(p, "MKPKG_COMPRESSION_MODE") == 0) { n = strlen(val); if (n > 0 && n < sizeof(conf_ext)) snprintf(conf_ext, sizeof(conf_ext), "%s", val); } else if (strcmp(p, "MKPKG_PACKAGE_DIR") == 0) { snprintf(conf_pkgdir, sizeof(conf_pkgdir), "%s", val); } } fclose(fp); } static const char *get_compression_ext(void) { load_mkpkg_conf(); return conf_ext; } /* * Where mkpkg writes the package of this port: MKPKG_PACKAGE_DIR if * set (relative to the port directory, as mkpkg runs there), else the * port directory. */ static void package_dir(const port_t *port, char *buf, size_t len) { load_mkpkg_conf(); if (!conf_pkgdir[0]) snprintf(buf, len, "%s", port->path); else if (conf_pkgdir[0] == '/') snprintf(buf, len, "%s", conf_pkgdir); else snprintf(buf, len, "%s/%s", port->path, conf_pkgdir); } /* All extensions mkpkg can produce. find_pkg_file/pkg_is_built scan * every one so that switching MKPKG_COMPRESSION_MODE doesn't make * previously built packages invisible (which forced full rebuilds). */ static const char *pkg_exts[] = { "gz", "xz", "zst", "bz2", "lz" }; #define PKG_EXT_COUNT (sizeof(pkg_exts) / sizeof(pkg_exts[0])) /* Find newest built package file in directory, any known extension */ static char *find_pkg_file(const char *dir, const char *name) { glob_t g; char pattern[PATH_MAX]; char *result = NULL; struct stat st, best_st = {0}; size_t i, x; for (x = 0; x < PKG_EXT_COUNT; x++) { snprintf(pattern, sizeof(pattern), "%s/%s#*.pkg.tar.%s", dir, name, pkg_exts[x]); if (glob(pattern, 0, NULL, &g) != 0) continue; for (i = 0; i < g.gl_pathc; i++) { if (stat(g.gl_pathv[i], &st) == 0 && st.st_mtime > best_st.st_mtime) { free(result); result = xstrdup(g.gl_pathv[i]); best_st = st; } } globfree(&g); } return result; } /* * The package file for the port's CURRENT recipe (MAKEPKG.lock if * present, see port_load), any known extension, configured one * first. Caller frees. NULL if that exact version isn't on disk. * * pkg_is_built and pkg_install must agree on this file. install used * to take the newest name#* by mtime instead — but bulk's rsync -a * keeps the SERVER's mtime, so an older local build from this morning * beat the freshly fetched binary: addpkg installed the old version, * depdb recorded the new one, and every sysup "upgraded" it again. */ static char *current_pkg_file(port_t *port) { char expected[PATH_MAX], dirs[2][PATH_MAX]; struct stat st; size_t x, d; if (!port->pkg || !port->pkg->loaded || !port->pkg->version || !port->pkg->release) return NULL; /* mkpkg's package directory first; the port directory too, since * that is where bulk puts what it fetches */ package_dir(port, dirs[0], sizeof(dirs[0])); snprintf(dirs[1], sizeof(dirs[1]), "%s", port->path); for (d = 0; d < 2; d++) { if (d == 1 && strcmp(dirs[0], dirs[1]) == 0) break; snprintf(expected, sizeof(expected), "%s/%s#%s-%s.pkg.tar.%s", dirs[d], port->name, port->pkg->version, port->pkg->release, get_compression_ext()); if (stat(expected, &st) == 0) return xstrdup(expected); for (x = 0; x < PKG_EXT_COUNT; x++) { snprintf(expected, sizeof(expected), "%s/%s#%s-%s.pkg.tar.%s", dirs[d], port->name, port->pkg->version, port->pkg->release, pkg_exts[x]); if (stat(expected, &st) == 0) return xstrdup(expected); } } return NULL; } static bool port_loaded(const port_t *port) { return port->pkg && port->pkg->loaded && port->pkg->version && port->pkg->release; } bool pkg_is_built(port_t *port) { char *f; if (!port) return false; /* loaded: only the current recipe's version counts as built */ if (port_loaded(port)) { f = current_pkg_file(port); } else { char dir[PATH_MAX]; package_dir(port, dir, sizeof(dir)); f = find_pkg_file(dir, port->name); } if (!f) return false; free(f); return true; } int pkg_build(port_t *port) { char *argv[] = { "mkpkg", "-d", NULL }; if (pkg_is_built(port)) return 0; return run_cmd(MKPKG_CMD, argv, port->path); } int pkg_install(port_t *port, bool upgrade, const char *root) { char *pkgfile; char *argv[7]; int argc = 0; int ret; /* exactly the file pkg_is_built() accepted — never "newest" */ if (port_loaded(port)) { pkgfile = current_pkg_file(port); } else { char dir[PATH_MAX]; package_dir(port, dir, sizeof(dir)); pkgfile = find_pkg_file(dir, port->name); } if (!pkgfile) { warnx("no package file in %s", port->path); return -1; } argv[argc++] = "addpkg"; if (upgrade) argv[argc++] = "-u"; if (root && strcmp(root, "/") != 0) { argv[argc++] = "-r"; argv[argc++] = (char *)root; } argv[argc++] = pkgfile; argv[argc] = NULL; ret = run_cmd(PKGADD_CMD, argv, NULL); free(pkgfile); return ret; } int pkg_remove(const char *name, const char *root) { char *argv[5]; int argc = 0; argv[argc++] = "delpkg"; if (root && strcmp(root, "/") != 0) { argv[argc++] = "-r"; argv[argc++] = (char *)root; } argv[argc++] = (char *)name; argv[argc] = NULL; return run_cmd(PKGDEL_CMD, argv, NULL); } /* * Fetch binaries for the given package names via the `bulk` tool. * One fork: `bulk name1 name2 ... nameN`. bulk's own logic handles * up-to-date detection, .signature lookup, fetch and verify per name. * * Returns 0 on success (every binary now sitting in its port dir), * non-zero if bulk failed for any package — caller should abort * because '-b' means "binaries only", silent fallback to source * defeats the flag. */ int pkg_bulk_fetch(strarray_t *names) { char **argv; size_t i; int ret; if (!names || names->size == 0) return 0; /* argv layout: [0]=BULK_CMD, [1..N]=names, [N+1]=NULL */ argv = xmalloc((names->size + 2) * sizeof(char *)); argv[0] = (char *)BULK_CMD; for (i = 0; i < names->size; i++) argv[i + 1] = (char *)names->data[i]; argv[names->size + 1] = NULL; ret = run_cmd(BULK_CMD, argv, NULL); free(argv); return ret; }