]> pd.if.org Git - zpackage/commitdiff
add zpm-pkgfiles
authorNathan Wagner <nw@hydaspes.if.org>
Sun, 23 Sep 2018 08:10:39 +0000 (08:10 +0000)
committerNathan Wagner <nw@hydaspes.if.org>
Mon, 24 Sep 2018 10:40:18 +0000 (10:40 +0000)
Makefile
zpm-pkgfiles.c [new file with mode: 0644]

index e85060629124316ed57b01a975ce6a898049477c..641e9129cfc2ca296eb1ca656714fc204af272d0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -90,7 +90,7 @@ t/ctap/prove: t/ctap/prove.o
        $(CC) $(CFLAGS) -o $@ $+
 
 test: $(ZPKGBIN) t/ctap/prove
-       PATH=$(curdir)/t:$(curdir):$(PATH) t/ctap/prove t/*.t
+       @PATH=$(curdir)/t:$(curdir):$(PATH) t/ctap/prove t/*.t
 
 programs: $(ZPKGBIN)
 
@@ -129,6 +129,9 @@ zpm-foreach-path: zpm-foreach-path.o libzpm.a sqlite/sqlite3.h
 zpm-findpkg: zpm-findpkg.o libzpm.a
        $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf
 
+zpm-pkgfiles: zpm-pkgfiles.o libzpm.a
+       $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf -lm
+
 zpm-parse: zpm-parse.o libzpm.a
        $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf
 
diff --git a/zpm-pkgfiles.c b/zpm-pkgfiles.c
new file mode 100644 (file)
index 0000000..b096c4b
--- /dev/null
@@ -0,0 +1,535 @@
+#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;
+}