+++ /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 <dirent.h>
-#include <limits.h>
-#include <errno.h>
-#include <ctype.h>
-#include <pwd.h>
-#include <grp.h>
-#include <math.h>
-#include <stdarg.h>
-#include <time.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 *rootdir;
- int errabort, errors, verbose, dryrun, conflicts;
- int setuser, setgroup;
- int reverse, exitonerror;
- int overwrite, accept, acceptdir, ignoredirmd;
- unsigned long ops_total, ops_completed;
- unsigned long ops_remove, ops_remove_completed;
- unsigned long ops_update, ops_update_completed;
- unsigned long ops_install, ops_install_completed;
- int progress; /* type of progress meter */
-};
-
-struct nitem {
- int op;
- char *opstr;
- uid_t uid;
- gid_t gid;
- char *dest;
- char *path;
- char *hash, *ohash;
- char *mds, *omds;
- char *target;
- char *pkglist; /* space separated */
- time_t mtime;
- mode_t mode;
- int ftype;
- int configuration, oldwasconf;
- struct timespec times[2];
-};
-
-static void usage() {
- printf("usage: zpm $scriptname [-fncC] args ...\n");
-}
-
-static void warn(char *fmt, ...) {
- va_list args;
-
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
- va_end(args);
- fprintf(stderr, "\n");
-}
-
-static void pdots(int len, int ch, int was, int now, int total) {
- was = len * was / total;
- if (now > total) {
- now = total;
- }
- now = len * now / total;
- while (was++ < now) {
- putchar(ch);
- }
- fflush(stdout);
-}
-
-static int seterror(struct config *conf, char *msgfmt, ...) {
- char msg[1024];
- va_list ap;
-
- conf->errors++;
-
- va_start(ap, msgfmt);
- vsnprintf(msg, sizeof msg, msgfmt, ap);
- va_end(ap);
-
- msg[1023] = 0;
- if (conf->log->errmsg) {
- free(conf->log->errmsg);
- }
-
- conf->log->errmsg = strdup(msg);
-
- if (conf->verbose) {
- fprintf(stderr, "%s\n", msg);
- }
-
- return conf->errabort;
-}
-
-static int setsyserr(struct config *conf, char *msgfmt, ...) {
- char msg[1024];
- va_list ap;
- int printed;
-
- conf->errors++;
-
- va_start(ap, msgfmt);
- printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
- va_end(ap);
-
- if (printed < 1) {
- /* nothing we can really do */
- return conf->errabort;
- }
-
- if ((size_t)printed < sizeof msg) {
- snprintf(msg+printed, sizeof msg - printed, ": %s",
- strerror(errno));
- }
-
- msg[1023] = 0;
- if (conf->log->errmsg) {
- free(conf->log->errmsg);
- }
-
- conf->log->errmsg = strdup(msg);
-
- if (conf->verbose) {
- fprintf(stderr, "%s\n", msg);
- }
-
- return conf->errabort;
-}
-
-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
- * should be (eventually) a symlink to a directory
- * so we want stat here, not lstat
- */
- if (mkdir(pcopy, 0755) == -1) {
- switch (errno) {
- case EEXIST:
- if (stat(pcopy, &st) == -1) {
- /* can't stat? */
- return 0;
- }
- switch (st.st_mode & S_IFMT) {
- case S_IFDIR:
- break;
- default:
- return 0;
- }
- break;
- default:
- return 0;
- }
- }
- if (delim) {
- *delim = ch;
- }
- s = delim;
- } while (delim);
-
- return 1;
-}
-
-static 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 COL(x) column(x, ncols, vals, cols)
-#define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
-
-
-/* TODO handle other ops properly */
-static char *ops[] = { "new", "remove", "update", 0 };
-
-enum op {
- OP_NEW = 1,
- OP_REMOVE = 2,
- OP_UPDATE = 3
-};
-
-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, *hash, *pkg, *conflict_type, *mds;
-
- pkg = COL("pkgid");
- path = COL("path");
- conflict_type = COL("conflict");
- if (!strcmp(conflict_type, "hash")) {
- hash = COL("hash");
- fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
- pkg, path, hash);
- } else
- if (!strcmp(conflict_type, "md")) {
- mds = COL("mds");
- fprintf(stderr, "md conflict: package %s path %s md %s\n",
- pkg, path, mds);
- } else {
- fprintf(stderr, "%s conflict: package %s path %s\n",
- conflict_type, pkg, path);
- }
-
- conf->conflicts++;
- return 0;
-}
-
-static int read_item(struct config *conf, int ncols, char **vals, char **cols,
- struct nitem *n) {
- char *val;
- long lval;
- struct passwd *pw;
- struct group *gr;
- struct nitem zero = { 0 };
-
- *n = zero;
-
- val = COL("op");
- if (!val) {
- seterror(conf, "can't determine op");
- return 0;
- }
- n->opstr = val;
- n->op = getop(val);
- if (!n->op) {
- seterror(conf, "can't determine op");
- return 0;
- }
-
- n->path = COL("path");
- if (!n->path) {
- seterror(conf, "no file path");
- return 0;
- }
- if (strlen(n->path) == 0) {
- seterror(conf, "zero length path not allowed");
- return 0;
- }
-
- /* TODO config to dishonor setuid/setgid */
- n->dest = COL("dest");
- if (!n->dest) {
- seterror(conf, "no file dest");
- return 0;
- }
-
- if (strlen(n->dest) == 0) {
- seterror(conf, "zero length dest not allowed");
- return 0;
- }
-
- val = COL("mode");
-
- if (!val) {
- seterror(conf, "can't determine mode");
- return 0;
- }
-
- n->mode = strtoul(val, NULL, 8);
-
- val = COL("configuration");
- if (!val) {
- seterror(conf, "can't determine config status");
- return 0;
- }
- lval = strtol(val, NULL, 10);
-
- n->configuration = ((lval & 1) != 0);
- n->oldwasconf = ((lval & 2) != 0);
-
- val = COL("filetype");
- if (!val || strlen(val) == 0) {
- seterror(conf, "can't determine file type");
- return 0;
- }
- n->ftype = *val;
-
- /* these can be null */
- n->ohash = COL("ohash");
- n->mds = COL("mds");
- n->omds = COL("omds");
- n->pkglist = COL("pkglist");
-
- if (n->ftype == 'r') {
- n->hash = COL("hash");
- if (!n->hash) {
- seterror(conf, "can't get hash");
- return 0;
- }
- } else if (n->ftype == 'l') {
- n->target = COL("target");
- if (!n->target) {
- seterror(conf, "can't get target");
- return 0;
- }
- if (strlen(n->target) == 0) {
- seterror(conf, "zero length target not allowed");
- return 0;
- }
- n->hash = n->target;
- }
-
- if (conf->setuser) {
- val = COL("username");
- if (!val) {
- seterror(conf, "no username");
- return 0;
- }
- pw = getpwnam(val);
- if (!pw) {
- seterror(conf, "no passwd entry");
- return 0;
- }
- n->uid = pw->pw_uid;
- } else {
- n->uid = geteuid();
- }
-
- if (conf->setgroup) {
- val = COL("groupname");
- if (!val) {
- seterror(conf, "no groupname");
- return 0;
- }
- gr = getgrnam(val);
- if (!gr) {
- seterror(conf, "no group entry for %s", val);
- return 0;
- }
- n->gid = gr->gr_gid;
- } else {
- n->gid = getegid();
- }
-
- errno = 0;
- double mtime = strtod(COL("mtime"),NULL);
- if (errno) {
- mtime = (double)time(NULL);
- }
-
- n->mtime = (time_t)mtime;
-
- n->times[0].tv_sec = 0;
- n->times[0].tv_nsec = UTIME_OMIT;
- n->times[1].tv_sec = (time_t)llrint(floor(mtime));
- n->times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
-
- return 1;
-}
-
-/* file does not exist */
-#define D_NOEXIST 0x1
-/* files are different types */
-#define D_TYPE 0x2
-/* metadata is different */
-#define D_MD 0x4
-/* content or link target is different */
-#define D_HASH 0x8
-/* file to be installed is a directory */
-#define D_ISDIR 0x10
-/* path on disk is a directory */
-#define D_EISDIR 0x20
-/* usernames different */
-#define D_UID 0x40
-/* group names different */
-#define D_GID 0x80
-/* file mode is different */
-#define D_MODE 0x100
-/* mtimes are different */
-#define D_MTIME 0x200
-/* the hash of the file we are supposedly replacing is different than
- * the the hash of the file on disk
- */
-#define D_OHASH 0x400
-/* file exists, and is a directory, and is empty */
-#define D_ISEMPTY 0x800
-/* an error occurred trying to compare the file (other than it doesn't exist */
-#define D_ERROR 0x1000
-/* there was a stat error */
-#define D_STATERROR 0x2000
-/* there was an error calling readlink */
-#define D_RLERROR 0x4000
-
-static int dir_is_empty(char *path) {
- DIR *dir;
- struct dirent *dp;
- int empty;
-
- dir = opendir(path);
- if (!dir) {
- return -1;
- }
-
- dp = readdir(dir);
- if (dp) {
- empty = 0;
- } else {
- empty = 1;
- }
- closedir(dir);
-
- return empty;
-}
-
-/* 1 = file doesn't exist, 2 = file is a directory, target isn't */
-/* 4 == ftype different */
-/* 8 = hash different when both are regular files */
-static unsigned int file_compare(struct nitem *n, struct stat *st) {
- int etype = 0, stat_type;
- char ehash[ZPM_HASH_STRLEN+1];
- unsigned int diff = 0;
- char link[1024];
- ssize_t lsize;
-
- switch (n->ftype) {
- case 'd': etype = S_IFDIR; diff |= D_ISDIR ; break;
- case 'r': etype = S_IFREG; break;
- case 'l': etype = S_IFLNK; break;
- default: etype = 0; break;
- }
-
- errno = 0;
- /* new file, so check type, hash, etc */
- if (lstat(n->dest, st) == 0) {
- stat_type = st->st_mode & S_IFMT;
- if (stat_type != etype) {
- diff |= D_TYPE;
- }
- if (stat_type == S_IFDIR) {
- diff |= D_EISDIR;
- if (dir_is_empty(n->dest)) {
- diff |= D_ISEMPTY;
- }
- }
-
- if (n->hash && etype == S_IFREG && stat_type == S_IFREG) {
- zpm_hash(n->dest, ehash, 0);
- if (strcmp(n->hash, ehash) != 0) {
- diff |= D_HASH;
- }
- if (n->ohash && strcmp(n->ohash, ehash) != 0) {
- diff |= D_OHASH;
- }
- }
- if (n->hash && etype == S_IFLNK && stat_type == S_IFLNK) {
- lsize = readlink(n->dest, link, sizeof link);
-
- if (lsize == -1 || lsize == sizeof link) {
- diff |= D_RLERROR;
- diff |= D_ERROR;
- } else {
- link[lsize] = 0;
- if (strcmp(n->target, link) != 0) {
- diff |= D_HASH;
- }
- }
- }
- if (n->uid != st->st_uid) {
- diff |= D_UID;
- diff |= D_MD;
- }
- if (n->gid != st->st_gid) {
- diff |= D_GID;
- diff |= D_MD;
- }
- if (n->mode != (st->st_mode & 07777)) {
- diff |= D_MODE;
- diff |= D_MD;
- }
- } else {
- switch(errno) {
- case ENOENT: diff |= D_NOEXIST; break;
- default: diff |= (D_STATERROR|D_ERROR); break;
- }
- }
-
- return diff;
-}
-
-
-/* 0 = not acceptable
- * 1 = accept and create/update/remove
- * 2 = accept as is
- * 3 = remove and overwrite
- * 4 = update metadata
- */
-static int acceptable(struct config *conf, unsigned int diffs, int op) {
- int exist = (!(diffs & D_NOEXIST));
- int sametype = (!(diffs & D_TYPE));
- int mdsame = (!(diffs & D_MD));
- int hashsame = (!(diffs & D_HASH));
- int isdir = (diffs & D_ISDIR);
-
- if (!exist) {
- return op == OP_REMOVE ? 2 : 1;
- }
-
- if (op == OP_UPDATE) {
- return sametype ? 4 : 3;
- }
-
- if (op == OP_REMOVE) {
- return 1;
- }
-
- /* the hard cases, should be installing new, but already exists */
-
- if (!sametype) {
- return conf->overwrite ? 3 : 0;
- }
-
- if (mdsame && (conf->accept || conf->overwrite)) {
- return 1;
- }
-
- if (isdir) {
- if (mdsame || conf->ignoredirmd) {
- return conf->acceptdir ? 2 : 0;
- }
- if (conf->overwrite) {
- return 4;
- }
- }
-
- if (hashsame && (conf->accept || conf->overwrite)) {
- return 1;
- }
-
- return conf->overwrite ? 3 : 0;
-}
-
-static int check_existing(void *f, int ncols, char **vals, char **cols) {
- struct config *conf = f;
- struct stat st;
- struct nitem nitem;
-
- if (!read_item(conf, ncols, vals, cols, &nitem)) {
- fprintf(stderr, "can't read item\n");
- return conf->errabort;
- }
-
- if (conf->verbose > 1) {
- fprintf(stderr, "check for existing %s\n", nitem.path);
- }
-
- if (lstat(nitem.path, &st) == -1) {
- switch(errno) {
- /* not an error, file shouldn't exist*/
- case ENOENT: break;
- default:
- fprintf(stderr, "unable to check %s: %s\n",
- nitem.path, strerror(errno));
- conf->errors++;
- break;
- }
- return 0;
- }
-
- unsigned int diffs = file_compare(&nitem, &st);
- int sametype = (!(diffs & D_TYPE));
-
- if (diffs >= D_ERROR) {
- return seterror(conf, "can't check %s", nitem.dest);
- }
-
- if (sametype && nitem.configuration) {
- return 0;
- }
-
- int action = acceptable(conf, diffs, nitem.op);
- if (!action) {
- if (conf->accept) {
- fprintf(stderr, "%s exists and is not acceptable\n", nitem.path);
- } else {
- fprintf(stderr, "%s exists\n", nitem.path);
- }
- conf->errors++;
- }
-
- return 0;
-}
-
-static void update_progress(struct config *conf, char *op, char *path) {
- if (!conf->verbose) {
- return;
- }
-
- if (conf->progress == 0) {
- pdots(50, '.', conf->ops_completed-1, conf->ops_completed, conf->ops_total);
- } else if (conf->progress == 1) {
- size_t len = strlen(path);
- int offset = 0;
- if (len > 50) {
- offset = len-50;
- }
- printf("\r%lu/%lu %.10s %.50s\n", conf->ops_completed,
- conf->ops_total, op, path+offset);
- } else if (conf->progress == 2) {
- printf("%lu/%lu %s %s\n", conf->ops_completed,
- conf->ops_total, op, path);
- }
- fflush(stdout);
-}
-
-static int remove_files(void *f, int ncols, char **vals, char **cols) {
- struct config *conf = f;
- char *dest;
- struct stat st;
- int flags = 0;
-
- conf->ops_completed++;
-
- dest = COL("dest");
- char *ftype = COL("filetype");
-
- if (!dest) return seterror(conf,"no file dest");
- if (!ftype) return seterror(conf,"no file type");
-
- update_progress(conf, *ftype == 'd' ? "rmdir" : "unlink", dest);
-
- if (conf->dryrun) {
- return 0;
- }
-
- errno = 0;
-
- if (lstat(dest, &st) == -1) {
- switch (errno) {
- case ENOENT:
- if (conf->verbose > 1) {
- fprintf(stderr, "expected file not found: '%s'\n", dest);
- }
- break;
- default:
- return seterror(conf, "can't stat %s: %s", dest, strerror(errno));
- }
- return 0;
- }
-
- if (S_ISDIR(st.st_mode)) {
- flags = AT_REMOVEDIR;
- }
- /* TODO check that expected filetype matches actual filetype */
- /* alternatively, just use the expected type, skip the stat,
- * and let it fail if the type is wrong
- */
-
- errno = 0;
-
- if (unlinkat(AT_FDCWD, dest, flags) == -1) {
- switch (errno) {
- case ENOENT:
- break;
- case ENOTEMPTY: /* fall through */
- case EEXIST:
- fprintf(stderr, "expected empty directory: %s\n", dest);
- break;
- default:
- return seterror(conf, "can't unlink %s: %s", dest, strerror(errno));
- }
- }
-
- return 0;
-}
-
-#define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
-
-static int remove_dir(struct config *conf, char *path) {
- int rv;
-
- rv = rmdir(path);
- if (rv == -1) {
- setsyserr(conf, "can't rmdir %s", path);
- return 0;
- }
- return 1;
-}
-
-static int remove_existing(struct config *conf, char *path) {
- int rv;
-
- rv = unlink(path);
- if (rv == -1) {
- setsyserr(conf, "can't unlink %s", path);
- return 0;
- }
- return 1;
-}
-
-static int set_md(struct config *conf, struct nitem *item) {
- int rv;
- int success = 0;
-
- if (conf->dryrun) {
- if (item->ftype != 'l') {
- printf("chmod %o %s\n", item->mode, item->dest);
- }
- if (conf->setuser && conf->setgroup) {
- printf("lchown %d:%d %s\n", item->uid, item->gid,
- item->dest);
- }
- printf("mtime %.0f %s\n", (double)item->mtime, item->dest);
- fflush(stdout);
- return success;
- }
-
- if (conf->setuser && conf->setgroup) {
- rv = lchown(item->dest, item->uid, item->gid);
- if (rv == -1) {
- setsyserr(conf, "can't lchown %s", item->dest);
- return conf->errabort;
- }
- }
-
- /* have to chmod after the chown, setuid bits may (and will)
- * be cleared after a chown
- */
- /* can't chmod a symlink */
- if (item->ftype != 'l') {
- rv = chmod(item->dest, item->mode);
-
- if (rv == -1) {
- setsyserr(conf, "can't chmod %o %s", item->mode, item->dest);
- return conf->errabort;
- }
- }
-
- rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW);
- if (rv == -1) {
- setsyserr(conf, "can't set mtime %.0f %s", (double)item->mtime,
- item->dest);
- return conf->errabort;
- }
- return 0;
-}
-
-/* install a file or create a directory or symlink. path should not exist
- * at this point.
- */
-/* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file,
- * 8 = rmdir existing dir, 16 = return true/false
- */
-#define INS_MD 0x1
-#define INS_CLD 0x2
-#define INS_UNLINK 0x4
-#define INS_RMDIR 0x8
-#define INS_RTF 0x10
-#define INS_ZPMNEW 0x20
-static int install(struct config *conf, struct nitem *item, unsigned int flags) {
- int rv = 1;
- struct zpm *source;
-
- int mkleading = (flags & 2);
- int setmd = (flags & 1);
- int unlink_file = (flags & 4);
- int rm_dir = (flags & 8);
- int failure = conf->errabort;
- int success = 0;
-
- if (flags & INS_RTF) {
- failure = 0;
- success = 1;
- }
-
- if (conf->dryrun) {
- if (unlink_file) {
- printf("unlink %s\n", item->dest);
- } else if (rm_dir) {
- printf("rmdir %s\n", item->dest);
- }
-
- printf("install %c%o %d:%d %s", item->ftype,
- item->mode, item->uid, item->gid,
- item->dest);
- if (item->ftype == 'l') {
- printf(" -> %s", item->target);
- }
- printf("\n");
- fflush(stdout);
- return success;
- }
-
- source = conf->src ? conf->src : conf->log;
-
- if (mkleading) {
- rv = create_leading_dirs(item->dest);
- if (!rv) {
- setsyserr(conf, "can't create leading dirs for %s", item->dest);
- return failure;
- }
- }
-
- if (unlink_file) {
- rv = remove_existing(conf, item->dest);
- } else if (rm_dir) {
- rv = remove_dir(conf, item->dest);
- }
-
- if (rv != 1) {
- return failure;
- }
-
- errno = 0;
- switch (item->ftype) {
- case 'r': rv = zpm_extract(source, item->hash, item->dest, item->mode);
- if (rv == 0) rv = -1;
- break;
- case 'd': rv = mkdir(item->dest, item->mode);
- break;
- case 'l': rv = symlink(item->target, item->dest);
- break;
- default: /* error */
- break;
- }
-
- if (rv == -1) {
- switch (item->ftype) {
- case 'r':
- seterror(conf, "can't extract %s", item->dest);
- break;
- case 'd':
- setsyserr(conf, "install mkdir(\"%s\") failed", item->dest);
- break;
- case 'l':
- setsyserr(conf, "install symlink(\"%s\") failed", item->dest);
- break;
- }
- setsyserr(conf, "installing %s failed", item->dest);
- return failure;
- }
-
- if (setmd) {
- return set_md(conf, item) == 0 ? success : failure;
- }
-
- return success;
-}
-
-static int save_config_file(struct config *conf, struct nitem *n, char *msgfmt) {
- char hash[ZPM_HASH_STRLEN+1];
-
- if (!msgfmt) {
- msgfmt = "saved config file %.8s";
- }
-
- if (zpm_import(conf->log, n->dest, 0, hash)) {
- zpm_note_add(conf->log, n->pkglist, n->path, hash, msgfmt, hash);
- } else {
- warn("unable to import existing config file %s", n->dest);
- conf->errors++;
- return 0;
- }
- return 1;
-}
-
-#if 0
-/*
- * figure out what the difference is for a config file, only called
- * for an update of a configuration file
- * return -1 on an error
- * return 1 if the new file should not be installed
- * return 0 if the new file should be installed
- */
-static int adjust_for_config(struct nitem *n, unsigned int diffs) {
-
- if (!n->oldwasconf) {
- return 0;
- }
-
- int sametype = (!(diffs & D_TYPE));
- int isdir = (diffs & D_ISDIR);
- int eisdir = (diffs & D_EISDIR);
-
- /* what if old was a directory? */
- if (!n->configuration) {
- /* replacing conf with non-conf */
- /* absorb file, mark todo */
- return 0;
- }
-
- /* both old and new are config files */
- if (isdir && sametype) {
- /* both config directories, can only be changing
- * metadata, so no adjustment needed
- */
- return 0;
- }
-
- if (isdir) {
- return 0;
- }
-
- if (eisdir) {
- /* replacing old conf directory with a conf file.
- * nothing needs to be done, if the directory
- * is empty, it's ok to remove. if it's not empty,
- * the install will fail
- */
- return 0;
- }
-
- /* replacing old file with new file */
- /* new is same as on disk */
- if (!(diffs & D_HASH)) {
- return 0;
- }
-
- /* new is different than on disk, but on disk is same as old */
- if (!(diffs & D_OHASH)) {
- return 0;
- }
-
- return 1;
-
-}
-#endif
-
-static int config_handler(void *f, int ncols, char **vals, char **cols) {
- struct config *conf = f;
- struct nitem nitem;
- struct stat existing;
- char *save = 0;
- char *note = 0;
- char *notehash = 0;
- int update = 0;
-
- if (!read_item(conf, ncols, vals, cols, &nitem)) {
- fprintf(stderr, "can't read item\n");
- conf->errors++;
- return conf->errabort;
- }
-
- unsigned int diffs = file_compare(&nitem, &existing);
- if (diffs >= D_ERROR) {
- return seterror(conf, "can't check %s", nitem.dest);
- }
-
- int exist = (!(diffs & D_NOEXIST));
- int sametype = (!(diffs & D_TYPE));
- //int mdsame = (!(diffs & D_MD));
- int hashsame = (!(diffs & D_HASH));
- int oldhashsame = (!(diffs & D_OHASH));
- int isdir = (diffs & D_ISDIR);
- int eisdir = (diffs & D_EISDIR);
- update = (nitem.op == OP_UPDATE);
-
- notehash = nitem.hash;
-
- /* if the file doesn't exist in the system, nothing to do */
- /* could possibly note if we expected it, but the regular handling
- * should do that
- */
- if (!exist) {
- return 0;
- }
-
- if (nitem.op == OP_UPDATE && !nitem.oldwasconf) {
- /* possibly save anyway */
- return 0;
- }
-
- /* so, old was conf, and something exists in the filesystem */
-
- if (!sametype) {
- warn("won't %s %s%s, %s exists",
- nitem.op == OP_NEW ? "install" : nitem.opstr,
- nitem.path,
- isdir ? "/" : "",
- eisdir ? "directory" : "file"
- );
- conf->errors++;
- return conf->errabort;
- }
-
- /* all below are same type of file */
- /* what about sametype, but old was different type */
-
- if (isdir) {
- return 0;
- }
-
- /* save or note cases */
-
- if (nitem.op == OP_REMOVE) {
- save ="saved removed config file %.8s";
- } else
-
- if (nitem.op == OP_UPDATE) {
- if (!nitem.configuration) {
- /* replacing config with non-config */
- save = "replacing configuration file %.8s with non-configuration file";
- } else if (oldhashsame) {
- /* config file hasn't changed from old default,
- * so go ahead and install the new one
- */
- save = "replaced old default config (%.8s) with new one.";
- notehash = nitem.ohash;
- } else {
- note = "kept existing config file. new default version is %.8s";
- save = 0;
- }
- } else
-
- if (nitem.op == OP_NEW && !hashsame) {
- note = "config file already existed. would have installed %.8s";
- save = 0;
- }
-
- /*
- * save files, add notes
- */
- if (!conf->dryrun) {
- if (save) {
- warn("saving config file: %s (root %s)", nitem.path, conf->rootdir ? conf->rootdir : "/");
- save_config_file(conf, &nitem, save);
- }
- if (note) {
- zpm_note_add(conf->log, nitem.pkglist, nitem.path, nitem.hash,
- note, nitem.hash);
- }
- } else {
- if (save) {
- fprintf(stderr, "dry run: %s %s: ", nitem.pkglist,
- nitem.path);
- warn(save, notehash);
- }
- if (note) {
- fprintf(stderr, "dry run: %s %s: ", nitem.pkglist,
- nitem.path);
- warn(note, notehash);
- }
-
- }
-
- return 0;
-}
-
-static void handle_config_files(struct config *conf) {
- 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 syncinfo");
-
- sqlite3_str_appendall(s," where configuration > 0 and op in ('new','update','remove')");
-
- if (conf->reverse) {
- sqlite3_str_appendall(s," order by length(path) desc, path desc");
- }
-
- sql = sqlite3_str_value(s);
-
- rv = zpm_exec(conf->log, sql, config_handler, 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->errors && conf->exitonerror) {
- zpm_close(conf->log);
- zpm_close(conf->src);
- exit(EXIT_FAILURE);
- }
-}
-
-static int install_files(void *f, int ncols, char **vals, char **cols) {
- struct config *conf = f;
- struct nitem nitem;
- struct stat existing;
- int update = 0;
-
- /* put the result row in a hash table? May not actually
- * be faster
- */
- if (!read_item(conf, ncols, vals, cols, &nitem)) {
- fprintf(stderr, "can't read item\n");
- return conf->errabort;
- }
-
-#if 0
- int64_t used, high;
- used = sqlite3_memory_used()/1024/1024;
- high = sqlite3_memory_highwater(0)/1024/1024;
- fprintf(stderr, "memory = %ld MB / %ld MB\n", used, high);
-#endif
- char action[40];
- sprintf(action, "%.8s %c", nitem.opstr, nitem.ftype);
- conf->ops_completed++;
- update_progress(conf, action, nitem.dest);
-
- unsigned int diffs = file_compare(&nitem, &existing);
- if (diffs >= D_ERROR) {
- return seterror(conf, "can't check %s", nitem.dest);
- }
-
- /* updates:
- * exist & same type & md same & hash same: do nothing, but warn bug
- * exist & same type & md diff & hash same: fix md
- * exist & same type & md same & hash diff: replace
- * exist & same type & md diff & hash diff: replace & fix
- * no exist: install and warn
- * dir & not dir : remove, mkdir
- * not dir & not dir & diff type: remove, install
- * not dir & dir : remove dir if empty, error if not empty, install
- *
- * installs:
- * no exist: create leading dirs, install
- *
- * exist & same type & md same & hash same & accept or over: do nothing
- * exist & same & md diff or hash diff & overwrite : update
- * exist & same & md diff or hash diff & accept : error, can't accept
- * exist & same & md diff or hash diff & not accept : error
- *
- * exist & different type & not overwrite : error
- * not dir & not dir & overwrite : remove and install
- * not dir & dir & overwrite: remove empty or error, install
- * dir & dir & overwrite: fix md
- * dir & not dir & overwrite: remove and mkdir
- */
- int exist = (!(diffs & D_NOEXIST));
- int sametype = (!(diffs & D_TYPE));
- int mdsame = (!(diffs & D_MD));
- int hashsame = (!(diffs & D_HASH));
- int ohashsame = (!(diffs & D_OHASH));
- int isdir = (diffs & D_ISDIR);
- int eisdir = (diffs & D_EISDIR);
- int accept = conf->accept;
- int overwrite = conf->overwrite;
- int installing = (nitem.op == OP_NEW);
- update = (nitem.op == OP_UPDATE);
-
- /* if a config file doesn't exist on disk, go ahead and do
- * whatever you were going to do, this logic here just
- * needs to determine if we should skip what we were going to do
- *
- * if the old item was a configuration item and the new one isn't, it
- * will have been saved earlier, so we can just go ahead. so we only
- * test for an existing file, where the item is a configuration file
- */
- if (nitem.configuration && exist) {
- if (!sametype && !conf->overwrite) {
- return seterror(conf, "configuration file exists with different type: %s", nitem.dest);
- }
-
- if (conf->accept) {
- return 0;
- }
-
- if (isdir && !mdsame) return 0;
- if (!isdir && !ohashsame) return 0;
- }
-
- if (update) {
- if (!exist) {
- /* warn, it should exist */
- fprintf(stderr, "%s missing, installing\n", nitem.dest);
- return install(conf, &nitem, 3);
- }
-
- /* file exists in filesystem */
- if (sametype) {
- if (mdsame && hashsame) {
- /* warn, bug in logic. This shouldn't occur,
- * because if there is nothing to do, it
- * shouldn't be listed as an update
- */
- /* could be an update. We're checking against
- * what's actually on disk, not what was
- * expected to have been on disk. So, if
- * the admin has modified the file, or if
- * it had been installed ignoring the user
- * and group, it might be correct on disk
- * but not as in the local database
- */
-
- /* TODO detect whether this a logic bug or
- * an on-disk difference
- */
-#if 0
- fprintf(stderr, "%s should not be an update\n", nitem.dest);
- fprintf(stderr, "old hash: %s\n", nitem.ohash);
- fprintf(stderr, "new hash: %s\n", nitem.hash);
- fprintf(stderr, "old mds: %s\n", nitem.omds);
- fprintf(stderr, "new mds: %s\n", nitem.mds);
-#endif
- /* do nothing */
- return 0;
- }
- if (!mdsame && hashsame) {
- /* fix md */
- return set_md(conf, &nitem);
- }
-
- if (!hashsame) {
- /* doesn't matter on the md */
- int flags = INS_MD | INS_CLD;
- if (nitem.ftype == 'l') {
- flags |= INS_UNLINK;
- }
- return install(conf, &nitem, flags);
- }
- }
-
- /* file exists, and is not the same type */
-
- if (isdir && !eisdir) {
- /* remove existing */
- /* mkdir */
- return install(conf, &nitem, 7);
- }
- if (!isdir && eisdir) {
- /* remove dir, or error */
- /* install */
- return install(conf, &nitem, 11);
- }
- if (!isdir && !isdir) {
- /* necessarily !sametype, sametype handled above */
- /* remove existing */
- /* install */
- return install(conf, &nitem, 7);
- }
- /* error, should not be possible, assert(0)? */
- fprintf(stderr,"impossible state: %s:%d\n", __func__, __LINE__);
- }
-
- if (installing) {
- if (!exist) {
- return install(conf, &nitem, 3);
- }
-
- /* file exists in filesystem */
- if (sametype) {
- if (mdsame && hashsame && (accept || overwrite)) {
- /* do nothing */
- if (conf->dryrun || conf->verbose > 1) {
- fprintf(stderr, "accept %s: %s\n",
- eisdir ? "directory" : "file", nitem.dest);
- }
- return 0;
- }
-
- if (mdsame && isdir && conf->acceptdir) {
- return 0;
- }
-
- if (!mdsame && isdir && conf->ignoredirmd) {
- if (conf->verbose > 1) {
- fprintf(stderr, "ignoring directory metadata difference: %s\n", nitem.dest);
- }
- return 0;
- }
-
- if (mdsame && hashsame && !(accept || overwrite)) {
- /* error */
- return seterror(conf, "file exists: %s", nitem.dest);
- }
-
- if (mdsame && !hashsame && overwrite) {
- /* install */
- return install(conf, &nitem, eisdir ? 11 : 7);
- }
-
- if (nitem.configuration && accept) {
- /* accept a changed config file */
- if (conf->dryrun || conf->verbose) {
- fprintf(stderr, "accept %smodified config %s: %s\n", (!mdsame || !hashsame) ? "" : "un",
- eisdir ? "directory" : "file", nitem.dest);
- }
- return 0;
- }
-
- if (mdsame && !hashsame && !overwrite) {
- /* accept doesn't matter, since it's
- * not an acceptable file */
- /* error */
- if (nitem.ftype == 'l') {
- char link[1024];
- ssize_t lsize;
- lsize = readlink(nitem.dest, link, sizeof link);
- if (lsize == -1 || (size_t)lsize >= sizeof link) {
- return seterror(conf, "%s (linkdiff): expecting %s -> %s, unable to read link", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link);
- } else {
- link[lsize] = 0;
- /* links must be different */
- return seterror(conf, "%s (linkdiff): expecting %s -> %s, have -> %s", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link);
- }
- } else {
- return seterror(conf, "%s (hashdiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
- }
- }
- if (!mdsame && hashsame && overwrite) {
- /* fix md */
- return set_md(conf, &nitem);
- }
- if (!mdsame && hashsame && !overwrite) {
- /* accept doesn't matter, since it's
- * not an acceptable file */
- /* error */
- return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
- }
- if (!mdsame && !hashsame && overwrite) {
- /* install */
- return install(conf, &nitem, eisdir ? 11 : 7);
- }
- if (!mdsame && !hashsame && !overwrite) {
- /* accept doesn't matter, since it's
- * not an acceptable file */
- /* error */
- return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
- }
- /* error, should be impossible */
- return seterror(conf, "impossible state reached");
- }
-
- /* file exists, and is not the same type */
- if (!overwrite) {
- /* error */
- return seterror(conf, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
- }
-
- /* not the same type, but ok to overwrite */
- if (!eisdir) {
- /* remove existing */
- return install(conf, &nitem, 7);
- }
-
- /* existing path is a directory */
- if (isdir) {
- /* fix md */
- /* impossible, if isdir and eisdir, would be same type
- */
- return set_md(conf, &nitem);
- } else {
- /* remove empty dir or error */
- /* install */
- return install(conf, &nitem, 11);
- }
- /* if we get here, we missed a case */
- return seterror(conf, "impossible state 2 reached");
- }
-
- /* TODO extra verbose print perms, mtime, etc, probably ls -l format
- */
- if (conf->verbose) {
- printf("%s\n", nitem.path);
- }
-
- return 0;
-}
-
-static void check_conflicts(struct config *conf, char *conflict_type,
- 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 syncconflicts");
-
- if (conflict_type) {
- sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
- }
- if (conf->reverse) {
- sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
- } else {
- sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
-
- }
-
- sql = sqlite3_str_value(s);
-
- 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 */
-}
-
-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 syncinfo");
-
- 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);
-
- 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 0
- if (conf->log->errmsg) {
- fprintf(stderr, "error: %s\n", conf->log->errmsg);
- }
-#endif
- if (conf->errors && conf->exitonerror) {
- zpm_close(conf->log);
- zpm_close(conf->src);
- exit(EXIT_FAILURE);
- }
- /* TODO final report function in conf var */
-}
-
-static void count_ops(struct config *conf) {
- conf->ops_remove = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'remove'");
- conf->ops_update = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'update'");
- conf->ops_install = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'new'");
- conf->ops_total = conf->ops_remove + conf->ops_update + conf->ops_install;
-}
-
-int main(int ac, char **av) {
- struct zpm localdb;
- struct zpm pkgdb;
- int opt;
- char *pkgdbfile = 0, *localdbfile = 0;
- char *s;
-
- struct config conf = { 0 };
-
- conf.errabort = 1;
- conf.errors = 0;
- conf.conflicts = 0;
- conf.verbose = 1;
- conf.dryrun = 0;
- conf.setuser = 1;
- conf.setgroup = 1;
- conf.log = 0;
- conf.src = 0;
- conf.rootdir = 0;
- conf.reverse = 0;
- conf.overwrite = 0;
- conf.accept = 0;
- conf.acceptdir = 1;
-
- if (geteuid() != 0) {
- conf.setuser = 0;
- conf.setgroup = 0;
- }
-
- localdbfile = ZPM_LOCAL_DB;
- if ((s = getenv("ZPMDB"))) {
- localdbfile = s;
- }
-
- if ((s = getenv("ZPM_ROOT_DIR"))) {
- 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:vqOAMDp")) != -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 'q': conf.verbose--; break;
- case 'C': conf.errabort = 0; break;
- case 'R': conf.rootdir = optarg; break;
- case 'N': conf.setuser = 0; conf.setgroup = 0; break;
- case 'O': conf.overwrite = 1; break;
- case 'A': conf.accept = 1; break;
- case 'M': conf.ignoredirmd = 1;
- case 'D': conf.acceptdir = 0;
- case 'p': conf.progress++;
- 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);
- }
-
- if (!zpm_open(&localdb, localdbfile)) {
- fprintf(stderr, "can't open zpm db %s\n", localdbfile);
- exit(EXIT_FAILURE);
- }
- conf.log = &localdb;
-
- if (pkgdbfile) {
- /* TODO open read-only */
- if (!zpm_open(&pkgdb, pkgdbfile)) {
- fprintf(stderr, "can't open src db %s\n", localdbfile);
- exit(EXIT_FAILURE);
- } else {
- conf.src = &pkgdb;
- }
- }
-
- /* TODO set conf var to finalize error reporting */
- if (conf.verbose) {
- fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
- conf.rootdir ? conf.rootdir : "/",
- localdbfile, pkgdbfile);
- }
-
- conf.errors = 0;
- conf.exitonerror = 0;
- check_conflicts(&conf, NULL, report_conflicts);
-
- if (conf.conflicts) {
- fprintf(stderr, "%d conflicts reported, aborting sync\n",
- conf.conflicts);
- conf.errors++;
- } else {
- /* no point in running it if we're just going to
- * overwrite everything
- */
- if (!conf.overwrite && !conf.accept && !conf.dryrun) {
- runstage(&conf, "new", check_existing);
- }
-
- if (conf.verbose) {
- fprintf(stderr, "beginning %ssync\n", conf.dryrun ?
- "dryrun " : "");
- }
-
- if (!conf.errors) {
- handle_config_files(&conf);
- }
-
- /* have to do the removes first otherwise
- * old files may conflict with update file
- * type changes
- */
- if (!conf.errors) {
- conf.exitonerror = conf.dryrun ? 0 : 1;
- conf.errabort = conf.dryrun ? 0 : 1;
- count_ops(&conf);
- fprintf(stderr, "file ops: %lu\n", conf.ops_total);
- if (conf.ops_remove > 0) {
- if (conf.verbose) {
- fprintf(stderr, "removing %lu file%s\n", conf.ops_remove, conf.ops_remove > 1 ? "s" : "");
- }
- conf.reverse = 1;
- conf.ops_completed = 0;
- conf.ops_total = conf.ops_remove;
- runstage(&conf, "remove", remove_files);
- if (conf.verbose && conf.progress < 2) {
- fprintf(stderr, " done\n");
- fflush(stderr);
- }
- }
-
- if (conf.ops_update > 0) {
- if (conf.verbose) {
- fprintf(stderr, "updating %lu file%s\n", conf.ops_update, conf.ops_update > 1 ? "s" : "");
- }
- conf.reverse = 0;
- conf.ops_completed = 0;
- conf.ops_total = conf.ops_update;
- runstage(&conf, "update", install_files);
- if (conf.verbose && conf.progress < 2) {
- fprintf(stderr, " done\n");
- fflush(stderr);
- }
- }
-
- if (conf.ops_install > 0) {
- if (conf.verbose) {
- fprintf(stderr, "installing %lu file%s\n", conf.ops_install, conf.ops_install > 1 ? "s" : "");
- }
- conf.reverse = 0;
- conf.ops_completed = 0;
- conf.ops_total = conf.ops_install;
- runstage(&conf, "new", install_files);
- if (conf.verbose && conf.progress < 2) {
- fprintf(stderr, " done\n");
- fflush(stderr);
- }
- }
- }
- }
-
- zpm_close(&localdb);
- zpm_close(conf.src);
- return conf.errors ? 1 : 0;
-}