#include <pwd.h>
#include <grp.h>
#include <math.h>
+#include <stdarg.h>
/* needed for S_IFMT and AT_FDCWD */
#include <fcntl.h>
struct zpm *src;
char *dbfile;
char *rootdir;
- int errabort, errors, verbose, dryrun;
+ int errabort, errors, verbose, dryrun, conflicts;
int setuser, setgroup;
int reverse, exitonerror;
+ int overwrite, absorb;
};
static void usage() {
return val;
}
-#define IERR(x) do { conf->errors++; conf->log->errmsg = strdup(x); return conf->errabort; } while (0)
#define COL(x) column(x, ncols, vals, cols)
#define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
+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;
+}
+
+#define IERR(x) return seterror(conf, x)
+
static char *ops[] = { "new", "remove", "update", 0 };
static int getop(char *opstr) {
static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
struct config *conf = f;
- char *path;
- char *pkg;
+ 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->errors++;
- fprintf(stderr, "%s owned by %s\n", path, pkg);
+ conf->conflicts++;
return 0;
}
return 0;
}
+#define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
+
+/* 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(char *path, char *hash, int ftype, uid_t uid,
+ gid_t gid, int perms) {
+ struct stat st;
+ int etype = 0, stat_type;
+ char ehash[ZPM_HASH_STRLEN+1];
+ unsigned int diff = 0;
+
+ switch (ftype) {
+ case 'd': etype = S_IFDIR; break;
+ case 'r': etype = S_IFREG; break;
+ default: etype = 0; break;
+ }
+
+ errno = 0;
+ /* new file, so check type, hash, etc */
+ if (lstat(path, &st) == 0) {
+ stat_type = st.st_mode & S_IFMT;
+ if (stat_type != etype) {
+ diff &= 4;
+ }
+ if (stat_type == S_IFDIR && etype != S_IFDIR) {
+ diff &= 2;
+ }
+ if (hash && etype == S_IFREG && stat_type == S_IFREG) {
+ zpm_hash(path, ehash, 0);
+ if (strcmp(hash, ehash) != 0) {
+ diff &= 8;
+ }
+ }
+ if (uid != st.st_uid) {
+ diff &= 16;
+ }
+ if (gid != st.st_gid) {
+ diff &= 32;
+ }
+ if (perms != (st.st_mode & 07777)) {
+ diff &= 64;
+ }
+ } else {
+ switch(errno) {
+ case ENOENT: diff &= 1; break;
+ default: diff &= 128; break;
+ }
+ }
+
+ return diff;
+}
+
static int install_files(void *f, int ncols, char **vals, char **cols) {
struct config *conf = f;
struct passwd *pw;
/* 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");
+ return setsyserr(conf, "unable to create leading directories for %s\n", dest);
+ }
+
+ unsigned int diffs = file_compare(dest, hash, ftype, uid, gid, mode);
+
+ /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
+ /* 4 == ftype different */
+ /* 8 = hash different when both are regular files */
+ if (diffs == 128) {
+ return seterror(conf, "can't check %s", dest);
+ }
+
+ if (diffs > 1) {
+ return seterror(conf, "absorb and overwrite not implemented");
}
- if (ftype == 'd') {
+ if (ftype == 'd') {
if (mkdir(dest, mode) == -1) {
- IERR("can't mkdir");
+ return setsyserr(conf, "can't create directory %s",
+ dest);
}
} 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");
}
} else if (ftype == 'l') {
char *target = COL("target");
if (!target) {
- fprintf(stderr, "no target for symlink %s\n", path);
- conf->errors++;
- return conf->errabort;
+ return seterror(conf, "no target for symlink %s\n",
+ path);
}
if (strlen(target) == 0) {
IERR("zero length symlink not allowed");
}
- if (conf->verbose > 1) {
- fprintf(stderr, "symlink %s -> %s\n", path, target);
+ if (conf->verbose > 0) {
+ fprintf(stderr, "symlink %s -> %s\n", dest, target);
}
- if (symlink(target, path) == -1) {
- IERR("can't symlink");
+ if (symlink(target, dest) == -1) {
+ return setsyserr(conf, "symlink failed");
}
} else {
fprintf(stderr, "unhandled filetype %c\n", ftype);
if (conf->setuser && conf->setgroup) {
if (chown(dest, uid, gid) == -1) {
- IERR("can't chown");
+ return setsyserr(conf, "can't chown %s", dest);
}
}
- struct timespec times[2] = { 0 };
+ struct timespec times[2] = { {0}, {0} };
double mtime = strtod(COL("mtime"),NULL);
times[0].tv_nsec = UTIME_OMIT;
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);
+ 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 */
+}
+
static void runstage(struct config *conf, char *stage,
int (callback)(void *, int, char **, char **)) {
int rv;
} else {
sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
}
- sqlite3_str_appendall(s, " as dest from install_status");
+ sqlite3_str_appendall(s, " as dest from syncinfo");
if (stage) {
sqlite3_str_appendf(s," where op = %Q", stage);
conf.errabort = 1;
conf.errors = 0;
+ conf.conflicts = 0;
conf.verbose = 0;
conf.dryrun = 0;
conf.setuser = 1;
conf.src = 0;
conf.rootdir = 0;
conf.reverse = 0;
+ conf.overwrite = 0;
+ conf.absorb = 0;
if (geteuid() != 0) {
conf.setuser = 0;
* args are pkgid triple, but will do a pkg find on the pkgdb
*/
- while ((opt = getopt(ac, av, "f:d:c:nCR:v")) != -1) {
+ while ((opt = getopt(ac, av, "f:d:c:nCR:vOA")) != -1) {
switch (opt) {
case 'd': localdbfile = optarg; break;
case 'f': pkgdbfile = optarg; 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.absorb = 1; break;
default:
usage();
exit(EXIT_FAILURE);
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;
+ 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 == 0 || conf.absorb == 0) {
+ 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);