+#define _POSIX_C_SOURCE 200809L
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <sys/mman.h>
+
+#include "zpm.h"
+
+/*
+ * -f package file, otherwise localdb
+ * -p phase, defaults to 'configure'
+ * -s script name, defaults to /var/tmp/zpm-script
+ * -r chroot to directory
+ * -o script output, /var/tmp/zpm-script.out, '-' for stdout
+ *
+ * arg is package id
+ */
+
+void usage(void) {
+ fprintf(stderr, "usage: db hash file\n");
+}
+
+int setdir(char *rootdir) {
+ if (rootdir && strcmp(rootdir, "/")) {
+ if (chdir(rootdir) == -1) {
+ perror("can not chdir to rootdir");
+ return 0;
+ }
+ if (geteuid() == 0) {
+ /* chroot is deprecated, and not in posix. need to use
+ * OS/kernel specific code.
+ */
+ fprintf(stderr, "support for chroot equivalent not supported on this OS\n");
+ } else {
+ fprintf(stderr, "unable to chroot as non root user\n");
+ }
+ } else {
+ if (chdir("/") == -1) {
+ perror("can not chdir to /");
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+int run(char *program, char **args, char *output, int *status) {
+ /* if stdout is a terminal, leave stdout where it goes,
+ * if it's not a terminal, redirect stdout to output.
+ * in either case, send stderr to output, unless null
+ * if output is null or "-", just run the program
+ */
+ int interactive = 0;
+ pid_t pid;
+ int rv;
+
+ errno = 0;
+
+ interactive = isatty(1);
+
+ pid = fork();
+
+ if (pid == -1) {
+ *status = 1;
+ return -1;
+ }
+
+ if (pid == 0) {
+ /* child */
+ if (output && strcmp(output, "-") != 0) {
+ close(2);
+ rv = open(output, O_NOFOLLOW|O_TRUNC|O_CREAT|O_WRONLY,
+ 0600);
+ if (rv == -1) {
+ perror("cannot open output file");
+ }
+ if (!interactive) {
+ rv = dup2(2,1);
+ if (rv == -1) {
+ perror("unable to redirect stdout");
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ rv = execvp(program, args);
+ if (rv == -1) {
+ perror("cannot exec");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ pid = wait(status);
+ if (pid == -1) {
+ perror("error waiting for child");
+ return -2;
+ }
+
+ if (WIFEXITED(*status)) {
+ return WEXITSTATUS(*status);
+ }
+
+ return -3;
+}
+
+#define RUN 1
+#define SET 2
+#define LIST 3
+
+#define SOFT 1
+#define HARD 2
+
+int main(int ac, char **av){
+ struct zpm zpm;
+ int rv;
+ int status;
+ int required = 0;
+ int fail = 0;
+ char *pkgstr;
+ int opt;
+
+ char hash[ZPM_HASH_STRLEN+1];
+ char *args[4];
+ char *pkgid;
+
+ char *rootdir = 0;
+ char *db = "/var/lib/zpm/zpm.db";
+ char *script = "/var/tmp/zpm-script";
+ char *output = "/var/tmp/zpm-script.out";
+ char *phase = 0;
+ int quiet = 0;
+ int scriptishash = 0;
+ int mode = RUN;
+ int all = 0;
+
+ if (getenv("ZPMDB")) {
+ db = getenv("ZPMDB");
+ }
+ /* ZPM_PACKAGE_FILE ? */
+
+ rootdir = getenv("ZPM_ROOT_DIR");
+
+ /* run, set, show, hash */
+ /* set -S, if -H set the hash, output hash, unless quiet
+ * show: -o, or stdout,
+ * All modes: [-f pkgfile] default to zpmdb
+ * All modes: [-p phase], default to configure
+ * All modes: [-R rootdir], chdir, attempt to chroot
+ * All modes: [-N], required, error if no such script
+ *
+ * run: zpm-script -r -p foo <pkgid args...>
+ * set: zpm-script -s <pkgid [script]>
+ * set: zpm-script -s -h <pkgid [hash]>
+ * show: zpm-script -l [-a] [-o outfile] <pkgid>
+ * show hash: zpm-script -lh <pkgid>
+ */
+ while ((opt = getopt(ac, av, "f:p:rslRFho:S:q")) != -1) {
+ switch (opt) {
+ case 'f': db = optarg; break;
+ case 'p': phase = optarg; break;
+ case 'r': mode = RUN; break;
+ case 's': mode = SET; break;
+ case 'l': mode = LIST; break;
+ case 'R': rootdir = optarg; break;
+ case 'F': required = 1; break;
+ case 'a': all = 1; break;
+
+ case 'h': scriptishash = 1; break;
+ case 'o': output = optarg; break;
+ case 'S': script = optarg; break;
+ case 'q': quiet = 1;
+
+ default:
+ usage();
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+ int argn = optind;
+
+ if (argn >= ac) {
+ usage();
+ exit(EXIT_FAILURE);
+ }
+
+ if (!zpm_open(&zpm, db)) {
+ fprintf(stderr, "unable to open zpm database: %s\n", db);
+ if (zpm.errmsg) {
+ fprintf(stderr, "error detail: %s\n", zpm.errmsg);
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ /* first non option arg is always a package id */
+ pkgstr = av[argn];
+ pkgid = zpm_findpkg(&zpm, pkgstr);
+
+ if (!pkgid) {
+ fprintf(stderr, "no package for %s\n", pkgstr);
+ zpm_close(&zpm);
+ exit(EXIT_FAILURE);
+ }
+
+ argn++;
+
+ if (mode == SET) {
+ char *sethash = 0;
+ char *setscript = 0;
+ int rv = 0;
+
+ if (!phase) {
+ phase = "configure";
+ }
+
+ if (argn < ac) {
+ if (scriptishash) {
+ sethash = av[argn];
+ } else {
+ setscript = av[argn];
+ }
+ }
+
+ if (setscript) {
+ rv = zpm_import(&zpm,setscript,0,hash);
+ if (!rv) {
+ fprintf(stderr, "unable to import %s\n",
+ setscript);
+ fail = HARD;
+ } else {
+ sethash = hash;
+ }
+ }
+
+ if (!fail && !zpm_script_set(&zpm,pkgid,phase,sethash)) {
+ fprintf(stderr, "unable to set %s script hash %s\n",
+ pkgid,
+ sethash ? sethash : "null");
+ fprintf(stderr, "zpm error: %s\n", zpm.errmsg);
+ fail = HARD;
+ }
+ } else if (mode == LIST) {
+ if (!zpm_script_hash(&zpm, pkgid, phase, hash)) {
+ fail = SOFT;
+ } else if (scriptishash) {
+ printf("%s\n", hash);
+ } else {
+ if (!output) {
+ output = "-";
+ }
+ if (!zpm_extract(&zpm, hash, output, 0700)) {
+ fail = HARD;
+ }
+ }
+ } else {
+ /* run a script */
+ if (!phase) {
+ phase = "configure";
+ }
+
+ if (!output) {
+ output = "/var/tmp/zpm-script.out";
+ }
+
+ if (!script) {
+ script = "/var/tmp/zpm-script";
+ }
+
+ /* since the script file name doesn't really
+ * mean anything, pass in the phase as arg 0
+ */
+ /* TODO sanitize environment ? */
+ args[0] = phase;
+ args[1] = pkgid;
+ args[2] = 0;
+ args[3] = 0;
+
+ if (argn <= ac) {
+ args[2] = av[argn];
+ }
+
+ if (!zpm_script_hash(&zpm, pkgid, phase, hash)) {
+ fail = SOFT;
+ goto cleanup;
+ }
+
+ if (!setdir(rootdir)) {
+ fail = HARD;
+ goto cleanup;
+ }
+
+ if (!zpm_extract(&zpm, hash, script, 0700)) {
+ fprintf(stderr, "unable to extract script");
+ fail = HARD;
+ goto cleanup;
+ }
+
+ rv = run(script, args, output, &status);
+ if (rv) {
+ fprintf(stderr, "package %s script failed with code %d\n",
+ pkgid, rv);
+ fail = HARD;
+ }
+
+ /* TODO log output */
+ if (script) {
+ unlink(script);
+ }
+ if (output) {
+ unlink(output);
+ }
+ }
+
+cleanup:
+ free(pkgid);
+ zpm_close(&zpm);
+
+ return (fail == HARD || (required && fail)) ? EXIT_FAILURE : 0;
+
+}