+ 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 int remove_files(void *f, int ncols, char **vals, char **cols) {
+ struct config *conf = f;
+ char *dest;
+ struct stat st;
+ int flags = 0;
+
+ dest = COL("dest");
+ if (!dest) return seterror(conf,"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;
+ }
+ fflush(stdout);
+ return 0;
+ }
+
+ if (conf->verbose) {
+ if (conf->progress == 2) {
+ fprintf(stderr, "%s(%s)\n", flags ? "rmdir" : "unlink", dest);
+ } else if (conf->progress == 1) {
+ /* overwrite */
+ pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
+ conf->ops_completed++;
+ conf->ops_completed++;
+ } else {
+ pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
+ conf->ops_completed++;
+ }
+ }
+
+ errno = 0;
+
+ if (lstat(dest, &st) == -1) {
+ switch (errno) {
+ case ENOENT:
+ /* TODO chatter if verbose */
+ 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 */
+
+ errno = 0;
+
+ if (unlinkat(AT_FDCWD, dest, flags) == -1) {
+ switch (errno) {
+ case ENOENT:
+ break;
+ case ENOTEMPTY: /* fall through */
+ case EEXIST:
+ /* TODO chatter, or possibly require */
+ 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);
+
+ /* TODO what if old was a directory? */
+ if (!n->configuration) {
+ /* replacing conf with non-conf */
+ /* absorb file, mark todo */
+ return 0;
+ }