From 7f5d240252e25f231b74ba8ddb857da4ae98044d Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Sun, 23 Sep 2018 08:10:39 +0000 Subject: [PATCH] add zpm-pkgfiles --- Makefile | 5 +- zpm-pkgfiles.c | 535 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 539 insertions(+), 1 deletion(-) create mode 100644 zpm-pkgfiles.c diff --git a/Makefile b/Makefile index e850606..641e912 100644 --- 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 index 0000000..b096c4b --- /dev/null +++ b/zpm-pkgfiles.c @@ -0,0 +1,535 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* needed for S_IFMT and AT_FDCWD */ +#include + +#include + +#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; +} -- 2.40.0