1 #define _POSIX_C_SOURCE 200809L
17 /* needed for S_IFMT and AT_FDCWD */
26 struct zpm *log; /* logging db will be attached as "log" */
30 int errabort, errors, verbose, dryrun, conflicts;
31 int setuser, setgroup;
32 int reverse, exitonerror;
33 int overwrite, accept, acceptdir, ignoredirmd;
34 int ops_total, ops_completed;
35 int ops_remove, ops_remove_completed;
36 int ops_update, ops_update_completed;
37 int ops_install, ops_install_completed;
38 int progress; /* type of progress meter */
51 char *pkglist; /* space separated */
55 int configuration, oldwasconf;
56 struct timespec times[2];
60 printf("usage: zpm $scriptname [-fncC] args ...\n");
63 static void pdots(int len, int ch, int was, int now, int total) {
64 was = len * was / total;
68 now = len * now / total;
75 static int seterror(struct config *conf, char *msgfmt, ...) {
82 vsnprintf(msg, sizeof msg, msgfmt, ap);
86 if (conf->log->errmsg) {
87 free(conf->log->errmsg);
90 conf->log->errmsg = strdup(msg);
93 fprintf(stderr, "%s\n", msg);
96 return conf->errabort;
99 static int setsyserr(struct config *conf, char *msgfmt, ...) {
106 va_start(ap, msgfmt);
107 printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
111 /* nothing we can really do */
112 return conf->errabort;
115 if ((size_t)printed < sizeof msg) {
116 snprintf(msg+printed, sizeof msg - printed, ": %s",
121 if (conf->log->errmsg) {
122 free(conf->log->errmsg);
125 conf->log->errmsg = strdup(msg);
128 fprintf(stderr, "%s\n", msg);
131 return conf->errabort;
134 static int exists(char *path, mode_t *mode) {
137 if (lstat(path, &st) == -1) {
140 if (mode) *mode = st.st_mode;
144 /* TODO maintain a list of already created directories */
145 static int create_leading_dirs(char *path) {
148 char pcopy[ZPM_PATH_MAX];
153 delim = strrchr(pcopy, '/');
154 if (!delim) return 1; /* not an error, but no leading dirs */
156 /* cut off last component */
165 delim = strchr(s, '/');
171 /* try to create the directory, if it exists
172 * and is a directory or a symlink, that's ok
173 * should be (eventually) a symlink to a directory
174 * so we want stat here, not lstat
176 if (mkdir(pcopy, 0755) == -1) {
179 if (stat(pcopy, &st) == -1) {
183 switch (st.st_mode & S_IFMT) {
203 static char *column(char *col, int ncols, char **vals, char **cols) {
207 for (i=0; i < ncols; i++) {
208 // fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
210 if (!strcmp(col, cols[i])) {
218 #define COL(x) column(x, ncols, vals, cols)
219 #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
222 static char *ops[] = { "new", "remove", "update", 0 };
230 static int getop(char *opstr) {
233 if (!opstr) return 0;
234 for (i=0;ops[i];i++) {
235 if (!strcmp(opstr, ops[i])) {
242 static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
243 struct config *conf = f;
244 char *path, *hash, *pkg, *conflict_type, *mds;
248 conflict_type = COL("conflict");
249 if (!strcmp(conflict_type, "hash")) {
251 fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
254 if (!strcmp(conflict_type, "md")) {
256 fprintf(stderr, "md conflict: package %s path %s md %s\n",
259 fprintf(stderr, "%s conflict: package %s path %s\n",
260 conflict_type, pkg, path);
267 static int read_item(struct config *conf, int ncols, char **vals, char **cols,
273 struct nitem zero = { 0 };
279 seterror(conf, "can't determine op");
285 seterror(conf, "can't determine op");
289 n->path = COL("path");
291 seterror(conf, "no file path");
294 if (strlen(n->path) == 0) {
295 seterror(conf, "zero length path not allowed");
299 /* TODO config to dishonor setuid/setgid */
300 n->dest = COL("dest");
302 seterror(conf, "no file dest");
306 if (strlen(n->dest) == 0) {
307 seterror(conf, "zero length dest not allowed");
314 seterror(conf, "can't determine mode");
318 n->mode = strtoul(val, NULL, 8);
320 val = COL("configuration");
322 seterror(conf, "can't determine config status");
325 lval = strtol(val, NULL, 10);
327 n->configuration = ((lval & 1) != 0);
328 n->oldwasconf = ((lval & 2) != 0);
330 val = COL("filetype");
331 if (!val || strlen(val) == 0) {
332 seterror(conf, "can't determine file type");
337 /* these can be null */
338 n->ohash = COL("ohash");
340 n->omds = COL("omds");
341 n->pkglist = COL("pkglist");
343 if (n->ftype == 'r') {
344 n->hash = COL("hash");
346 seterror(conf, "can't get hash");
349 } else if (n->ftype == 'l') {
350 n->target = COL("target");
352 seterror(conf, "can't get target");
355 if (strlen(n->target) == 0) {
356 seterror(conf, "zero length target not allowed");
363 val = COL("username");
365 seterror(conf, "no username");
370 seterror(conf, "no passwd entry");
378 if (conf->setgroup) {
379 val = COL("groupname");
381 seterror(conf, "no groupname");
386 seterror(conf, "no group entry for %s", val);
395 double mtime = strtod(COL("mtime"),NULL);
397 mtime = (double)time(NULL);
400 n->mtime = (time_t)mtime;
402 n->times[0].tv_sec = 0;
403 n->times[0].tv_nsec = UTIME_OMIT;
404 n->times[1].tv_sec = (time_t)llrint(floor(mtime));
405 n->times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
410 /* file does not exist */
411 #define D_NOEXIST 0x1
412 /* files are different types */
414 /* metadata is different */
416 /* content or link target is different */
418 /* file to be installed is a directory */
420 /* path on disk is a directory */
421 #define D_EISDIR 0x20
422 /* usernames different */
424 /* group names different */
426 /* file mode is different */
428 /* mtimes are different */
429 #define D_MTIME 0x200
430 /* the hash of the file we are supposedly replacing is different than
431 * the the hash of the file on disk
433 #define D_OHASH 0x400
434 /* an error occurred trying to compare the file (other than it doesn't exist */
435 #define D_ERROR 0x1000
436 /* there was a stat error */
437 #define D_STATERROR 0x2000
438 /* there was an error calling readlink */
439 #define D_RLERROR 0x4000
441 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
442 /* 4 == ftype different */
443 /* 8 = hash different when both are regular files */
444 static unsigned int file_compare(struct nitem *n, struct stat *st) {
445 int etype = 0, stat_type;
446 char ehash[ZPM_HASH_STRLEN+1];
447 unsigned int diff = 0;
452 case 'd': etype = S_IFDIR; diff |= D_ISDIR ; break;
453 case 'r': etype = S_IFREG; break;
454 case 'l': etype = S_IFLNK; break;
455 default: etype = 0; break;
459 /* new file, so check type, hash, etc */
460 if (lstat(n->dest, st) == 0) {
461 stat_type = st->st_mode & S_IFMT;
462 if (stat_type != etype) {
465 if (stat_type == S_IFDIR) {
469 if (n->hash && etype == S_IFREG && stat_type == S_IFREG) {
470 zpm_hash(n->dest, ehash, 0);
471 if (strcmp(n->hash, ehash) != 0) {
474 if (n->ohash && strcmp(n->ohash, ehash) != 0) {
478 if (n->hash && etype == S_IFLNK && stat_type == S_IFLNK) {
479 lsize = readlink(n->dest, link, sizeof link);
481 if (lsize == -1 || lsize == sizeof link) {
486 if (strcmp(n->target, link) != 0) {
491 if (n->uid != st->st_uid) {
495 if (n->gid != st->st_gid) {
499 if (n->mode != (st->st_mode & 07777)) {
505 case ENOENT: diff |= D_NOEXIST; break;
506 default: diff |= (D_STATERROR|D_ERROR); break;
514 /* 0 = not acceptable
515 * 1 = accept and create/update/remove
517 * 3 = remove and overwrite
518 * 4 = update metadata
520 static int acceptable(struct config *conf, unsigned int diffs, int op) {
521 int exist = (!(diffs & D_NOEXIST));
522 int sametype = (!(diffs & D_TYPE));
523 int mdsame = (!(diffs & D_MD));
524 int hashsame = (!(diffs & D_HASH));
525 int isdir = (diffs & D_ISDIR);
528 return op == OP_REMOVE ? 2 : 1;
531 if (op == OP_UPDATE) {
532 return sametype ? 4 : 3;
535 if (op == OP_REMOVE) {
539 /* the hard cases, should be installing new, but already exists */
542 return conf->overwrite ? 3 : 0;
545 if (mdsame && (conf->accept || conf->overwrite)) {
550 if (mdsame || conf->ignoredirmd) {
551 return conf->acceptdir ? 2 : 0;
553 if (conf->overwrite) {
558 if (hashsame && (conf->accept || conf->overwrite)) {
562 return conf->overwrite ? 3 : 0;
565 static int check_existing(void *f, int ncols, char **vals, char **cols) {
566 struct config *conf = f;
570 if (!read_item(conf, ncols, vals, cols, &nitem)) {
571 fprintf(stderr, "can't read item\n");
572 return conf->errabort;
575 if (conf->verbose > 1) {
576 fprintf(stderr, "check for existing %s\n", nitem.path);
579 if (lstat(nitem.path, &st) == -1) {
581 /* not an error, file shouldn't exist*/
584 fprintf(stderr, "unable to check %s: %s\n",
585 nitem.path, strerror(errno));
592 unsigned int diffs = file_compare(&nitem, &st);
593 if (diffs >= D_ERROR) {
594 return seterror(conf, "can't check %s", nitem.dest);
597 int action = acceptable(conf, diffs, nitem.op);
600 fprintf(stderr, "%s exists and is not acceptable\n", nitem.path);
602 fprintf(stderr, "%s exists\n", nitem.path);
610 static int remove_files(void *f, int ncols, char **vals, char **cols) {
611 struct config *conf = f;
617 if (!dest) return seterror(conf,"no file dest");
620 char *ftype = COL("filetype");
624 case 'd': printf("rmdir %s\n", dest); break;
625 default: printf("unlink %s\n", dest); break;
632 if (conf->progress == 2) {
633 fprintf(stderr, "%s(%s)\n", flags ? "rmdir" : "unlink", dest);
634 } else if (conf->progress == 1) {
636 pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
637 conf->ops_completed++;
638 conf->ops_completed++;
640 pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
641 conf->ops_completed++;
647 if (lstat(dest, &st) == -1) {
650 /* TODO chatter if verbose */
653 return seterror(conf, "can't stat %s: %s", dest, strerror(errno));
658 if (S_ISDIR(st.st_mode)) {
659 flags = AT_REMOVEDIR;
661 /* TODO check that expected filetype matches actual filetype */
666 if (unlinkat(AT_FDCWD, dest, flags) == -1) {
670 case ENOTEMPTY: /* fall through */
672 /* TODO chatter, or possibly require */
675 return seterror(conf, "can't unlink %s: %s", dest, strerror(errno));
682 #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
685 static int remove_dir(struct config *conf, char *path) {
690 setsyserr(conf, "can't rmdir %s", path);
696 static int remove_existing(struct config *conf, char *path) {
701 setsyserr(conf, "can't unlink %s", path);
707 static int set_md(struct config *conf, struct nitem *item) {
712 if (item->ftype != 'l') {
713 printf("chmod %o %s\n", item->mode, item->dest);
715 if (conf->setuser && conf->setgroup) {
716 printf("lchown %d:%d %s\n", item->uid, item->gid,
719 printf("mtime %.0f %s\n", (double)item->mtime, item->dest);
724 if (conf->setuser && conf->setgroup) {
725 rv = lchown(item->dest, item->uid, item->gid);
727 setsyserr(conf, "can't lchown %s", item->dest);
728 return conf->errabort;
732 /* have to chmod after the chown, setuid bits may (and will)
733 * be cleared after a chown
735 /* can't chmod a symlink */
736 if (item->ftype != 'l') {
737 rv = chmod(item->dest, item->mode);
740 setsyserr(conf, "can't chmod %o %s", item->mode, item->dest);
741 return conf->errabort;
746 rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW);
748 setsyserr(conf, "can't set mtime %.0f %s", (double)item->mtime,
750 return conf->errabort;
755 /* install a file or create a directory or symlink. path should not exist
758 /* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file,
759 * 8 = rmdir existing dir, 16 = return true/false
763 #define INS_UNLINK 0x4
764 #define INS_RMDIR 0x8
766 #define INS_ZPMNEW 0x20
767 static int install(struct config *conf, struct nitem *item, unsigned int flags) {
771 int mkleading = (flags & 2);
772 int setmd = (flags & 1);
773 int unlink_file = (flags & 4);
774 int rm_dir = (flags & 8);
775 int failure = conf->errabort;
785 printf("unlink %s\n", item->dest);
787 printf("rmdir %s\n", item->dest);
790 printf("install %c%o %d:%d %s", item->ftype,
791 item->mode, item->uid, item->gid,
793 if (item->ftype == 'l') {
794 printf(" -> %s", item->target);
801 source = conf->src ? conf->src : conf->log;
804 rv = remove_existing(conf, item->dest);
806 rv = remove_dir(conf, item->dest);
814 rv = create_leading_dirs(item->dest);
816 setsyserr(conf, "can't create leading dirs for %s", item->dest);
822 switch (item->ftype) {
823 case 'r': rv = zpm_extract(source, item->hash, item->dest, item->mode);
824 if (rv == 0) rv = -1;
826 case 'd': rv = mkdir(item->dest, item->mode);
828 case 'l': rv = symlink(item->target, item->dest);
835 switch (item->ftype) {
837 seterror(conf, "can't extract %s", item->dest);
840 setsyserr(conf, "install mkdir(\"%s\") failed", item->dest);
843 setsyserr(conf, "install symlink(\"%s\") failed", item->dest);
846 setsyserr(conf, "installing %s failed", item->dest);
851 return set_md(conf, item) == 0 ? success : failure;
858 * figure out what the difference is for a config file, only called
859 * for an update of a configuration file
860 * return -1 on an error
861 * return 1 if the new file should not be installed
862 * return 0 if the new file should be installed
864 static int adjust_for_config(struct config *conf, struct nitem *n, unsigned int
867 if (!n->oldwasconf) {
871 /* TODO what if old was a directory? */
872 if (!n->configuration) {
873 /* replacing conf with non-conf */
874 /* absorb file, mark todo */
875 char hash[ZPM_HASH_STRLEN+1];
878 fprintf(stderr, "importing old conf file\n");
880 if (zpm_import(conf->log, n->dest, 0, hash)) {
881 zpm_note_add(conf->log, n->pkglist, n->dest, hash,
882 "replaced config file with non-config. zpm-cat %.8s", hash);
884 fprintf(stderr, "unable to import existing config file %s\n", n->dest);
888 fprintf(stderr, "dry-run: would replace config file %s with non-config file\n", n->dest);
893 int sametype = (!(diffs & D_TYPE));
894 int isdir = (diffs & D_ISDIR);
895 int eisdir = (diffs & D_EISDIR);
897 /* both old and new are config files */
898 if (isdir && sametype) {
899 /* both config directories, can only be changing
900 * metadata, so no adjustment needed
903 fprintf(stderr, "both config dirs, ok to update\n");
909 char hash[ZPM_HASH_STRLEN+1];
911 /* replacing old file with new directory */
912 /* absorb, make note */
914 if (zpm_import(conf->log, n->dest, 0, hash)) {
915 zpm_note_add(conf->log, n->pkglist, n->dest, hash,
916 "replaced config file with config directory. zpm-cat %.8s", hash);
918 fprintf(stderr, "unable to import existing config file %s\n", n->dest);
922 fprintf(stderr, "dry-run: would replace config file %s with config directory\n", n->dest);
928 /* replacing old conf directory with a conf file.
929 * nothing needs to be done, if the directory
930 * is empty, it's ok to remove. if it's not empty,
931 * the install will fail
936 /* replacing old file with new file */
937 /* new is same as on disk */
938 if (!(diffs & D_HASH)) {
940 fprintf(stderr, "new config file is already on disk, probably shouldn't happen\n");
945 /* new is different than on disk, but on disk is same as old */
946 if (!(diffs & D_OHASH)) {
948 fprintf(stderr, "old config file not changed from default, replacing with new default\n");
950 /* ok to do the update, since same as default */
951 fprintf(stderr, "updating default config %s\n", n->dest);
955 /* new is different than on disk, and disk different than old */
958 fprintf(stderr, "new default config file is different than on disk, and old default was changed, should keep on-disk config\n");
961 zpm_note_add(conf->log, n->pkglist, n->dest, n->hash,
962 "default config file update. zpm-cat %.8s", n->hash);
963 /* TODO check for note error */
965 fprintf(stderr, "dry-run: default config file %s update\n",
972 static int install_files(void *f, int ncols, char **vals, char **cols) {
973 struct config *conf = f;
975 struct stat existing;
978 /* TODO put the result row in a hash table. May not actually
981 if (!read_item(conf, ncols, vals, cols, &nitem)) {
982 fprintf(stderr, "can't read item\n");
983 return conf->errabort;
988 used = sqlite3_memory_used()/1024/1024;
989 high = sqlite3_memory_highwater(0)/1024/1024;
990 fprintf(stderr, "memory = %ld MB / %ld MB\n", used, high);
992 if (conf->verbose && !conf->dryrun) {
993 if (conf->progress == 2) {
994 fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype,
996 } else if (conf->progress == 1) {
998 pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
999 conf->ops_completed++;
1000 conf->ops_completed++;
1002 pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
1003 conf->ops_completed++;
1007 unsigned int diffs = file_compare(&nitem, &existing);
1008 if (diffs >= D_ERROR) {
1009 return seterror(conf, "can't check %s", nitem.dest);
1013 * exist & same type & md same & hash same: do nothing, but warn bug
1014 * exist & same type & md diff & hash same: fix md
1015 * exist & same type & md same & hash diff: replace
1016 * exist & same type & md diff & hash diff: replace & fix
1017 * no exist: install and warn
1018 * dir & not dir : remove, mkdir
1019 * not dir & not dir & diff type: remove, install
1020 * not dir & dir : remove dir if empty, error if not empty, install
1023 * no exist: create leading dirs, install
1025 * exist & same type & md same & hash same & accept or over: do nothing
1026 * exist & same & md diff or hash diff & overwrite : update
1027 * exist & same & md diff or hash diff & accept : error, can't accept
1028 * exist & same & md diff or hash diff & not accept : error
1030 * exist & different type & not overwrite : error
1031 * not dir & not dir & overwrite : remove and install
1032 * not dir & dir & overwrite: remove empty or error, install
1033 * dir & dir & overwrite: fix md
1034 * dir & not dir & overwrite: remove and mkdir
1036 int exist = (!(diffs & D_NOEXIST));
1037 int sametype = (!(diffs & D_TYPE));
1038 int mdsame = (!(diffs & D_MD));
1039 int hashsame = (!(diffs & D_HASH));
1040 int isdir = (diffs & D_ISDIR);
1041 int eisdir = (diffs & D_EISDIR);
1042 int accept = conf->accept;
1043 int overwrite = conf->overwrite;
1044 int installing = (nitem.op == OP_NEW);
1045 update = (nitem.op == OP_UPDATE);
1049 /* warn, it should exist */
1050 fprintf(stderr, "%s missing, installing", nitem.dest);
1051 return install(conf, &nitem, 3);
1054 switch (adjust_for_config(conf, &nitem, diffs)) {
1055 case -1: return conf->errabort; break;
1057 fprintf(stderr, "skipping changed default config file: %s\n", nitem.dest);
1062 /* file exists in filesystem */
1064 if (mdsame && hashsame) {
1065 /* warn, bug in logic. This shouldn't occur,
1066 * because if there is nothing to do, it
1067 * shouldn't be listed as an update
1069 /* could be an update. We're checking against
1070 * what's actually on disk, not what was
1071 * expected to have been on disk. So, if
1072 * the admin has modified the file, or if
1073 * it had been installed ignoring the user
1074 * and group, it might be correct on disk
1075 * but not as in the local database
1077 /* TODO detect whether this a logic bug or
1078 * an on-disk difference
1081 fprintf(stderr, "%s should not be an update\n", nitem.dest);
1082 fprintf(stderr, "old hash: %s\n", nitem.ohash);
1083 fprintf(stderr, "new hash: %s\n", nitem.hash);
1084 fprintf(stderr, "old mds: %s\n", nitem.omds);
1085 fprintf(stderr, "new mds: %s\n", nitem.mds);
1090 if (!mdsame && hashsame) {
1092 return set_md(conf, &nitem);
1094 if (mdsame && !hashsame) {
1096 return install(conf, &nitem, 3);
1098 if (!mdsame && !hashsame) {
1100 return install(conf, &nitem, 3);
1104 /* file exists, and is not the same type */
1106 if (isdir && !eisdir) {
1107 /* remove existing */
1109 return install(conf, &nitem, 7);
1111 if (!isdir && eisdir) {
1112 /* remove dir, or error */
1114 return install(conf, &nitem, 11);
1116 if (!isdir && !isdir) {
1117 /* necessarily !sametype, sametype handled above */
1118 /* remove existing */
1120 return install(conf, &nitem, 7);
1122 /* error, should not be possible, assert(0)? */
1123 fprintf(stderr,"impossible state: %s:%d\n", __func__, __LINE__);
1128 return install(conf, &nitem, 3);
1131 /* file exists in filesystem */
1133 if (mdsame && hashsame && (accept || overwrite)) {
1135 if (conf->dryrun || conf->verbose) {
1136 fprintf(stderr, "accept %s: %s\n",
1137 eisdir ? "directory" : "file", nitem.dest);
1142 if (mdsame && isdir && conf->acceptdir) {
1146 if (!mdsame && isdir && conf->ignoredirmd) {
1147 /* TODO warn ignoring dir md */
1151 if (mdsame && hashsame && !(accept || overwrite)) {
1153 return seterror(conf, "file exists: %s", nitem.dest);
1156 if (mdsame && !hashsame && overwrite) {
1158 return install(conf, &nitem, eisdir ? 11 : 7);
1161 if (nitem.configuration && accept) {
1162 /* accept a changed config file */
1163 if (conf->dryrun || conf->verbose) {
1164 fprintf(stderr, "accept %smodified config %s: %s\n", (!mdsame || !hashsame) ? "" : "un",
1165 eisdir ? "directory" : "file", nitem.dest);
1170 if (mdsame && !hashsame && !overwrite) {
1171 /* accept doesn't matter, since it's
1172 * not an acceptable file */
1174 if (nitem.ftype == 'l') {
1177 lsize = readlink(nitem.dest, link, sizeof link);
1178 if (lsize == -1 || (size_t)lsize >= sizeof link) {
1179 return seterror(conf, "%s (linkdiff): expecting %s -> %s, unable to read link", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link);
1182 /* links must be different */
1183 return seterror(conf, "%s (linkdiff): expecting %s -> %s, have -> %s", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link);
1186 return seterror(conf, "%s (hashdiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1189 if (!mdsame && hashsame && overwrite) {
1191 return set_md(conf, &nitem);
1193 if (!mdsame && hashsame && !overwrite) {
1194 /* accept doesn't matter, since it's
1195 * not an acceptable file */
1197 return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1199 if (!mdsame && !hashsame && overwrite) {
1201 return install(conf, &nitem, eisdir ? 11 : 7);
1203 if (!mdsame && !hashsame && !overwrite) {
1204 /* accept doesn't matter, since it's
1205 * not an acceptable file */
1207 return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1209 /* TODO error, should be impossible */
1210 return seterror(conf, "impossible state reached");
1213 /* file exists, and is not the same type */
1216 return seterror(conf, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1219 /* not the same type, but ok to overwrite */
1221 /* remove existing */
1222 return install(conf, &nitem, 7);
1225 /* existing path is a directory */
1228 /* impossible, if isdir and eisdir, would
1232 return set_md(conf, &nitem);
1234 /* remove empty dir or error */
1236 return install(conf, &nitem, 11);
1238 /* if we get here, we missed a case */
1240 return seterror(conf, "impossible state 2 reached");
1243 /* TODO extra verbose print perms, mtime, etc, probably ls -l
1246 if (conf->verbose) {
1247 printf("%s\n", nitem.path);
1253 static void check_conflicts(struct config *conf, char *conflict_type,
1254 int (callback)(void *, int, char **, char **)) {
1260 s = sqlite3_str_new(conf->log->db);
1261 sqlite3_str_appendall(s, "select *, ");
1262 if (conf->rootdir) {
1263 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
1265 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
1267 sqlite3_str_appendall(s, " as dest from syncconflicts");
1269 if (conflict_type) {
1270 sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
1272 if (conf->reverse) {
1273 sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
1275 sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
1279 sql = sqlite3_str_value(s);
1281 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
1283 sqlite3_str_finish(s);
1286 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
1288 fprintf(stderr, "database error: %s\n", errmsg);
1291 if (conf->log->error == 1) {
1292 fprintf(stderr, "unable to allocate memory\n");
1294 fprintf(stderr, "zpm_exec failure: %s\n",
1295 conf->log->errmsg ? conf->log->errmsg : "unknown");
1298 if (conf->log->errmsg) {
1299 fprintf(stderr, "error: %s\n", conf->log->errmsg);
1301 if (conf->errors && conf->exitonerror) {
1302 zpm_close(conf->log);
1303 zpm_close(conf->src);
1306 /* TODO final report function in conf var */
1309 static void runstage(struct config *conf, char *stage,
1310 int (callback)(void *, int, char **, char **)) {
1316 s = sqlite3_str_new(conf->log->db);
1317 sqlite3_str_appendall(s, "select *, ");
1318 if (conf->rootdir) {
1319 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
1321 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
1323 sqlite3_str_appendall(s, " as dest from syncinfo");
1326 sqlite3_str_appendf(s," where op = %Q", stage);
1328 if (conf->reverse) {
1329 sqlite3_str_appendall(s," order by length(path) desc, path desc");
1332 sql = sqlite3_str_value(s);
1334 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
1336 sqlite3_str_finish(s);
1339 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
1341 fprintf(stderr, "database error: %s\n", errmsg);
1344 if (conf->log->error == 1) {
1345 fprintf(stderr, "unable to allocate memory\n");
1347 fprintf(stderr, "zpm_exec failure: %s\n",
1348 conf->log->errmsg ? conf->log->errmsg : "unknown");
1352 if (conf->log->errmsg) {
1353 fprintf(stderr, "error: %s\n", conf->log->errmsg);
1356 if (conf->errors && conf->exitonerror) {
1357 zpm_close(conf->log);
1358 zpm_close(conf->src);
1361 /* TODO final report function in conf var */
1364 static void count_ops(struct config *conf) {
1365 conf->ops_remove = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'remove'");
1366 conf->ops_update = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'update'");
1367 conf->ops_install = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'new'");
1368 conf->ops_total = conf->ops_remove + conf->ops_update + conf->ops_install;
1371 int main(int ac, char **av) {
1375 char *pkgdbfile = 0, *localdbfile = 0;
1378 struct config conf = { 0 };
1395 if (geteuid() != 0) {
1400 localdbfile = ZPM_LOCAL_DB;
1401 if ((s = getenv("ZPMDB"))) {
1402 /* TODO does this need to be copied ? */
1406 if ((s = getenv("ZPM_ROOT_DIR"))) {
1407 /* TODO does this need to be copied ? */
1412 * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
1413 * -f 'package database', otherwise regular default of env
1414 * ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
1415 * -R root of pkg, will just chdir there
1417 * args are pkgid triple, but will do a pkg find on the pkgdb
1420 while ((opt = getopt(ac, av, "f:d:c:nCR:vOAMDp")) != -1) {
1422 case 'd': localdbfile = optarg; break;
1423 case 'f': pkgdbfile = optarg; break;
1424 case 'n': conf.dryrun = 1; break;
1425 case 'v': conf.verbose++; break;
1426 case 'C': conf.errabort = 0; break;
1427 case 'R': conf.rootdir = optarg; break;
1428 case 'N': conf.setuser = 0; conf.setgroup = 0; break;
1429 case 'O': conf.overwrite = 1; break;
1430 case 'A': conf.accept = 1; break;
1431 case 'M': conf.ignoredirmd = 1;
1432 case 'D': conf.acceptdir = 0;
1433 case 'p': conf.progress++;
1441 /* verify root dir exists */
1442 if (conf.rootdir && !exists(conf.rootdir, NULL)) {
1443 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
1446 if (!zpm_open(&localdb, localdbfile)) {
1447 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
1450 conf.log = &localdb;
1453 /* TODO open read-only */
1454 if (!zpm_open(&pkgdb, pkgdbfile)) {
1455 fprintf(stderr, "can't open src db %s\n", localdbfile);
1462 /* TODO find pkgid from arg */
1464 /* TODO set conf var to finalize error reporting */
1466 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
1467 conf.rootdir ? conf.rootdir : "/",
1468 localdbfile, pkgdbfile);
1472 conf.exitonerror = 0;
1473 check_conflicts(&conf, NULL, report_conflicts);
1475 if (conf.conflicts) {
1476 fprintf(stderr, "%d conflicts reported, aborting sync\n",
1480 /* no point in running it if we're just going to
1481 * overwrite everything
1483 if (!conf.overwrite && !conf.accept && !conf.dryrun) {
1484 runstage(&conf, "new", check_existing);
1488 fprintf(stderr, "beginning %ssync\n", conf.dryrun ?
1491 /* have to do the removes first otherwise
1492 * old files may conflict with update file
1496 conf.exitonerror = conf.dryrun ? 0 : 1;
1497 conf.errabort = conf.dryrun ? 0 : 1;
1499 fprintf(stderr, "file ops: %d\n", conf.ops_total);
1500 if (conf.ops_remove > 0) {
1502 fprintf(stderr, "removing %d file%s\n", conf.ops_remove, conf.ops_remove > 0 ? "s" : "");
1505 conf.ops_completed = 0;
1506 conf.ops_total = conf.ops_remove;
1507 runstage(&conf, "remove", remove_files);
1508 if (conf.verbose && conf.progress < 2) {
1509 fprintf(stderr, " done\n");
1514 if (conf.ops_update > 0) {
1516 fprintf(stderr, "updating %d file%s\n", conf.ops_update, conf.ops_update > 0 ? "s" : "");
1519 conf.ops_completed = 0;
1520 conf.ops_total = conf.ops_update;
1521 runstage(&conf, "update", install_files);
1522 if (conf.verbose && conf.progress < 2) {
1523 fprintf(stderr, " done\n");
1528 if (conf.ops_install > 0) {
1530 fprintf(stderr, "installing %d file%s\n", conf.ops_install, conf.ops_install > 0 ? "s" : "");
1533 conf.ops_completed = 0;
1534 conf.ops_total = conf.ops_install;
1535 runstage(&conf, "new", install_files);
1536 if (conf.verbose && conf.progress < 2) {
1537 fprintf(stderr, " done\n");
1544 zpm_close(&localdb);
1545 zpm_close(conf.src);
1546 return conf.errors ? 1 : 0;