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;
48 struct timespec times[2];
52 printf("usage: zpm $scriptname [-fncC] args ...\n");
55 static int seterror(struct config *conf, char *msgfmt, ...) {
62 vsnprintf(msg, sizeof msg, msgfmt, ap);
66 if (conf->log->errmsg) {
67 free(conf->log->errmsg);
70 conf->log->errmsg = strdup(msg);
73 fprintf(stderr, "%s\n", msg);
76 return conf->errabort;
79 static int setsyserr(struct config *conf, char *msgfmt, ...) {
87 printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
91 /* nothing we can really do */
92 return conf->errabort;
95 if ((size_t)printed < sizeof msg) {
96 snprintf(msg+printed, sizeof msg - printed, ": %s",
101 if (conf->log->errmsg) {
102 free(conf->log->errmsg);
105 conf->log->errmsg = strdup(msg);
108 fprintf(stderr, "%s\n", msg);
111 return conf->errabort;
114 static int exists(char *path, mode_t *mode) {
117 if (lstat(path, &st) == -1) {
120 if (mode) *mode = st.st_mode;
124 /* TODO maintain a list of already created directories */
125 static int create_leading_dirs(char *path) {
128 char pcopy[ZPM_PATH_MAX];
133 delim = strrchr(pcopy, '/');
134 if (!delim) return 1; /* not an error, but no leading dirs */
136 /* cut off last component */
145 delim = strchr(s, '/');
151 /* try to create the directory, if it exists
152 * and is a directory or a symlink, that's ok
153 * should be (eventually) a symlink to a directory
154 * so we want stat here, not lstat
156 if (mkdir(pcopy, 0755) == -1) {
159 if (stat(pcopy, &st) == -1) {
163 switch (st.st_mode & S_IFMT) {
183 static char *column(char *col, int ncols, char **vals, char **cols) {
187 for (i=0; i < ncols; i++) {
188 // fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
190 if (!strcmp(col, cols[i])) {
198 #define COL(x) column(x, ncols, vals, cols)
199 #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
202 static char *ops[] = { "new", "remove", "update", 0 };
210 static int getop(char *opstr) {
213 if (!opstr) return 0;
214 for (i=0;ops[i];i++) {
215 if (!strcmp(opstr, ops[i])) {
222 static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
223 struct config *conf = f;
224 char *path, *hash, *pkg, *conflict_type, *mds;
228 conflict_type = COL("conflict");
229 if (!strcmp(conflict_type, "hash")) {
231 fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
234 if (!strcmp(conflict_type, "md")) {
236 fprintf(stderr, "md conflict: package %s path %s md %s\n",
239 fprintf(stderr, "%s conflict: package %s path %s\n",
240 conflict_type, pkg, path);
247 static int check_existing(void *f, int ncols, char **vals, char **cols) {
248 struct config *conf = f;
254 return seterror(conf, "no path");
258 printf("checkfor %s\n", path);
264 fprintf(stderr, "check for existing %s\n", path);
267 if (lstat(path, &st) == 0) {
268 fprintf(stderr, "%s exists\n", path);
272 /* not an error, file shouldn't exist*/
275 fprintf(stderr, "unable to check %s: %s\n",
276 path, strerror(errno));
284 static int remove_files(void *f, int ncols, char **vals, char **cols) {
285 struct config *conf = f;
290 if (!dest) return seterror(conf,"no file dest");
293 char *ftype = COL("filetype");
297 case 'd': printf("rmdir %s\n", dest); break;
298 default: printf("unlink %s\n", dest); break;
304 if (lstat(dest, &st) == -1) {
305 return seterror(conf,"can't stat");
308 if (S_ISDIR(st.st_mode)) {
310 fprintf(stderr, "rmdir(%s)\n", dest);
313 } else if (S_ISREG(st.st_mode)) {
314 /* TODO conf to import before removal */
316 fprintf(stderr, "unlink(%s)\n", dest);
318 if (unlink(dest) == -1) {
319 return seterror(conf, "can't unlink");
322 if (unlink(dest) == -1) {
323 return seterror(conf, "can't unlink");
330 #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
332 #define D_NOEXIST 0x1
337 #define D_EISDIR 0x20
341 #define D_MTIME 0x200
342 #define D_ERROR 0x1000
343 #define D_STATERROR 0x2000
344 #define D_RLERROR 0x4000
346 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
347 /* 4 == ftype different */
348 /* 8 = hash different when both are regular files */
349 static unsigned int file_compare(struct nitem *n, struct stat *st) {
350 int etype = 0, stat_type;
351 char ehash[ZPM_HASH_STRLEN+1];
352 unsigned int diff = 0;
357 case 'd': etype = S_IFDIR; diff |= D_ISDIR ; break;
358 case 'r': etype = S_IFREG; break;
359 case 'l': etype = S_IFLNK; break;
360 default: etype = 0; break;
364 /* new file, so check type, hash, etc */
365 if (lstat(n->dest, st) == 0) {
366 stat_type = st->st_mode & S_IFMT;
367 if (stat_type != etype) {
370 if (stat_type == S_IFDIR) {
374 if (n->hash && etype == S_IFREG && stat_type == S_IFREG) {
375 zpm_hash(n->dest, ehash, 0);
376 if (strcmp(n->hash, ehash) != 0) {
380 if (n->hash && etype == S_IFLNK && stat_type == S_IFLNK) {
381 lsize = readlink(n->dest, link, sizeof link);
382 if (lsize == -1 || lsize == sizeof link) {
385 } else if (strcmp(n->target, link) != 0) {
389 if (n->uid != st->st_uid) {
393 if (n->gid != st->st_gid) {
397 if (n->mode != (st->st_mode & 07777)) {
403 case ENOENT: diff |= D_NOEXIST; break;
404 default: diff |= (D_STATERROR|D_ERROR); break;
411 static int read_item(struct config *conf, int ncols, char **vals, char **cols,
416 struct nitem zero = { 0 };
422 seterror(conf, "can't determine op");
428 seterror(conf, "can't determine op");
432 n->path = COL("path");
434 seterror(conf, "no file path");
437 if (strlen(n->path) == 0) {
438 seterror(conf, "zero length path not allowed");
442 /* TODO config to dishonor setuid/setgid */
443 n->dest = COL("dest");
445 seterror(conf, "no file dest");
449 if (strlen(n->dest) == 0) {
450 seterror(conf, "zero length dest not allowed");
457 seterror(conf, "can't determine mode");
461 n->mode = strtoul(val, NULL, 8);
463 val = COL("filetype");
464 if (!val || strlen(val) == 0) {
465 seterror(conf, "can't determine file type");
470 if (n->ftype == 'r') {
471 n->hash = COL("hash");
473 seterror(conf, "can't get hash");
476 } else if (n->ftype == 'l') {
477 n->target = COL("target");
479 seterror(conf, "can't get target");
482 if (strlen(n->target) == 0) {
483 seterror(conf, "zero length target not allowed");
490 val = COL("username");
492 seterror(conf, "no username");
497 seterror(conf, "no passwd entry");
505 if (conf->setgroup) {
506 val = COL("groupname");
508 seterror(conf, "no groupname");
513 seterror(conf, "no group entry");
522 double mtime = strtod(COL("mtime"),NULL);
524 mtime = (double)time(NULL);
527 n->mtime = (time_t)mtime;
529 n->times[0].tv_sec = 0;
530 n->times[0].tv_nsec = UTIME_OMIT;
531 n->times[1].tv_sec = (time_t)llrint(floor(mtime));
532 n->times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
537 static int remove_dir(struct config *conf, char *path) {
542 setsyserr(conf, "can't rmdir %s", path);
548 static int remove_existing(struct config *conf, char *path) {
553 setsyserr(conf, "can't unlink %s", path);
559 static int set_md(struct config *conf, struct nitem *item) {
564 printf("chmod %o %s\n", item->mode, item->dest);
565 if (conf->setuser && conf->setgroup) {
566 printf("chown %d:%d %s\n", item->uid, item->gid,
569 printf("mtime %.0f %s\n", (double)item->mtime, item->dest);
574 rv = chmod(item->dest, item->mode);
577 setsyserr(conf, "can't chmod %o %s", item->mode, item->dest);
578 return conf->errabort;
581 if (conf->setuser && conf->setgroup) {
582 rv = chown(item->dest, item->uid, item->gid);
584 setsyserr(conf, "can't chown %s", item->dest);
585 return conf->errabort;
589 rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW);
591 setsyserr(conf, "can't set mtime %.0f %s", (double)item->mtime,
593 return conf->errabort;
598 /* install a file or create a directory or symlink. path should not exist
601 /* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file,
602 * 8 = rmdir existing dir, 16 = return true/false
604 static int install(struct config *conf, struct nitem *item, unsigned int flags) {
608 int mkleading = (flags & 2);
609 int setmd = (flags & 1);
610 int unlink_file = (flags & 4);
611 int rm_dir = (flags & 8);
612 int failure = conf->errabort;
622 printf("unlink %s\n", item->dest);
624 printf("rmdir %s\n", item->dest);
627 printf("install %c%o %d:%d %s -> %s\n", item->ftype,
628 item->mode, item->uid, item->gid, item->path,
634 source = conf->src ? conf->src : conf->log;
637 rv = remove_existing(conf, item->dest);
639 rv = remove_dir(conf, item->dest);
647 rv = create_leading_dirs(item->dest);
649 setsyserr(conf, "can't create leading dirs for %s", item->dest);
654 if (item->ftype == 'r') {
655 rv = zpm_extract(source, item->hash, item->dest, item->mode);
657 seterror(conf, "can't extract %s", item->dest);
663 switch (item->ftype) {
664 case 'd': rv = mkdir(item->dest, item->mode);
666 case 'l': rv = symlink(item->target, item->dest);
673 setsyserr(conf, "installing %s failed", item->dest);
678 return set_md(conf, item) == 0 ? success : failure;
684 static int install_files(void *f, int ncols, char **vals, char **cols) {
685 struct config *conf = f;
687 struct stat existing;
690 /* TODO put the result row in a hash table. May not actually
693 if (!read_item(conf, ncols, vals, cols, &nitem)) {
694 fprintf(stderr, "can't read item\n");
695 return conf->errabort;
698 if (conf->verbose && !conf->dryrun) {
699 fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype,
703 unsigned int diffs = file_compare(&nitem, &existing);
704 if (diffs >= D_ERROR) {
705 return seterror(conf, "can't check %s", nitem.dest);
709 * exist & same type & md same & hash same: do nothing, but warn bug
710 * exist & same type & md diff & hash same: fix md
711 * exist & same type & md same & hash diff: replace
712 * exist & same type & md diff & hash diff: replace & fix
713 * no exist: install and warn
714 * dir & not dir : remove, mkdir
715 * not dir & not dir & diff type: remove, install
716 * not dir & dir : remove dir if empty, error if not empty, install
719 * no exist: create leading dirs, install
721 * exist & same type & md same & hash same & accept or over: do nothing
722 * exist & same & md diff or hash diff & overwrite : update
723 * exist & same & md diff or hash diff & accept : error, can't accept
724 * exist & same & md diff or hash diff & not accept : error
726 * exist & different type & not overwrite : error
727 * not dir & not dir & overwrite : remove and install
728 * not dir & dir & overwrite: remove empty or error, install
729 * dir & dir & overwrite: fix md
730 * dir & not dir & overwrite: remove and mkdir
732 int exist = (!(diffs & D_NOEXIST));
733 int sametype = (!(diffs & D_TYPE));
734 int mdsame = (!(diffs & D_MD));
735 int hashsame = (!(diffs & D_HASH));
736 int isdir = (diffs & D_ISDIR);
737 int eisdir = (diffs & D_EISDIR);
738 int accept = conf->absorb;
739 int overwrite = conf->overwrite;
740 int installing = (nitem.op == OP_NEW);
741 update = (nitem.op == OP_UPDATE);
745 /* warn, it should exist */
746 fprintf(stderr, "%s missing, installing", nitem.dest);
747 return install(conf, &nitem, 3);
750 /* file exists in filesystem */
752 if (mdsame && hashsame) {
753 fprintf(stderr, "%s should not be an update", nitem.dest);
754 /* warn, bug in logic. This shouldn't
755 * occur, because if there is nothing
756 * to do, it shouldn't be listed
762 if (!mdsame && hashsame) {
764 return set_md(conf, &nitem);
766 if (mdsame && !hashsame) {
768 return install(conf, &nitem, 3);
770 if (!mdsame && !hashsame) {
772 return install(conf, &nitem, 3);
776 /* file exists, and is not the same type */
778 if (isdir && !eisdir) {
779 /* remove existing */
781 return install(conf, &nitem, 7);
783 if (!isdir && eisdir) {
784 /* remove dir, or error */
786 return install(conf, &nitem, 11);
788 if (!isdir && !isdir) {
789 /* necessarily !sametype, sametype handled above */
790 /* remove existing */
792 return install(conf, &nitem, 7);
794 /* error, should not be possible, assert(0)? */
799 return install(conf, &nitem, 3);
802 /* file exists in filesystem */
804 if (mdsame && hashsame && (accept || overwrite)) {
806 if (conf->dryrun || conf->verbose) {
807 fprintf(stderr, "accepting existing file: %s\n", nitem.dest);
811 if (mdsame && hashsame && !(accept || overwrite)) {
813 return seterror(conf, "will not accept or overwrite existing file: %s", nitem.dest);
815 if (mdsame && !hashsame && overwrite) {
817 return install(conf, &nitem, eisdir ? 11 : 7);
819 if (mdsame && !hashsame && !overwrite) {
820 /* accept doesn't matter, since it's
821 * not an acceptable file */
823 return seterror(conf, "%s (hashdiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
825 if (!mdsame && hashsame && overwrite) {
827 return set_md(conf, &nitem);
829 if (!mdsame && hashsame && !overwrite) {
830 /* accept doesn't matter, since it's
831 * not an acceptable file */
833 return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
835 if (!mdsame && !hashsame && overwrite) {
837 return install(conf, &nitem, eisdir ? 11 : 7);
839 if (!mdsame && !hashsame && !overwrite) {
840 /* accept doesn't matter, since it's
841 * not an acceptable file */
843 return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
845 /* TODO error, should be impossible */
846 return seterror(conf, "impossible state reached");
849 /* file exists, and is not the same type */
852 return seterror(conf, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
855 /* not the same type, but ok to overwrite */
857 /* remove existing */
858 return install(conf, &nitem, 7);
861 /* existing path is a directory */
864 /* impossible, if isdir and eisdir, would
868 return set_md(conf, &nitem);
870 /* remove empty dir or error */
872 return install(conf, &nitem, 11);
874 /* if we get here, we missed a case */
876 return seterror(conf, "impossible state 2 reached");
879 /* TODO extra verbose print perms, mtime, etc, probably ls -l
883 printf("%s\n", nitem.path);
889 static void check_conflicts(struct config *conf, char *conflict_type,
890 int (callback)(void *, int, char **, char **)) {
896 s = sqlite3_str_new(conf->log->db);
897 sqlite3_str_appendall(s, "select *, ");
899 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
901 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
903 sqlite3_str_appendall(s, " as dest from syncconflicts");
906 sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
909 sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
911 sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
915 sql = sqlite3_str_value(s);
917 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
919 sqlite3_str_finish(s);
922 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
924 fprintf(stderr, "database error: %s\n", errmsg);
927 if (conf->log->error == 1) {
928 fprintf(stderr, "unable to allocate memory\n");
930 fprintf(stderr, "zpm_exec failure: %s\n",
931 conf->log->errmsg ? conf->log->errmsg : "unknown");
934 if (conf->log->errmsg) {
935 fprintf(stderr, "error: %s\n", conf->log->errmsg);
937 if (conf->errors && conf->exitonerror) {
938 zpm_close(conf->log);
939 zpm_close(conf->src);
942 /* TODO final report function in conf var */
945 static void runstage(struct config *conf, char *stage,
946 int (callback)(void *, int, char **, char **)) {
952 s = sqlite3_str_new(conf->log->db);
953 sqlite3_str_appendall(s, "select *, ");
955 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
957 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
959 sqlite3_str_appendall(s, " as dest from syncinfo");
962 sqlite3_str_appendf(s," where op = %Q", stage);
965 sqlite3_str_appendall(s," order by length(path) desc, path desc");
968 sql = sqlite3_str_value(s);
970 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
972 sqlite3_str_finish(s);
975 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
977 fprintf(stderr, "database error: %s\n", errmsg);
980 if (conf->log->error == 1) {
981 fprintf(stderr, "unable to allocate memory\n");
983 fprintf(stderr, "zpm_exec failure: %s\n",
984 conf->log->errmsg ? conf->log->errmsg : "unknown");
988 if (conf->log->errmsg) {
989 fprintf(stderr, "error: %s\n", conf->log->errmsg);
992 if (conf->errors && conf->exitonerror) {
993 zpm_close(conf->log);
994 zpm_close(conf->src);
997 /* TODO final report function in conf var */
1000 int main(int ac, char **av){
1004 char *pkgdbfile = 0, *localdbfile = 0;
1023 if (geteuid() != 0) {
1028 localdbfile = ZPM_LOCAL_DB;
1029 if ((s = getenv("ZPMDB"))) {
1030 /* TODO does this need to be copied ? */
1034 if ((s = getenv("ZPM_ROOT_DIR"))) {
1035 /* TODO does this need to be copied ? */
1040 * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
1041 * -f 'package database', otherwise regular default of env
1042 * ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
1043 * -R root of pkg, will just chdir there
1045 * args are pkgid triple, but will do a pkg find on the pkgdb
1048 while ((opt = getopt(ac, av, "f:d:c:nCR:vOA")) != -1) {
1050 case 'd': localdbfile = optarg; break;
1051 case 'f': pkgdbfile = optarg; break;
1052 case 'n': conf.dryrun = 1; break;
1053 case 'v': conf.verbose++; break;
1054 case 'C': conf.errabort = 0; break;
1055 case 'R': conf.rootdir = optarg; break;
1056 case 'N': conf.setuser = 0; conf.setgroup = 0; break;
1057 case 'O': conf.overwrite = 1; break;
1058 case 'A': conf.absorb = 1; break;
1066 /* verify root dir exists */
1067 if (conf.rootdir && !exists(conf.rootdir, NULL)) {
1068 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
1071 if (!zpm_open(&localdb, localdbfile)) {
1072 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
1075 conf.log = &localdb;
1078 if (!zpm_open(&pkgdb, pkgdbfile)) {
1079 fprintf(stderr, "can't open src db %s\n", localdbfile);
1086 /* TODO find pkgid from arg */
1088 /* TODO set conf var to finalize error reporting */
1090 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
1091 conf.rootdir ? conf.rootdir : "/",
1092 localdbfile, pkgdbfile);
1096 conf.exitonerror = 0;
1097 check_conflicts(&conf, NULL, report_conflicts);
1099 if (conf.conflicts) {
1100 fprintf(stderr, "%d conflicts reported, aborting sync\n",
1104 /* no point in running it if we're just going to
1105 * overwrite everything
1107 if (!conf.overwrite && !conf.absorb && !conf.dryrun) {
1108 runstage(&conf, "new", check_existing);
1112 fprintf(stderr, "beginning %ssync\n", conf.dryrun ?
1115 /* have to do the removes first otherwise
1116 * old files may conflict with update file
1120 conf.exitonerror = conf.dryrun ? 0 : 1;
1121 conf.errabort = conf.dryrun ? 0 : 1;
1124 fprintf(stderr, "removing old files\n");
1126 runstage(&conf, "remove", remove_files);
1129 fprintf(stderr, "updating files\n");
1131 runstage(&conf, "update", install_files);
1133 fprintf(stderr, "installing files\n");
1135 runstage(&conf, "new", install_files);
1139 zpm_close(&localdb);
1140 zpm_close(conf.src);
1141 return conf.errors ? 1 : 0;