#include <pwd.h>
#include <grp.h>
#include <math.h>
+#include <stdarg.h>
/* needed for S_IFMT and AT_FDCWD */
#include <fcntl.h>
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) {
#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 (ftype == 'd') {
+ if (diffs > 1) {
+ return seterror(conf, "absorb and overwrite not implemented");
+ }
+
+ 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) {
fprintf(stderr, "symlink %s -> %s\n", dest, target);
}
if (symlink(target, dest) == -1) {
- perror("symlink failed");
- IERR("can't symlink");
+ 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);
}
}
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.conflicts);
conf.errors++;
} else {
- runstage(&conf, "new", check_existing);
+ /* 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;