1 #define _POSIX_C_SOURCE 200809L
16 /* needed for S_IFMT and AT_FDCWD */
25 struct zpm *log; /* logging db will be attached as "log" */
29 int errabort, errors, verbose, dryrun, conflicts;
30 int setuser, setgroup;
31 int reverse, exitonerror;
32 int overwrite, absorb;
36 printf("usage: zpm $scriptname [-fncC] args ...\n");
39 static int exists(char *path, mode_t *mode) {
42 if (lstat(path, &st) == -1) {
45 if (mode) *mode = st.st_mode;
49 /* TODO maintain a list of already created directories */
50 static int create_leading_dirs(char *path) {
53 char pcopy[ZPM_PATH_MAX];
58 delim = strrchr(pcopy, '/');
59 if (!delim) return 1; /* not an error, but no leading dirs */
61 /* cut off last component */
70 delim = strchr(s, '/');
76 /* try to create the directory, if it exists
77 * and is a directory or a symlink, that's ok
79 if (mkdir(pcopy, 0755) == -1) {
82 if (lstat(pcopy, &st) == -1) {
86 switch (st.st_mode & S_IFMT) {
107 char *column(char *col, int ncols, char **vals, char **cols) {
111 for (i=0; i < ncols; i++) {
112 // fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
114 if (!strcmp(col, cols[i])) {
122 #define COL(x) column(x, ncols, vals, cols)
123 #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
125 static int seterror(struct config *conf, char *msgfmt, ...) {
131 va_start(ap, msgfmt);
132 vsnprintf(msg, sizeof msg, msgfmt, ap);
136 if (conf->log->errmsg) {
137 free(conf->log->errmsg);
140 conf->log->errmsg = strdup(msg);
143 fprintf(stderr, "%s\n", msg);
146 return conf->errabort;
149 static int setsyserr(struct config *conf, char *msgfmt, ...) {
156 va_start(ap, msgfmt);
157 printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
161 /* nothing we can really do */
162 return conf->errabort;
165 if ((size_t)printed < sizeof msg) {
166 snprintf(msg+printed, sizeof msg - printed, ": %s",
171 if (conf->log->errmsg) {
172 free(conf->log->errmsg);
175 conf->log->errmsg = strdup(msg);
178 fprintf(stderr, "%s\n", msg);
181 return conf->errabort;
184 #define IERR(x) return seterror(conf, x)
186 static char *ops[] = { "new", "remove", "update", 0 };
188 static int getop(char *opstr) {
191 if (!opstr) return 0;
192 for (i=0;ops[i];i++) {
193 if (!strcmp(opstr, ops[i])) {
200 static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
201 struct config *conf = f;
202 char *path, *hash, *pkg, *conflict_type, *mds;
206 conflict_type = COL("conflict");
207 if (!strcmp(conflict_type, "hash")) {
209 fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
212 if (!strcmp(conflict_type, "md")) {
214 fprintf(stderr, "md conflict: package %s path %s md %s\n",
217 fprintf(stderr, "%s conflict: package %s path %s\n",
218 conflict_type, pkg, path);
225 static int check_existing(void *f, int ncols, char **vals, char **cols) {
226 struct config *conf = f;
231 if (!path) IERR("can't check for existing");
234 printf("checkfor %s\n", path);
239 fprintf(stderr, "check for existing %s\n", path);
242 if (lstat(path, &st) == 0) {
243 fprintf(stderr, "%s exists \n", path);
247 /* not an error, file shouldn't exist*/
250 fprintf(stderr, "unable to check %s: %s\n",
251 path, strerror(errno));
259 static int update_files(void *f, int ncols, char **vals, char **cols) {
260 struct config *conf = f;
266 if (!pkg) IERR("can't get pkgid");
268 if (!path) IERR("can't get path");
270 if (!dest) IERR("no file dest");
272 /* hash is different, so no different than install,
273 * but we don't need to create leading directories
277 fprintf(stderr, "update %s\n", dest);
281 fprintf(stderr, "update not implemented: %s", dest);
287 static int remove_files(void *f, int ncols, char **vals, char **cols) {
288 struct config *conf = f;
293 if (!dest) IERR("no file dest");
296 char *ftype = COL("filetype");
300 case 'd': printf("rmdir %s\n", dest); break;
301 default: printf("unlink %s\n", dest); break;
306 if (lstat(dest, &st) == -1) {
310 if (S_ISDIR(st.st_mode)) {
312 fprintf(stderr, "rmdir(%s)\n", dest);
315 } else if (S_ISREG(st.st_mode)) {
316 /* TODO conf to import before removal */
318 fprintf(stderr, "unlink(%s)\n", dest);
320 if (unlink(dest) == -1) {
321 IERR("can't unlink");
324 if (unlink(dest) == -1) {
325 IERR("can't unlink");
332 #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
334 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
335 /* 4 == ftype different */
336 /* 8 = hash different when both are regular files */
337 static unsigned int file_compare(char *path, char *hash, int ftype, uid_t uid,
338 gid_t gid, int perms) {
340 int etype = 0, stat_type;
341 char ehash[ZPM_HASH_STRLEN+1];
342 unsigned int diff = 0;
345 case 'd': etype = S_IFDIR; break;
346 case 'r': etype = S_IFREG; break;
347 default: etype = 0; break;
351 /* new file, so check type, hash, etc */
352 if (lstat(path, &st) == 0) {
353 stat_type = st.st_mode & S_IFMT;
354 if (stat_type != etype) {
357 if (stat_type == S_IFDIR && etype != S_IFDIR) {
360 if (hash && etype == S_IFREG && stat_type == S_IFREG) {
361 zpm_hash(path, ehash, 0);
362 if (strcmp(hash, ehash) != 0) {
366 if (uid != st.st_uid) {
369 if (gid != st.st_gid) {
372 if (perms != (st.st_mode & 07777)) {
377 case ENOENT: diff &= 1; break;
378 default: diff &= 128; break;
385 static int install_files(void *f, int ncols, char **vals, char **cols) {
386 struct config *conf = f;
400 /* TODO put the result row in a hash table. May not actually
405 if (op == 0) IERR("invalid operation");
407 /* TODO config to dishonor setuid/setgid */
409 if (!path) IERR("no file path");
410 if (strlen(path) == 0) {
411 IERR("zero length path not allowed");
414 if (!dest) IERR("no file dest");
415 if (strlen(dest) == 0) {
416 IERR("zero length dest not allowed");
421 if (!val) IERR("can't determine mode");
422 mode = strtoul(val, NULL, 8);
424 val = COL("filetype");
425 if (!val || strlen(val) == 0) {
426 IERR("can't determine file type");
432 if (!hash) IERR("can't get hash");
436 fprintf(stderr, "installing '%c' %s\n", ftype, dest);
443 val = COL("username");
444 if (!val) IERR("no username");
446 if (!pw) IERR("no passwd entry");
449 if (conf->setgroup) {
450 val = COL("groupname");
451 if (!val) IERR("no groupname");
453 if (!gr) IERR("no group entry");
458 //printf("cld %s\n", path);
459 printf("new %c%o %d:%d %s -> %s\n", ftype, mode, uid, gid, path, dest);
463 /* TODO should these be owned by the path owner if they don't exist? */
464 /* probably, though they then belong to the package, sort of */
465 if (!create_leading_dirs(dest)) {
466 return setsyserr(conf, "unable to create leading directories for %s\n", dest);
469 unsigned int diffs = file_compare(dest, hash, ftype, uid, gid, mode);
471 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
472 /* 4 == ftype different */
473 /* 8 = hash different when both are regular files */
475 return seterror(conf, "can't check %s", dest);
479 return seterror(conf, "absorb and overwrite not implemented");
483 if (mkdir(dest, mode) == -1) {
484 return setsyserr(conf, "can't create directory %s",
487 } else if (ftype == 'r') {
489 source = conf->src ? conf->src : conf->log;
491 if (conf->verbose > 1) {
492 fprintf(stderr, "extracting %8.8s to %s with mode %o\n",
496 if (!zpm_extract(source, hash, dest, mode)) {
497 IERR("can't extract file");
499 } else if (ftype == 'l') {
500 char *target = COL("target");
502 return seterror(conf, "no target for symlink %s\n",
506 if (strlen(target) == 0) {
507 IERR("zero length symlink not allowed");
510 if (conf->verbose > 0) {
511 fprintf(stderr, "symlink %s -> %s\n", dest, target);
513 if (symlink(target, dest) == -1) {
514 return setsyserr(conf, "symlink failed");
517 fprintf(stderr, "unhandled filetype %c\n", ftype);
520 if (conf->setuser && conf->setgroup) {
521 if (chown(dest, uid, gid) == -1) {
522 return setsyserr(conf, "can't chown %s", dest);
526 struct timespec times[2] = { {0}, {0} };
527 double mtime = strtod(COL("mtime"),NULL);
529 times[0].tv_nsec = UTIME_OMIT;
530 times[1].tv_sec = (time_t)llrint(floor(mtime));
531 times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
533 utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW);
536 printf("%s\n", path);
542 static void check_conflicts(struct config *conf, char *conflict_type,
543 int (callback)(void *, int, char **, char **)) {
549 s = sqlite3_str_new(conf->log->db);
550 sqlite3_str_appendall(s, "select *, ");
552 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
554 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
556 sqlite3_str_appendall(s, " as dest from syncconflicts");
559 sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
562 sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
564 sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
568 sql = sqlite3_str_value(s);
569 if (conf->verbose > 2) {
570 fprintf(stderr, "stage query: %s\n", sql);
573 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
575 sqlite3_str_finish(s);
578 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
580 fprintf(stderr, "database error: %s\n", errmsg);
583 if (conf->log->error == 1) {
584 fprintf(stderr, "unable to allocate memory\n");
586 fprintf(stderr, "zpm_exec failure: %s\n",
587 conf->log->errmsg ? conf->log->errmsg : "unknown");
590 if (conf->log->errmsg) {
591 fprintf(stderr, "error: %s\n", conf->log->errmsg);
593 if (conf->errors && conf->exitonerror) {
594 zpm_close(conf->log);
595 zpm_close(conf->src);
598 /* TODO final report function in conf var */
601 static void runstage(struct config *conf, char *stage,
602 int (callback)(void *, int, char **, char **)) {
608 s = sqlite3_str_new(conf->log->db);
609 sqlite3_str_appendall(s, "select *, ");
611 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
613 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
615 sqlite3_str_appendall(s, " as dest from syncinfo");
618 sqlite3_str_appendf(s," where op = %Q", stage);
621 sqlite3_str_appendall(s," order by length(path) desc, path desc");
624 sql = sqlite3_str_value(s);
625 if (conf->verbose > 2) {
626 fprintf(stderr, "stage query: %s\n", sql);
629 rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
631 sqlite3_str_finish(s);
634 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
636 fprintf(stderr, "database error: %s\n", errmsg);
639 if (conf->log->error == 1) {
640 fprintf(stderr, "unable to allocate memory\n");
642 fprintf(stderr, "zpm_exec failure: %s\n",
643 conf->log->errmsg ? conf->log->errmsg : "unknown");
646 if (conf->log->errmsg) {
647 fprintf(stderr, "error: %s\n", conf->log->errmsg);
649 if (conf->errors && conf->exitonerror) {
650 zpm_close(conf->log);
651 zpm_close(conf->src);
654 /* TODO final report function in conf var */
657 int main(int ac, char **av){
661 char *pkgdbfile = 0, *localdbfile = 0;
680 if (geteuid() != 0) {
685 localdbfile = ZPM_LOCAL_DB;
686 if ((s = getenv("ZPMDB"))) {
687 /* TODO does this need to be copied ? */
691 if ((s = getenv("ZPM_ROOT_DIR"))) {
692 /* TODO does this need to be copied ? */
697 * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
698 * -f 'package database', otherwise regular default of env
699 * ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
700 * -R root of pkg, will just chdir there
702 * args are pkgid triple, but will do a pkg find on the pkgdb
705 while ((opt = getopt(ac, av, "f:d:c:nCR:vOA")) != -1) {
707 case 'd': localdbfile = optarg; break;
708 case 'f': pkgdbfile = optarg; break;
709 case 'n': conf.dryrun = 1; break;
710 case 'v': conf.verbose++; break;
711 case 'C': conf.errabort = 0; break;
712 case 'R': conf.rootdir = optarg; break;
713 case 'N': conf.setuser = 0; conf.setgroup = 0; break;
714 case 'O': conf.overwrite = 1; break;
715 case 'A': conf.absorb = 1; break;
723 /* verify root dir exists */
724 if (conf.rootdir && !exists(conf.rootdir, NULL)) {
725 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
728 if (!zpm_open(&localdb, localdbfile)) {
729 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
735 if (!zpm_open(&pkgdb, pkgdbfile)) {
736 fprintf(stderr, "can't open src db %s\n", localdbfile);
743 /* TODO find pkgid from arg */
745 /* TODO set conf var to finalize error reporting */
747 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
748 conf.rootdir ? conf.rootdir : "/",
749 localdbfile, pkgdbfile);
753 conf.exitonerror = 0;
754 check_conflicts(&conf, NULL, report_conflicts);
755 if (conf.conflicts) {
756 fprintf(stderr, "%d conflicts reported, aborting sync\n",
760 /* no point in running it if we're just going to
761 * overwrite everything
763 if (conf.overwrite == 0 || conf.absorb == 0) {
764 runstage(&conf, "new", check_existing);
768 conf.exitonerror = 1;
769 runstage(&conf, "new", install_files);
770 runstage(&conf, "update", update_files);
772 runstage(&conf, "remove", remove_files);
779 return conf.errors ? 1 : 0;