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, absorb;
49 struct timespec times[2];
53 printf("usage: zpm $scriptname [-fncC] args ...\n");
56 static int seterror(struct config *conf, char *msgfmt, ...) {
63 vsnprintf(msg, sizeof msg, msgfmt, ap);
67 if (conf->log->errmsg) {
68 free(conf->log->errmsg);
71 conf->log->errmsg = strdup(msg);
74 fprintf(stderr, "%s\n", msg);
77 return conf->errabort;
80 static int setsyserr(struct config *conf, char *msgfmt, ...) {
88 printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
92 /* nothing we can really do */
93 return conf->errabort;
96 if ((size_t)printed < sizeof msg) {
97 snprintf(msg+printed, sizeof msg - printed, ": %s",
102 if (conf->log->errmsg) {
103 free(conf->log->errmsg);
106 conf->log->errmsg = strdup(msg);
109 fprintf(stderr, "%s\n", msg);
112 return conf->errabort;
115 static int exists(char *path, mode_t *mode) {
118 if (lstat(path, &st) == -1) {
121 if (mode) *mode = st.st_mode;
125 /* TODO maintain a list of already created directories */
126 static int create_leading_dirs(char *path) {
129 char pcopy[ZPM_PATH_MAX];
134 delim = strrchr(pcopy, '/');
135 if (!delim) return 1; /* not an error, but no leading dirs */
137 /* cut off last component */
146 delim = strchr(s, '/');
152 /* try to create the directory, if it exists
153 * and is a directory or a symlink, that's ok
154 * should be (eventually) a symlink to a directory
155 * so we want stat here, not lstat
157 if (mkdir(pcopy, 0755) == -1) {
160 if (stat(pcopy, &st) == -1) {
164 switch (st.st_mode & S_IFMT) {
184 static char *column(char *col, int ncols, char **vals, char **cols) {
188 for (i=0; i < ncols; i++) {
189 // fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
191 if (!strcmp(col, cols[i])) {
199 #define COL(x) column(x, ncols, vals, cols)
200 #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
203 static char *ops[] = { "new", "remove", "update", 0 };
211 static int getop(char *opstr) {
214 if (!opstr) return 0;
215 for (i=0;ops[i];i++) {
216 if (!strcmp(opstr, ops[i])) {
223 static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
224 struct config *conf = f;
225 char *path, *hash, *pkg, *conflict_type, *mds;
229 conflict_type = COL("conflict");
230 if (!strcmp(conflict_type, "hash")) {
232 fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
235 if (!strcmp(conflict_type, "md")) {
237 fprintf(stderr, "md conflict: package %s path %s md %s\n",
240 fprintf(stderr, "%s conflict: package %s path %s\n",
241 conflict_type, pkg, path);
248 static int check_existing(void *f, int ncols, char **vals, char **cols) {
249 struct config *conf = f;
255 return seterror(conf, "no path");
259 printf("checkfor %s\n", path);
265 fprintf(stderr, "check for existing %s\n", path);
268 if (lstat(path, &st) == 0) {
269 fprintf(stderr, "%s exists\n", path);
273 /* not an error, file shouldn't exist*/
276 fprintf(stderr, "unable to check %s: %s\n",
277 path, strerror(errno));
285 static int remove_files(void *f, int ncols, char **vals, char **cols) {
286 struct config *conf = f;
292 if (!dest) return seterror(conf,"no file dest");
295 char *ftype = COL("filetype");
299 case 'd': printf("rmdir %s\n", dest); break;
300 default: printf("unlink %s\n", dest); break;
306 if (lstat(dest, &st) == -1) {
307 return seterror(conf,"can't stat");
310 if (S_ISDIR(st.st_mode)) {
311 flags = AT_REMOVEDIR;
313 /* TODO check that expected filetype matches actual filetype */
316 fprintf(stderr, "%s(%s)\n", flags ? "rmdir" : "unlink", dest);
321 if (unlinkat(AT_FDCWD, dest, flags) == -1) {
326 return seterror(conf, "can't unlink");
333 #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
335 #define D_NOEXIST 0x1
340 #define D_EISDIR 0x20
344 #define D_MTIME 0x200
345 #define D_OHASH 0x400
346 #define D_ERROR 0x1000
347 #define D_STATERROR 0x2000
348 #define D_RLERROR 0x4000
350 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
351 /* 4 == ftype different */
352 /* 8 = hash different when both are regular files */
353 static unsigned int file_compare(struct nitem *n, struct stat *st) {
354 int etype = 0, stat_type;
355 char ehash[ZPM_HASH_STRLEN+1];
356 unsigned int diff = 0;
361 case 'd': etype = S_IFDIR; diff |= D_ISDIR ; break;
362 case 'r': etype = S_IFREG; break;
363 case 'l': etype = S_IFLNK; break;
364 default: etype = 0; break;
368 /* new file, so check type, hash, etc */
369 if (lstat(n->dest, st) == 0) {
370 stat_type = st->st_mode & S_IFMT;
371 if (stat_type != etype) {
374 if (stat_type == S_IFDIR) {
378 if (n->hash && etype == S_IFREG && stat_type == S_IFREG) {
379 zpm_hash(n->dest, ehash, 0);
380 if (strcmp(n->hash, ehash) != 0) {
383 if (n->ohash && strcmp(n->ohash, ehash) != 0) {
387 if (n->hash && etype == S_IFLNK && stat_type == S_IFLNK) {
388 lsize = readlink(n->dest, link, sizeof link);
389 if (lsize == -1 || lsize == sizeof link) {
392 } else if (strcmp(n->target, link) != 0) {
396 if (n->uid != st->st_uid) {
400 if (n->gid != st->st_gid) {
404 if (n->mode != (st->st_mode & 07777)) {
410 case ENOENT: diff |= D_NOEXIST; break;
411 default: diff |= (D_STATERROR|D_ERROR); break;
418 static int read_item(struct config *conf, int ncols, char **vals, char **cols,
423 struct nitem zero = { 0 };
429 seterror(conf, "can't determine op");
435 seterror(conf, "can't determine op");
439 n->path = COL("path");
441 seterror(conf, "no file path");
444 if (strlen(n->path) == 0) {
445 seterror(conf, "zero length path not allowed");
449 /* TODO config to dishonor setuid/setgid */
450 n->dest = COL("dest");
452 seterror(conf, "no file dest");
456 if (strlen(n->dest) == 0) {
457 seterror(conf, "zero length dest not allowed");
464 seterror(conf, "can't determine mode");
468 n->mode = strtoul(val, NULL, 8);
470 val = COL("configuration");
472 seterror(conf, "can't determine config status");
475 n->configuration = strtoul(val, NULL, 10);
477 val = COL("filetype");
478 if (!val || strlen(val) == 0) {
479 seterror(conf, "can't determine file type");
484 n->ohash = COL("hash");
486 if (n->ftype == 'r') {
487 n->hash = COL("hash");
489 seterror(conf, "can't get hash");
492 } else if (n->ftype == 'l') {
493 n->target = COL("target");
495 seterror(conf, "can't get target");
498 if (strlen(n->target) == 0) {
499 seterror(conf, "zero length target not allowed");
506 val = COL("username");
508 seterror(conf, "no username");
513 seterror(conf, "no passwd entry");
521 if (conf->setgroup) {
522 val = COL("groupname");
524 seterror(conf, "no groupname");
529 seterror(conf, "no group entry");
538 double mtime = strtod(COL("mtime"),NULL);
540 mtime = (double)time(NULL);
543 n->mtime = (time_t)mtime;
545 n->times[0].tv_sec = 0;
546 n->times[0].tv_nsec = UTIME_OMIT;
547 n->times[1].tv_sec = (time_t)llrint(floor(mtime));
548 n->times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
553 static int remove_dir(struct config *conf, char *path) {
558 setsyserr(conf, "can't rmdir %s", path);
564 static int remove_existing(struct config *conf, char *path) {
569 setsyserr(conf, "can't unlink %s", path);
575 static int set_md(struct config *conf, struct nitem *item) {
580 printf("chmod %o %s\n", item->mode, item->dest);
581 if (conf->setuser && conf->setgroup) {
582 printf("chown %d:%d %s\n", item->uid, item->gid,
585 printf("mtime %.0f %s\n", (double)item->mtime, item->dest);
590 rv = chmod(item->dest, item->mode);
593 setsyserr(conf, "can't chmod %o %s", item->mode, item->dest);
594 return conf->errabort;
597 if (conf->setuser && conf->setgroup) {
598 rv = chown(item->dest, item->uid, item->gid);
600 setsyserr(conf, "can't chown %s", item->dest);
601 return conf->errabort;
605 rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW);
607 setsyserr(conf, "can't set mtime %.0f %s", (double)item->mtime,
609 return conf->errabort;
614 /* install a file or create a directory or symlink. path should not exist
617 /* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file,
618 * 8 = rmdir existing dir, 16 = return true/false
622 #define INS_UNLINK 0x4
623 #define INS_RMDIR 0x8
625 #define INS_ZPMNEW 0x20
626 static int install(struct config *conf, struct nitem *item, unsigned int flags) {
630 int mkleading = (flags & 2);
631 int setmd = (flags & 1);
632 int unlink_file = (flags & 4);
633 int rm_dir = (flags & 8);
634 int failure = conf->errabort;
644 printf("unlink %s\n", item->dest);
646 printf("rmdir %s\n", item->dest);
649 printf("install %c%o %d:%d %s -> %s\n", item->ftype,
650 item->mode, item->uid, item->gid, item->path,
656 source = conf->src ? conf->src : conf->log;
659 rv = remove_existing(conf, item->dest);
661 rv = remove_dir(conf, item->dest);
669 rv = create_leading_dirs(item->dest);
671 setsyserr(conf, "can't create leading dirs for %s", item->dest);
676 if (item->ftype == 'r') {
677 rv = zpm_extract(source, item->hash, item->dest, item->mode);
679 seterror(conf, "can't extract %s", item->dest);
685 switch (item->ftype) {
686 case 'd': rv = mkdir(item->dest, item->mode);
688 case 'l': rv = symlink(item->target, item->dest);
695 setsyserr(conf, "installing %s failed", item->dest);
700 return set_md(conf, item) == 0 ? success : failure;
706 static int install_files(void *f, int ncols, char **vals, char **cols) {
707 struct config *conf = f;
709 struct stat existing;
713 /* TODO put the result row in a hash table. May not actually
716 if (!read_item(conf, ncols, vals, cols, &nitem)) {
717 fprintf(stderr, "can't read item\n");
718 return conf->errabort;
721 if (conf->verbose && !conf->dryrun) {
722 fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype,
726 unsigned int diffs = file_compare(&nitem, &existing);
727 if (diffs >= D_ERROR) {
728 return seterror(conf, "can't check %s", nitem.dest);
732 * exist & same type & md same & hash same: do nothing, but warn bug
733 * exist & same type & md diff & hash same: fix md
734 * exist & same type & md same & hash diff: replace
735 * exist & same type & md diff & hash diff: replace & fix
736 * no exist: install and warn
737 * dir & not dir : remove, mkdir
738 * not dir & not dir & diff type: remove, install
739 * not dir & dir : remove dir if empty, error if not empty, install
742 * no exist: create leading dirs, install
744 * exist & same type & md same & hash same & accept or over: do nothing
745 * exist & same & md diff or hash diff & overwrite : update
746 * exist & same & md diff or hash diff & accept : error, can't accept
747 * exist & same & md diff or hash diff & not accept : error
749 * exist & different type & not overwrite : error
750 * not dir & not dir & overwrite : remove and install
751 * not dir & dir & overwrite: remove empty or error, install
752 * dir & dir & overwrite: fix md
753 * dir & not dir & overwrite: remove and mkdir
755 int exist = (!(diffs & D_NOEXIST));
756 int sametype = (!(diffs & D_TYPE));
757 int mdsame = (!(diffs & D_MD));
758 int hashsame = (!(diffs & D_HASH));
759 int isdir = (diffs & D_ISDIR);
760 int eisdir = (diffs & D_EISDIR);
761 int accept = conf->absorb;
762 int overwrite = conf->overwrite;
763 int installing = (nitem.op == OP_NEW);
764 update = (nitem.op == OP_UPDATE);
768 /* warn, it should exist */
769 fprintf(stderr, "%s missing, installing", nitem.dest);
770 return install(conf, &nitem, 3);
773 if (nitem.configuration) {
774 /* ohash == nhash, not an update */
775 /* fhash == ohash, just update */
776 /* fhash != ohash, install as dest.zpmnew, warn */
777 /* TODO handle replacing config file
778 * with config directory */
779 if (diffs & D_OHASH) {
780 if (strlen(nitem.dest) > sizeof dest - 8) {
781 return seterror(conf,"config file path too long for install as %s.zpmnew", nitem.dest);
783 fprintf(stderr, "installing as .zpmnew\n");
784 sprintf(dest, "%s.zpmnew", nitem.dest);
789 /* file exists in filesystem */
791 if (mdsame && hashsame) {
792 fprintf(stderr, "%s should not be an update", nitem.dest);
793 /* warn, bug in logic. This shouldn't
794 * occur, because if there is nothing
795 * to do, it shouldn't be listed
801 if (!mdsame && hashsame) {
803 return set_md(conf, &nitem);
805 if (mdsame && !hashsame) {
807 return install(conf, &nitem, 3);
809 if (!mdsame && !hashsame) {
811 return install(conf, &nitem, 3);
815 /* file exists, and is not the same type */
817 if (isdir && !eisdir) {
818 /* remove existing */
820 return install(conf, &nitem, 7);
822 if (!isdir && eisdir) {
823 /* remove dir, or error */
825 return install(conf, &nitem, 11);
827 if (!isdir && !isdir) {
828 /* necessarily !sametype, sametype handled above */
829 /* remove existing */
831 return install(conf, &nitem, 7);
833 /* error, should not be possible, assert(0)? */
838 return install(conf, &nitem, 3);
841 /* file exists in filesystem */
843 if (mdsame && hashsame && (accept || overwrite)) {
845 if (conf->dryrun || conf->verbose) {
846 fprintf(stderr, "accepting existing file: %s\n", nitem.dest);
850 if (mdsame && hashsame && !(accept || overwrite)) {
852 return seterror(conf, "will not accept or overwrite existing file: %s", nitem.dest);
854 if (mdsame && !hashsame && overwrite) {
856 return install(conf, &nitem, eisdir ? 11 : 7);
858 if (mdsame && !hashsame && !overwrite) {
859 /* accept doesn't matter, since it's
860 * not an acceptable file */
862 return seterror(conf, "%s (hashdiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
864 if (!mdsame && hashsame && overwrite) {
866 return set_md(conf, &nitem);
868 if (!mdsame && hashsame && !overwrite) {
869 /* accept doesn't matter, since it's
870 * not an acceptable file */
872 return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
874 if (!mdsame && !hashsame && overwrite) {
876 return install(conf, &nitem, eisdir ? 11 : 7);
878 if (!mdsame && !hashsame && !overwrite) {
879 /* accept doesn't matter, since it's
880 * not an acceptable file */
882 return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
884 /* TODO error, should be impossible */
885 return seterror(conf, "impossible state reached");
888 /* file exists, and is not the same type */
891 return seterror(conf, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
894 /* not the same type, but ok to overwrite */
896 /* remove existing */
897 return install(conf, &nitem, 7);
900 /* existing path is a directory */
903 /* impossible, if isdir and eisdir, would
907 return set_md(conf, &nitem);
909 /* remove empty dir or error */
911 return install(conf, &nitem, 11);
913 /* if we get here, we missed a case */
915 return seterror(conf, "impossible state 2 reached");
918 /* TODO extra verbose print perms, mtime, etc, probably ls -l
922 printf("%s\n", nitem.path);
928 static void check_conflicts(struct config *conf, char *conflict_type,
929 int (callback)(void *, int, char **, char **)) {
935 s = sqlite3_str_new(conf->log->db);
936 sqlite3_str_appendall(s, "select *, ");
938 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
940 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
942 sqlite3_str_appendall(s, " as dest from syncconflicts");
945 sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
948 sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
950 sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
954 sql = sqlite3_str_value(s);
956 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
958 sqlite3_str_finish(s);
961 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
963 fprintf(stderr, "database error: %s\n", errmsg);
966 if (conf->log->error == 1) {
967 fprintf(stderr, "unable to allocate memory\n");
969 fprintf(stderr, "zpm_exec failure: %s\n",
970 conf->log->errmsg ? conf->log->errmsg : "unknown");
973 if (conf->log->errmsg) {
974 fprintf(stderr, "error: %s\n", conf->log->errmsg);
976 if (conf->errors && conf->exitonerror) {
977 zpm_close(conf->log);
978 zpm_close(conf->src);
981 /* TODO final report function in conf var */
984 static void runstage(struct config *conf, char *stage,
985 int (callback)(void *, int, char **, char **)) {
991 s = sqlite3_str_new(conf->log->db);
992 sqlite3_str_appendall(s, "select *, ");
994 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
996 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
998 sqlite3_str_appendall(s, " as dest from syncinfo");
1001 sqlite3_str_appendf(s," where op = %Q", stage);
1003 if (conf->reverse) {
1004 sqlite3_str_appendall(s," order by length(path) desc, path desc");
1007 sql = sqlite3_str_value(s);
1009 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
1011 sqlite3_str_finish(s);
1014 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
1016 fprintf(stderr, "database error: %s\n", errmsg);
1019 if (conf->log->error == 1) {
1020 fprintf(stderr, "unable to allocate memory\n");
1022 fprintf(stderr, "zpm_exec failure: %s\n",
1023 conf->log->errmsg ? conf->log->errmsg : "unknown");
1027 if (conf->log->errmsg) {
1028 fprintf(stderr, "error: %s\n", conf->log->errmsg);
1031 if (conf->errors && conf->exitonerror) {
1032 zpm_close(conf->log);
1033 zpm_close(conf->src);
1036 /* TODO final report function in conf var */
1039 int main(int ac, char **av){
1043 char *pkgdbfile = 0, *localdbfile = 0;
1062 if (geteuid() != 0) {
1067 localdbfile = ZPM_LOCAL_DB;
1068 if ((s = getenv("ZPMDB"))) {
1069 /* TODO does this need to be copied ? */
1073 if ((s = getenv("ZPM_ROOT_DIR"))) {
1074 /* TODO does this need to be copied ? */
1079 * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
1080 * -f 'package database', otherwise regular default of env
1081 * ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
1082 * -R root of pkg, will just chdir there
1084 * args are pkgid triple, but will do a pkg find on the pkgdb
1087 while ((opt = getopt(ac, av, "f:d:c:nCR:vOA")) != -1) {
1089 case 'd': localdbfile = optarg; break;
1090 case 'f': pkgdbfile = optarg; break;
1091 case 'n': conf.dryrun = 1; break;
1092 case 'v': conf.verbose++; break;
1093 case 'C': conf.errabort = 0; break;
1094 case 'R': conf.rootdir = optarg; break;
1095 case 'N': conf.setuser = 0; conf.setgroup = 0; break;
1096 case 'O': conf.overwrite = 1; break;
1097 case 'A': conf.absorb = 1; break;
1105 /* verify root dir exists */
1106 if (conf.rootdir && !exists(conf.rootdir, NULL)) {
1107 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
1110 if (!zpm_open(&localdb, localdbfile)) {
1111 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
1114 conf.log = &localdb;
1117 if (!zpm_open(&pkgdb, pkgdbfile)) {
1118 fprintf(stderr, "can't open src db %s\n", localdbfile);
1125 /* TODO find pkgid from arg */
1127 /* TODO set conf var to finalize error reporting */
1129 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
1130 conf.rootdir ? conf.rootdir : "/",
1131 localdbfile, pkgdbfile);
1135 conf.exitonerror = 0;
1136 check_conflicts(&conf, NULL, report_conflicts);
1138 if (conf.conflicts) {
1139 fprintf(stderr, "%d conflicts reported, aborting sync\n",
1143 /* no point in running it if we're just going to
1144 * overwrite everything
1146 if (!conf.overwrite && !conf.absorb && !conf.dryrun) {
1147 runstage(&conf, "new", check_existing);
1151 fprintf(stderr, "beginning %ssync\n", conf.dryrun ?
1154 /* have to do the removes first otherwise
1155 * old files may conflict with update file
1159 conf.exitonerror = conf.dryrun ? 0 : 1;
1160 conf.errabort = conf.dryrun ? 0 : 1;
1163 fprintf(stderr, "removing old files\n");
1165 runstage(&conf, "remove", remove_files);
1168 fprintf(stderr, "updating files\n");
1170 runstage(&conf, "update", install_files);
1172 fprintf(stderr, "installing files\n");
1174 runstage(&conf, "new", install_files);
1178 zpm_close(&localdb);
1179 zpm_close(conf.src);
1180 return conf.errors ? 1 : 0;