+}
+
+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;
+
+ /* TODO 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 (conf->verbose && !conf->dryrun) {
+ fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype,
+ 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 isdir = (diffs & D_ISDIR);
+ int eisdir = (diffs & D_EISDIR);
+ int accept = conf->absorb;
+ int overwrite = conf->overwrite;
+ int installing = (nitem.op == OP_NEW);
+ update = (nitem.op == OP_UPDATE);
+
+ if (update) {
+ if (!exist) {
+ /* warn, it should exist */
+ fprintf(stderr, "%s missing, installing", nitem.dest);
+ return install(conf, &nitem, 3);
+ }
+
+ switch (adjust_for_config(conf, &nitem, diffs)) {
+ case -1: return conf->errabort; break;
+ case 1:
+ fprintf(stderr, "skipping changed default config file: %s\n", nitem.dest);
+ return 0; break;
+ default: break;
+ }
+
+ /* 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 (mdsame && !hashsame) {
+ /* install */
+ return install(conf, &nitem, 3);
+ }
+ if (!mdsame && !hashsame) {
+ /* install */
+ return install(conf, &nitem, 3);
+ }
+ }
+
+ /* 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);