do not edit — generated by btf.
git.druid.rocksindexdruid520qsrc/q.c

src/q.c


#include "config.h"
 
#ifdef OS_NETBSD_C
#define _NETBSD_SOURCE 1
#endif
#define _POSIX_C_SOURCE 200809L
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
 
#include "q.h"
 
static void printusage(FILE* out, const char* prog);
static void usage(const char* prog);
static void recipe_path(const struct qcfg* c, const char* arg, char* out, size_t max);
static void recipe_load(const struct qcfg* c, const char* arg, char* recipe,
    size_t rmax, char* name, char* ver);
static void cmd_add(const struct qcfg* c, const char* arg);
static void cmd_del(const struct qcfg* c, const char* arg);
static void cmd_upd(const struct qcfg* c, const char* arg);
static void cmd_get(const struct qcfg* c, const char* arg);
static void cmd_list(const struct qcfg* c);
static void cmd_owns(const struct qcfg* c, const char* path);
static int cmp_kv(const void* a, const void* b);
 
static void
printusage(FILE* out, const char* prog)
{
	fprintf(out, "usage: %s <h|a|d|u|g|l|o> [arg]\n", prog);
	fprintf(out, "  h            show this help.\n");
	fprintf(out, "  a <pkg>      install: run the pkg's 'add', snapshot the prefix,\n");
	fprintf(out, "               record the files it added in its manifest, mark it\n");
	fprintf(out, "               installed.\n");
	fprintf(out, "  d <pkg>      remove: run the pkg's 'del', delete any leftover\n");
	fprintf(out, "               files it owns, drop it from the installed db.\n");
	fprintf(out, "  u <pkg>      update: run the pkg's 'upd', refresh its manifest.\n");
	fprintf(out, "  g <pkg>      fetch: run the pkg's 'get'.\n");
	fprintf(out, "  l            list installed packages as \"name ver\".\n");
	fprintf(out, "  o <path>     which installed pkg(s) own <path>.\n");
	fprintf(out, "config: /etc/q.cfg (or $Q_CONF), a posix sh script that may set\n");
	fprintf(out, "prefix, db, shell, pkgs. defaults: prefix=/usr/local,\n");
	fprintf(out, "db=<prefix>/var/db/q, shell=/bin/sh, pkgs=<prefix>/pkgs.\n");
	fprintf(out, "recipes: shell scripts under <pkgs>/, named <pkg>.q, with name=\"...\"\n");
	fprintf(out, "ver=\"...\" headers and get/add/del/upd entrypoints.\n");
}
 
static void
usage(const char* prog)
{
	printusage(stderr, prog);
	exit(1);
}
 
void
badusage(const char* prog, const char* syntax)
{
	fprintf(stderr, "err: bad usage.\n");
	fprintf(stderr, "usage: %s %s\n", prog, syntax);
	exit(1);
}
 
/* resolve arg (a pkg name, with or without a ".q" suffix) to a recipe
 * path under <pkgs>: try <pkgs>/<arg> verbatim, then <pkgs>/<arg>.q. */
static void
recipe_path(const struct qcfg* c, const char* arg, char* out, size_t max)
{
	char cand[MAXPATH];
	size_t off = 0;
 
	if(bufcat(cand, sizeof(cand), &off, c->pkgs) == 0
	    || bufcat(cand, sizeof(cand), &off, "/") == 0
	    || bufcat(cand, sizeof(cand), &off, arg) == 0)
	{
		die("path too long");
		return;
	}
	if(access(cand, F_OK) == 0)
	{
		if(copystr(out, max, cand) == 0)
		{
			die("path too long");
			return;
		}
		return;
	}
	if(bufcat(cand, sizeof(cand), &off, ".q") == 0)
	{
		die("path too long");
		return;
	}
	if(access(cand, F_OK) == 0)
	{
		if(copystr(out, max, cand) == 0)
		{
			die("path too long");
			return;
		}
		return;
	}
	die("no such pkg");
}
 
/* resolve the recipe for arg and pull its name=/ver= headers out (the
 * same key="value" grammar parseconf handles). */
static void
recipe_load(const struct qcfg* c, const char* arg, char* recipe, size_t rmax,
    char* name, char* ver)
{
	static char buf[MAXFILE];
	static struct kv kv[MAXKV];
	const char* nm;
	const char* vr;
	long n;
	long nkv;
 
	recipe_path(c, arg, recipe, rmax);
	n = readfile(recipe, buf, (long)sizeof(buf) - 1);
	if(n == -2)
	{
		die("recipe too large to read (increase MAXFILE)");
		return;
	}
	if(n < 0)
	{
		die("failed reading recipe");
		return;
	}
	nkv = parseconf(buf, n, kv, MAXKV);
	nm = kvget(kv, nkv, "name");
	vr = kvget(kv, nkv, "ver");
	if(nm == NULL || nm[0] == '\0')
	{
		die("recipe has no name");
		return;
	}
	if(vr == NULL || vr[0] == '\0')
	{
		die("recipe has no ver");
		return;
	}
	if(copystr(name, MAXKEY, nm) == 0)
	{
		die("pkg name too long");
		return;
	}
	if(copystr(ver, MAXVAL, vr) == 0)
	{
		die("pkg version too long");
		return;
	}
}
 
static void
cmd_add(const struct qcfg* c, const char* arg)
{
	char recipe[MAXPATH];
	char name[MAXKEY];
	char ver[MAXVAL];
	struct snap before;
	struct snap after;
	struct snap old;
	struct snap manifest;
 
	recipe_load(c, arg, recipe, sizeof(recipe), name, ver);
	if(db_has(c->db, name))
	{
		fprintf(stderr, "warn: %s already installed.\n", name);
	}
	snap_init(&before);
	snap_init(&after);
	snap_init(&old);
	snap_init(&manifest);
	walktree(&before, c->prefix, "");
	snap_sort(&before);
	if(runrecipe(c, recipe, "add") != 0)
	{
		die("pkg add failed");
		return;
	}
	walktree(&after, c->prefix, "");
	snap_sort(&after);
	manifest_diff(&before, &after, &old, &manifest);
	db_install(c->db, name, name, ver, &manifest);
	snap_free(&before);
	snap_free(&after);
	snap_free(&old);
	snap_free(&manifest);
}
 
static void
cmd_upd(const struct qcfg* c, const char* arg)
{
	char recipe[MAXPATH];
	char name[MAXKEY];
	char ver[MAXVAL];
	char pkgdir[MAXPATH];
	char mpath[MAXPATH];
	struct snap before;
	struct snap after;
	struct snap old;
	struct snap manifest;
 
	recipe_load(c, arg, recipe, sizeof(recipe), name, ver);
	snap_init(&before);
	snap_init(&after);
	snap_init(&old);
	snap_init(&manifest);
	db_pkgdir(c->db, name, pkgdir, sizeof(pkgdir));
	if(pathjoin(mpath, sizeof(mpath), pkgdir, "manifest") == 0)
	{
		die("path too long");
		return;
	}
	/* the existing manifest, if any, is the set of paths already owned
	 * -- a missing one (not yet installed) is simply empty. */
	if(snap_readfile(&old, mpath) >= 0)
	{
		snap_sort(&old);
	}
	walktree(&before, c->prefix, "");
	snap_sort(&before);
	if(runrecipe(c, recipe, "upd") != 0)
	{
		die("pkg upd failed");
		return;
	}
	walktree(&after, c->prefix, "");
	snap_sort(&after);
	manifest_diff(&before, &after, &old, &manifest);
	db_install(c->db, name, name, ver, &manifest);
	snap_free(&before);
	snap_free(&after);
	snap_free(&old);
	snap_free(&manifest);
}
 
static void
cmd_del(const struct qcfg* c, const char* arg)
{
	char recipe[MAXPATH];
	char name[MAXKEY];
	char ver[MAXVAL];
	char pkgdir[MAXPATH];
	char mpath[MAXPATH];
	struct snap manifest;
	long i;
 
	recipe_load(c, arg, recipe, sizeof(recipe), name, ver);
	if(!db_has(c->db, name))
	{
		die("pkg not installed");
		return;
	}
	snap_init(&manifest);
	db_pkgdir(c->db, name, pkgdir, sizeof(pkgdir));
	if(pathjoin(mpath, sizeof(mpath), pkgdir, "manifest") == 0)
	{
		die("path too long");
		return;
	}
	if(snap_readfile(&manifest, mpath) >= 0)
	{
		snap_sort(&manifest);
	}
	if(runrecipe(c, recipe, "del") != 0)
	{
		die("pkg del failed");
		return;
	}
	/* delete any manifest-listed path the recipe's own del left behind:
	 * files and symlinks directly, directories only if empty (a dir the
	 * recipe didn't clear may still hold another package's files). walk
	 * deepest-first so an emptied parent can be rmdir'd after its
	 * children. */
	for(i = manifest.n - 1; i >= 0; i = i - 1)
	{
		const char* p = snap_path(&manifest, i);
		char full[MAXPATH];
		struct stat st;
 
		if(pathjoin(full, sizeof(full), c->prefix, p) == 0)
		{
			die("path too long");
			return;
		}
		if(lstat(full, &st) == 0)
		{
			if(S_ISDIR(st.st_mode))
			{
				rmdir(full);
			}
			else
			{
				unlink(full);
			}
		}
	}
	db_remove(c->db, name);
	snap_free(&manifest);
}
 
static void
cmd_get(const struct qcfg* c, const char* arg)
{
	char recipe[MAXPATH];
	char name[MAXKEY];
	char ver[MAXVAL];
 
	recipe_load(c, arg, recipe, sizeof(recipe), name, ver);
	if(runrecipe(c, recipe, "get") != 0)
	{
		die("pkg get failed");
		return;
	}
}
 
static int
cmp_kv(const void* a, const void* b)
{
	const struct kv* ka = (const struct kv*)a;
	const struct kv* kb = (const struct kv*)b;
 
	return strcmp(ka->key, kb->key);
}
 
static void
cmd_list(const struct qcfg* c)
{
	static struct kv kv[MAXKV];
	long nkv;
	long i;
 
	nkv = db_list(c->db, kv, MAXKV);
	qsort(kv, (size_t)nkv, sizeof(kv[0]), cmp_kv);
	for(i = 0; i < nkv; i = i + 1)
	{
		printf("%s %s\n", kv[i].key, kv[i].val);
	}
}
 
static void
cmd_owns(const struct qcfg* c, const char* path)
{
	static struct kv kv[MAXKV];
	char rel[MAXPATH];
	const char* p;
	size_t plen;
	long nkv;
	long i;
	long found = 0;
 
	/* normalize path to prefix-relative: strip the prefix if it's an
	 * absolute path under it, reject anything outside it. */
	plen = strlen(c->prefix);
	if(strncmp(path, c->prefix, plen) == 0
	    && (path[plen] == '/' || path[plen] == '\0'))
	{
		p = path + plen;
	}
	else if(path[0] == '/')
	{
		return;
	}
	else
	{
		p = path;
	}
	while(*p == '/')
	{
		p = p + 1;
	}
	if(copystr(rel, sizeof(rel), p) == 0)
	{
		die("path too long");
		return;
	}
	if(rel[0] == '\0')
	{
		return;
	}
	nkv = db_list(c->db, kv, MAXKV);
	for(i = 0; i < nkv; i = i + 1)
	{
		char pkgdir[MAXPATH];
		char mpath[MAXPATH];
		struct snap manifest;
 
		snap_init(&manifest);
		db_pkgdir(c->db, kv[i].key, pkgdir, sizeof(pkgdir));
		if(pathjoin(mpath, sizeof(mpath), pkgdir, "manifest") == 0)
		{
			die("path too long");
			return;
		}
		if(snap_readfile(&manifest, mpath) >= 0)
		{
			snap_sort(&manifest);
			if(snap_has(&manifest, rel))
			{
				printf("%s %s\n", kv[i].key, kv[i].val);
				found = found + 1;
			}
		}
		snap_free(&manifest);
	}
	if(found == 0)
	{
		fprintf(stderr, "not owned: %s.\n", rel);
	}
}
 
int
main(int argc, char** argv)
{
	struct qcfg cfg;
 
	if(argc < 2)
	{
		usage(argv[0]);
	}
	loadcfg(&cfg);
	if(strcmp(argv[1], "h") == 0 || strcmp(argv[1], "help") == 0)
	{
		printusage(stdout, argv[0]);
		return 0;
	}
	if(strcmp(argv[1], "a") == 0)
	{
		if(argc != 3)
		{
			badusage(argv[0], "a <pkg>");
		}
		cmd_add(&cfg, argv[2]);
		return 0;
	}
	if(strcmp(argv[1], "d") == 0)
	{
		if(argc != 3)
		{
			badusage(argv[0], "d <pkg>");
		}
		cmd_del(&cfg, argv[2]);
		return 0;
	}
	if(strcmp(argv[1], "u") == 0)
	{
		if(argc != 3)
		{
			badusage(argv[0], "u <pkg>");
		}
		cmd_upd(&cfg, argv[2]);
		return 0;
	}
	if(strcmp(argv[1], "g") == 0)
	{
		if(argc != 3)
		{
			badusage(argv[0], "g <pkg>");
		}
		cmd_get(&cfg, argv[2]);
		return 0;
	}
	if(strcmp(argv[1], "l") == 0)
	{
		if(argc != 2)
		{
			badusage(argv[0], "l");
		}
		cmd_list(&cfg);
		return 0;
	}
	if(strcmp(argv[1], "o") == 0)
	{
		if(argc != 3)
		{
			badusage(argv[0], "o <path>");
		}
		cmd_owns(&cfg, argv[2]);
		return 0;
	}
	usage(argv[0]);
	return 1;
}
powered by btf.