--- /dev/null
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <grp.h>
+#include <math.h>
+
+/* needed for S_IFMT and AT_FDCWD */
+#include <fcntl.h>
+
+#include <string.h>
+
+#include "sqlite3.h"
+#include "zpm.h"
+
+struct config {
+ struct zpm *log; /* logging db will be attached as "log" */
+ struct zpm *src;
+ char *dbfile;
+ char *pkgid;
+ char *rootdir;
+ int errcontinue, errors, verbose, dryrun;
+ int setuser, setgroup;
+ int reverse, exitonerror;
+};
+
+static void usage() {
+ printf("usage: zpm $scriptname [-fncC] args ...\n");
+}
+
+static int exists(char *path, mode_t *mode) {
+ struct stat st;
+
+ if (lstat(path, &st) == -1) {
+ return 0;
+ }
+ if (mode) *mode = st.st_mode;
+ return 1;
+}
+
+/* TODO maintain a list of already created directories */
+static int create_leading_dirs(char *path) {
+ char *delim, *s;
+ int ch = 0;
+ char pcopy[ZPM_PATH_MAX];
+ struct stat st;
+
+ strcpy(pcopy, path);
+
+ delim = strrchr(pcopy, '/');
+ if (!delim) return 1; /* not an error, but no leading dirs */
+
+ /* cut off last component */
+ *delim = 0;
+
+ s = pcopy;
+ do {
+ while (*s == '/') {
+ s++;
+ }
+
+ delim = strchr(s, '/');
+ if (delim) {
+ ch = *delim;
+ *delim = 0;
+ }
+
+ /* try to create the directory, if it exists
+ * and is a directory or a symlink, that's ok
+ */
+ if (mkdir(pcopy, 0755) == -1) {
+ switch (errno) {
+ case EEXIST:
+ if (lstat(pcopy, &st) == -1) {
+ /* can't stat? */
+ return 0;
+ }
+ switch (st.st_mode & S_IFMT) {
+ case S_IFDIR:
+ case S_IFLNK:
+ break;
+ default:
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+ }
+ if (delim) {
+ *delim = ch;
+ }
+ s = delim;
+ } while (delim);
+
+ return 1;
+}
+
+char *column(char *col, int ncols, char **vals, char **cols) {
+ int i = 0;
+ char *val = NULL;
+
+ for (i=0; i < ncols; i++) {
+// fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
+
+ if (!strcmp(col, cols[i])) {
+ val = vals[i];
+ break;
+ }
+ }
+ return val;
+}
+
+#define IERR(x) do { conf->log->errmsg = strdup(x); return !conf->errcontinue; } while (0)
+#define COL(x) column(x, ncols, vals, cols)
+#define SYSERR(x) do { conf->log->error = 2; return conf->errcontinue; } while (0)
+
+static char *ops[] = { "new", "remove", "update", 0 };
+
+static int getop(char *opstr) {
+ int i;
+
+ if (!opstr) return 0;
+ for (i=0;ops[i];i++) {
+ if (!strcmp(opstr, ops[i])) {
+ return i+1;
+ }
+ }
+ return 0;
+}
+
+static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
+ struct config *conf = f;
+ char *path;
+ char *pkg;
+
+ pkg = COL("pkgid");
+ path = COL("path");
+
+ conf->errors++;
+ fprintf(stderr, "%s owned by %s\n", path, pkg);
+ return 0;
+}
+
+static int check_existing(void *f, int ncols, char **vals, char **cols) {
+ struct config *conf = f;
+ char *path;
+ struct stat st;
+
+ path = COL("dest");
+ if (!path) IERR("can't check for existing");
+
+ if (conf->dryrun) {
+ printf("checkfor %s\n", path);
+ return 0;
+ }
+
+ if (lstat(path, &st) == 0) {
+ fprintf(stderr, "%s exists \n", path);
+ conf->errors++;
+ } else {
+ switch(errno) {
+ /* not an error, file shouldn't exist*/
+ case ENOENT: break;
+ default:
+ fprintf(stderr, "unable to check %s: %s\n",
+ path, strerror(errno));
+ conf->errors++;
+ break;
+ }
+ }
+ return 0;
+}
+
+static int update_files(void *f, int ncols, char **vals, char **cols) {
+ struct config *conf = f;
+ char *pkg;
+ char *path;
+ char *dest;
+
+ pkg = COL("pkgid");
+ if (!pkg) IERR("can't get pkgid");
+ path = COL("path");
+ if (!path) IERR("can't get path");
+ dest = COL("dest");
+ if (!dest) IERR("no file dest");
+
+ /* hash is different, so no different than install,
+ * but we don't need to create leading directories
+ */
+
+ if (conf->dryrun) {
+ fprintf(stderr, "update %s\n", dest);
+ return 0;
+ }
+
+ fprintf(stderr, "update not implemented: %s", dest);
+ conf->errors++;
+
+ return 0;
+}
+
+static int remove_files(void *f, int ncols, char **vals, char **cols) {
+ struct config *conf = f;
+ char *dest;
+ struct stat st;
+
+ dest = COL("dest");
+ if (!dest) IERR("no file dest");
+
+ if (conf->dryrun) {
+ char *ftype = COL("filetype");
+ int t = *ftype;
+
+ switch(t) {
+ case 'd': printf("rmdir %s\n", dest); break;
+ default: printf("unlink %s\n", dest); break;
+ }
+ return 0;
+ }
+
+ if (lstat(dest, &st) == -1) {
+ IERR("can't stat");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fprintf(stderr, "rmdir %s\n", dest);
+ rmdir(dest);
+ } else if (S_ISREG(st.st_mode)) {
+ /* TODO conf to import before removal */
+ if (conf->verbose) {
+ fprintf(stderr, "unlink(%s)\n", dest);
+ }
+ unlink(dest);
+ } else {
+ unlink(dest);
+ }
+
+ return 0;
+}
+
+static int install_files(void *f, int ncols, char **vals, char **cols) {
+ struct config *conf = f;
+ struct passwd *pw;
+ struct group *gr;
+
+ mode_t mode = 0;
+ char *path, *dest;
+ uid_t uid;
+ gid_t gid;
+ int ftype;
+ char *val;
+ char *hash = 0;
+ char *opstr;
+ int op = 0;
+
+ /* TODO put the result row in a hash table. May not actually
+ * be faster
+ */
+ opstr = COL("op");
+ op = getop(opstr);
+ if (op == 0) IERR("invalid operation");
+
+ /* TODO config to dishonor setuid/setgid */
+ path = COL("path");
+ if (!path) IERR("no file path");
+ dest = COL("dest");
+ if (!dest) IERR("no file dest");
+
+ val = COL("mode");
+
+ if (!val) IERR("can't determine mode");
+ mode = strtoul(val, NULL, 8);
+
+ val = COL("filetype");
+ if (!val) {
+ IERR("can't determine file type");
+ }
+ ftype = *val;
+
+ if (ftype == 'r') {
+ hash = COL("hash");
+ if (!hash) IERR("can't get hash");
+ }
+
+ uid = getuid();
+ gid = getgid();
+
+ if (conf->setuser) {
+ val = COL("username");
+ if (!val) IERR("no username");
+ pw = getpwnam(val);
+ if (!pw) IERR("no passwd entry");
+ uid = pw->pw_uid;
+ }
+ if (conf->setgroup) {
+ val = COL("groupname");
+ if (!val) IERR("no groupname");
+ gr = getgrnam(val);
+ if (!gr) IERR("no group entry");
+ gid = gr->gr_gid;
+ }
+
+ if (conf->dryrun) {
+ //printf("cld %s\n", path);
+ printf("new %c%o %d:%d %s -> %s\n", ftype, mode, uid, gid, path, dest);
+ return 0;
+ }
+
+ /* TODO should these be owned by the path owner if they don't exist? */
+ /* probably, though they then belong to the package, sort of */
+ if (!create_leading_dirs(dest)) {
+ fprintf(stderr, "unable to create leading directories for %s\n",
+ dest);
+ IERR("cld failure");
+ }
+
+ if (ftype == 'd') {
+ if (mkdir(dest, mode) == -1) {
+ IERR("can't mkdir");
+ }
+ } else if (ftype == 'r') {
+ struct zpm *source;
+ source = conf->src ? conf->src : conf->log;
+ if (conf->verbose > 1) {
+ fprintf(stderr, "extracting %8.8s to %s with mode %o\n",
+ hash, dest, mode);
+ }
+ if (!zpm_extract(source, hash, dest, mode)) {
+ IERR("can't extract file");
+ }
+ }
+
+ if (conf->setuser && conf->setgroup) {
+ if (chown(dest, uid, gid) == -1) {
+ IERR("can't chown");
+ }
+ }
+
+ struct timespec times[2] = { 0 };
+ double mtime = strtod(COL("mtime"),NULL);
+
+ times[0].tv_nsec = UTIME_OMIT;
+ times[1].tv_sec = (time_t)llrint(floor(mtime));
+ times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
+
+ utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW);
+
+ if (conf->verbose) {
+ printf("%s\n", path);
+ }
+
+ return 0;
+}
+
+static void runstage(struct config *conf, char *stage,
+ int (callback)(void *, int, char **, char **)) {
+ int rv;
+ char *errmsg;
+ sqlite3_str *s;
+ char *sql;
+
+ s = sqlite3_str_new(conf->log->db);
+ sqlite3_str_appendall(s, "select *, ");
+ if (conf->rootdir) {
+ sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
+ } else {
+ sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
+ }
+ sqlite3_str_appendall(s, " as dest from install_status");
+
+ if (stage) {
+ sqlite3_str_appendf(s," where op = %Q", stage);
+ }
+ if (conf->reverse) {
+ sqlite3_str_appendall(s," order by length(path) desc, path desc");
+ }
+
+ sql = sqlite3_str_value(s);
+ if (conf->verbose > 2) {
+ fprintf(stderr, "stage query: %s\n", sql);
+ }
+
+ rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
+
+ sqlite3_str_finish(s);
+
+ if (rv) {
+ fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
+ if (errmsg) {
+ fprintf(stderr, "database error: %s\n", errmsg);
+ conf->errors++;
+ }
+ if (conf->log->error == 1) {
+ fprintf(stderr, "unable to allocate memory\n");
+ }
+ fprintf(stderr, "zpm_exec failure: %s\n",
+ conf->log->errmsg ? conf->log->errmsg : "unknown");
+ conf->errors++;
+ }
+ if (conf->log->errmsg) {
+ fprintf(stderr, "error: %s\n", conf->log->errmsg);
+ }
+ if (conf->errors && conf->exitonerror) {
+ zpm_close(conf->log);
+ zpm_close(conf->src);
+ exit(EXIT_FAILURE);
+ }
+ /* TODO final report function in conf var */
+}
+
+int main(int ac, char **av){
+ struct zpm localdb;
+ struct zpm pkgdb;
+ int opt, argn;
+ char *pkgdbfile = 0, *localdbfile = 0;
+ char *s;
+
+ struct config conf;
+
+ conf.errcontinue = 0;
+ conf.errors = 0;
+ conf.verbose = 0;
+ conf.dryrun = 0;
+ conf.setuser = 1;
+ conf.setgroup = 1;
+ conf.log = 0;
+ conf.src = 0;
+ conf.rootdir = 0;
+ conf.reverse = 0;
+
+ if (geteuid() != 0) {
+ conf.setuser = 0;
+ conf.setgroup = 0;
+ }
+
+ localdbfile = ZPM_LOCAL_DB;
+ if ((s = getenv("ZPMDB"))) {
+ /* TODO does this need to be copied ? */
+ localdbfile = s;
+ }
+
+ if ((s = getenv("ZPM_ROOT_DIR"))) {
+ /* TODO does this need to be copied ? */
+ conf.rootdir = s;
+ }
+
+ /*
+ * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
+ * -f 'package database', otherwise regular default of env
+ * ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
+ * -R root of pkg, will just chdir there
+ *
+ * args are pkgid triple, but will do a pkg find on the pkgdb
+ */
+
+ while ((opt = getopt(ac, av, "f:d:c:nCR:v")) != -1) {
+ switch (opt) {
+ case 'd': localdbfile = optarg; break;
+ case 'f': pkgdbfile = optarg; break;
+ case 'n': conf.dryrun = 1; break;
+ case 'v': conf.verbose++; break;
+ case 'C': conf.errcontinue = 1; break;
+ case 'R': conf.rootdir = optarg; break;
+ case 'N': conf.setuser = 0; conf.setgroup = 0; break;
+ default:
+ usage();
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ /* verify root dir exists */
+ if (conf.rootdir && !exists(conf.rootdir, NULL)) {
+ fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
+ }
+
+ argn = optind;
+ if (argn < ac) {
+ conf.pkgid = av[argn];
+ argn++;
+ } else {
+ fprintf(stderr, "must specify pkgid\n");
+ usage();
+ exit(EXIT_FAILURE);
+ }
+
+ if (!zpm_open(&localdb, localdbfile)) {
+ fprintf(stderr, "can't open zpm db %s\n", localdbfile);
+ exit(EXIT_FAILURE);
+ }
+ conf.log = &localdb;
+
+ if (pkgdbfile) {
+ if (!zpm_open(&pkgdb, pkgdbfile)) {
+ fprintf(stderr, "can't open src db %s\n", localdbfile);
+ exit(EXIT_FAILURE);
+ } else {
+ conf.src = &pkgdb;
+ }
+ }
+
+ /* TODO find pkgid from arg */
+
+ /* TODO set conf var to finalize error reporting */
+ if (conf.verbose) {
+ fprintf(stderr, "installing %s (ldb %s) from %s\n",
+ conf.pkgid, localdbfile, pkgdbfile);
+ }
+
+ conf.errors = 0;
+ conf.exitonerror = 0;
+ runstage(&conf, "conflict", report_conflicts);
+ runstage(&conf, "new", check_existing);
+
+ if (!conf.errors) {
+ conf.exitonerror = 1;
+ runstage(&conf, "new", install_files);
+ runstage(&conf, "update", update_files);
+ conf.reverse = 1;
+ runstage(&conf, "remove", remove_files);
+ conf.reverse = 0;
+ }
+
+ zpm_close(&localdb);
+ zpm_close(conf.src);
+ return conf.errors ? 1 : 0;
+}