]> pd.if.org Git - zpackage/blob - zpm-syncfs.c
improve syncfs dry-run output
[zpackage] / zpm-syncfs.c
1 #define _POSIX_C_SOURCE 200809L
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8 #include <limits.h>
9 #include <errno.h>
10 #include <ctype.h>
11 #include <pwd.h>
12 #include <grp.h>
13 #include <math.h>
14 #include <stdarg.h>
15 #include <time.h>
16
17 /* needed for S_IFMT and AT_FDCWD */
18 #include <fcntl.h>
19
20 #include <string.h>
21
22 #include "sqlite3.h"
23 #include "zpm.h"
24
25 struct config {
26         struct zpm *log; /* logging db will be attached as "log" */
27         struct zpm *src;
28         char *dbfile;
29         char *rootdir;
30         int errabort, errors, verbose, dryrun, conflicts;
31         int setuser, setgroup;
32         int reverse, exitonerror;
33         int overwrite, absorb;
34 };
35
36 struct nitem {
37         int op;
38         char *opstr;
39         uid_t uid;
40         gid_t gid;
41         char *dest;
42         char *path;
43         char *hash;
44         char *target;
45         time_t mtime;
46         mode_t mode;
47         int ftype;
48         struct timespec times[2];
49 };
50
51 static void usage() {
52         printf("usage: zpm $scriptname [-fncC] args ...\n");
53 }
54
55 static int seterror(struct config *conf, char *msgfmt, ...) {
56         char msg[1024];
57         va_list ap;
58
59         conf->errors++;
60
61         va_start(ap, msgfmt);
62         vsnprintf(msg, sizeof msg, msgfmt, ap);
63         va_end(ap);
64
65         msg[1023] = 0;
66         if (conf->log->errmsg) {
67                 free(conf->log->errmsg);
68         }
69
70         conf->log->errmsg = strdup(msg);
71
72         if (conf->verbose) {
73                 fprintf(stderr, "%s\n", msg);
74         }
75
76         return conf->errabort;
77 }
78
79 static int setsyserr(struct config *conf, char *msgfmt, ...) {
80         char msg[1024];
81         va_list ap;
82         int printed;
83
84         conf->errors++;
85
86         va_start(ap, msgfmt);
87         printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
88         va_end(ap);
89
90         if (printed < 1) {
91                 /* nothing we can really do */
92                 return conf->errabort;
93         }
94
95         if ((size_t)printed < sizeof msg) {
96                 snprintf(msg+printed, sizeof msg - printed, ": %s",
97                                 strerror(errno));
98         }
99
100         msg[1023] = 0;
101         if (conf->log->errmsg) {
102                 free(conf->log->errmsg);
103         }
104
105         conf->log->errmsg = strdup(msg);
106
107         if (conf->verbose) {
108                 fprintf(stderr, "%s\n", msg);
109         }
110
111         return conf->errabort;
112 }
113
114 static int exists(char *path, mode_t *mode) {
115         struct stat st;
116
117         if (lstat(path, &st) == -1) {
118                 return 0;
119         }
120         if (mode) *mode = st.st_mode;
121         return 1;
122 }
123
124 /* TODO maintain a list of already created directories */
125 static int create_leading_dirs(char *path) {
126         char *delim, *s;
127         int ch = 0;
128         char pcopy[ZPM_PATH_MAX];
129         struct stat st;
130         
131         strcpy(pcopy, path);
132
133         delim = strrchr(pcopy, '/');
134         if (!delim) return 1; /* not an error, but no leading dirs */
135
136         /* cut off last component */
137         *delim = 0;
138
139         s = pcopy;
140         do {
141                 while (*s == '/') {
142                         s++;
143                 }
144
145                 delim = strchr(s, '/');
146                 if (delim) {
147                         ch = *delim;
148                         *delim = 0;
149                 }
150
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
155                  */
156                 if (mkdir(pcopy, 0755) == -1) {
157                         switch (errno) {
158                                 case EEXIST:
159                                         if (stat(pcopy, &st) == -1) {
160                                                 /* can't stat? */
161                                                 return 0;
162                                         }
163                                         switch (st.st_mode & S_IFMT) {
164                                                 case S_IFDIR:
165                                                         break;
166                                                 default:
167                                                         return 0;
168                                         }
169                                         break;
170                                 default:
171                                         return 0;
172                         }
173                 }
174                 if (delim) {
175                         *delim = ch;
176                 }
177                 s = delim;
178         } while (delim);
179         
180         return 1;
181 }
182
183 static char *column(char *col, int ncols, char **vals, char **cols) {
184         int i = 0;
185         char *val = NULL;
186
187         for (i=0; i < ncols; i++) {
188 //              fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
189                 
190                 if (!strcmp(col, cols[i])) {
191                         val = vals[i];
192                         break;
193                 }
194         }
195         return val;
196 }
197
198 #define COL(x) column(x, ncols, vals, cols)
199 #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
200
201
202 static char *ops[] = { "new", "remove", "update", 0 };
203
204 enum op {
205         OP_NEW = 1,
206         OP_REMOVE = 2,
207         OP_UPDATE = 3
208 };
209
210 static int getop(char *opstr) {
211         int i;
212
213         if (!opstr) return 0;
214         for (i=0;ops[i];i++) {
215                 if (!strcmp(opstr, ops[i])) {
216                         return i+1;
217                 }
218         }
219         return 0;
220 }
221
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;
225
226         pkg = COL("pkgid");
227         path = COL("path");
228         conflict_type = COL("conflict");
229         if (!strcmp(conflict_type, "hash")) {
230                 hash = COL("hash");
231                 fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
232                                 pkg, path, hash);
233         } else
234         if (!strcmp(conflict_type, "md")) {
235                 mds = COL("mds");
236                 fprintf(stderr, "md conflict: package %s path %s md %s\n",
237                                 pkg, path, mds);
238         } else {
239                 fprintf(stderr, "%s conflict: package %s path %s\n",
240                                 conflict_type, pkg, path);
241         }
242
243         conf->conflicts++;
244         return 0;
245 }
246
247 static int check_existing(void *f, int ncols, char **vals, char **cols) {
248         struct config *conf = f;
249         char *path;
250         struct stat st;
251
252         path = COL("dest");
253         if (!path) {
254                 return seterror(conf, "no path");
255         }
256
257         if (conf->dryrun) {
258                 printf("checkfor %s\n", path);
259                 fflush(stdout);
260                 return 0;
261         }
262
263         if (conf->verbose) {
264                 fprintf(stderr, "check for existing %s\n", path);
265         }
266
267         if (lstat(path, &st) == 0) {
268                 fprintf(stderr, "%s exists\n", path);
269                 conf->errors++;
270         } else {
271                 switch(errno) {
272                         /* not an error, file shouldn't exist*/
273                         case ENOENT: break;
274                         default:
275                                 fprintf(stderr, "unable to check %s: %s\n",
276                                                 path, strerror(errno));
277                                 conf->errors++;
278                                 break;
279                 }
280         }
281         return 0;
282 }
283
284 static int remove_files(void *f, int ncols, char **vals, char **cols) {
285         struct config *conf = f;
286         char *dest;
287         struct stat st;
288
289         dest = COL("dest");
290         if (!dest) return seterror(conf,"no file dest");
291
292         if (conf->dryrun) {
293                 char *ftype = COL("filetype");
294                 int t = *ftype;
295
296                 switch(t) {
297                         case 'd': printf("rmdir %s\n", dest); break;
298                         default: printf("unlink %s\n", dest); break;
299                 }
300                 fflush(stdout);
301                 return 0;
302         }
303
304         if (lstat(dest, &st) == -1) {
305                 return seterror(conf,"can't stat");
306         }
307
308         if (S_ISDIR(st.st_mode)) {
309                 if (conf->verbose) {
310                         fprintf(stderr, "rmdir(%s)\n", dest);
311                 }
312                 rmdir(dest);
313         } else if (S_ISREG(st.st_mode)) {
314                 /* TODO conf to import before removal */
315                 if (conf->verbose) {
316                         fprintf(stderr, "unlink(%s)\n", dest);
317                 }
318                 if (unlink(dest) == -1) {
319                         return seterror(conf, "can't unlink");
320                 }
321         } else {
322                 if (unlink(dest) == -1) {
323                         return seterror(conf, "can't unlink");
324                 }
325         }
326         
327         return 0;
328 }
329
330 #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
331
332 #define D_NOEXIST 0x1
333 #define D_TYPE 0x2
334 #define D_MD 0x4
335 #define D_HASH 0x8
336 #define D_ISDIR  0x10
337 #define D_EISDIR 0x20
338 #define D_UID 0x40
339 #define D_GID 0x80
340 #define D_MODE 0x100
341 #define D_MTIME 0x200
342 #define D_ERROR 0x1000
343 #define D_STATERROR 0x2000
344 #define D_RLERROR 0x4000
345
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;
353         char link[1024];
354         ssize_t lsize;
355
356         switch (n->ftype) {
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;
361         }
362
363         errno = 0;
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) {
368                         diff |= D_TYPE;
369                 }
370                 if (stat_type == S_IFDIR) {
371                         diff |= D_EISDIR;
372                 }
373
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) {
377                                 diff |= D_HASH;
378                         }
379                 }
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) {
383                                 diff |= D_RLERROR;
384                                 diff |= D_ERROR;
385                         } else if (strcmp(n->target, link) != 0) {
386                                 diff |= D_HASH;
387                         }
388                 }
389                 if (n->uid != st->st_uid) {
390                         diff |= D_UID;
391                         diff |= D_MD;
392                 }
393                 if (n->gid != st->st_gid) {
394                         diff |= D_GID;
395                         diff |= D_MD;
396                 }
397                 if (n->mode != (st->st_mode & 07777)) {
398                         diff |= D_MODE;
399                         diff |= D_MD;
400                 }
401         } else {
402                 switch(errno) {
403                         case ENOENT: diff |= D_NOEXIST; break;
404                         default: diff |= (D_STATERROR|D_ERROR); break;
405                 }
406         }
407
408         return diff;
409 }
410
411 static int read_item(struct config *conf, int ncols, char **vals, char **cols,
412                 struct nitem *n) {
413         char *val;
414         struct passwd *pw;
415         struct group *gr;
416         struct nitem zero = { 0 };
417
418         *n = zero;
419
420         val = COL("op");
421         if (!val) {
422                 seterror(conf, "can't determine op");
423                 return 0;
424         }
425         n->opstr = val;
426         n->op = getop(val);
427         if (!n->op) {
428                 seterror(conf, "can't determine op");
429                 return 0;
430         }
431
432         n->path = COL("path");
433         if (!n->path) {
434                 seterror(conf, "no file path");
435                 return 0;
436         }
437         if (strlen(n->path) == 0) {
438                 seterror(conf, "zero length path not allowed");
439                 return 0;
440         }
441
442         /* TODO config to dishonor setuid/setgid */
443         n->dest = COL("dest");
444         if (!n->dest) {
445                 seterror(conf, "no file dest");
446                 return 0;
447         }
448
449         if (strlen(n->dest) == 0) {
450                 seterror(conf, "zero length dest not allowed");
451                 return 0;
452         }
453
454         val = COL("mode");
455
456         if (!val) {
457                 seterror(conf, "can't determine mode");
458                 return 0;
459         }
460
461         n->mode = strtoul(val, NULL, 8);
462
463         val = COL("filetype");
464         if (!val || strlen(val) == 0) {
465                 seterror(conf, "can't determine file type");
466                 return 0;
467         }
468         n->ftype = *val;
469
470         if (n->ftype == 'r') {
471                 n->hash = COL("hash");
472                 if (!n->hash) {
473                         seterror(conf, "can't get hash");
474                         return 0;
475                 }
476         } else if (n->ftype == 'l') {
477                 n->target = COL("target");
478                 if (!n->target) {
479                         seterror(conf, "can't get target");
480                         return 0;
481                 }
482                 if (strlen(n->target) == 0) {
483                         seterror(conf, "zero length target not allowed");
484                         return 0;
485                 }
486                 n->hash = n->target;
487         }
488
489         if (conf->setuser) {
490                 val = COL("username");
491                 if (!val) {
492                         seterror(conf, "no username");
493                         return 0;
494                 }
495                 pw = getpwnam(val);
496                 if (!pw) {
497                         seterror(conf, "no passwd entry");
498                         return 0;
499                 }
500                 n->uid = pw->pw_uid;
501         } else {
502                 n->uid = geteuid();
503         }
504
505         if (conf->setgroup) {
506                 val = COL("groupname");
507                 if (!val) {
508                         seterror(conf, "no groupname");
509                         return 0;
510                 }
511                 gr = getgrnam(val);
512                 if (!gr) {
513                         seterror(conf, "no group entry");
514                         return 0;
515                 }
516                 n->gid = gr->gr_gid;
517         } else {
518                 n->gid = getegid();
519         }
520
521         errno = 0;
522         double mtime = strtod(COL("mtime"),NULL);
523         if (errno) {
524                 mtime = (double)time(NULL);
525         }
526
527         n->mtime = (time_t)mtime;
528
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));
533
534         return 1;
535 }
536
537 static int remove_dir(struct config *conf, char *path) {
538         int rv;
539
540         rv = rmdir(path);
541         if (rv == -1) {
542                 setsyserr(conf, "can't rmdir %s", path);
543                 return 0;
544         }
545         return 1;
546 }
547
548 static int remove_existing(struct config *conf, char *path) {
549         int rv;
550
551         rv = unlink(path);
552         if (rv == -1) {
553                 setsyserr(conf, "can't unlink %s", path);
554                 return 0;
555         }
556         return 1;
557 }
558
559 static int set_md(struct config *conf, struct nitem *item) {
560         int rv;
561         int success = 0;
562
563         if (conf->dryrun) {
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,
567                                         item->dest);
568                 }
569                 printf("mtime %.0f %s\n", (double)item->mtime, item->dest);
570                 fflush(stdout);
571                 return success;
572         }
573
574         rv = chmod(item->dest, item->mode);
575
576         if (rv == -1) {
577                 setsyserr(conf, "can't chmod %o %s", item->mode, item->dest);
578                 return conf->errabort;
579         }
580
581         if (conf->setuser && conf->setgroup) {
582                 rv = chown(item->dest, item->uid, item->gid);
583                 if (rv == -1) {
584                         setsyserr(conf, "can't chown %s", item->dest);
585                         return conf->errabort;
586                 }
587         }
588
589         rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW);
590         if (rv == -1) {
591                 setsyserr(conf, "can't set mtime %.0f %s", (double)item->mtime,
592                                 item->dest);
593                 return conf->errabort;
594         }
595         return 0;
596 }
597
598 /* install a file or create a directory or symlink.  path should not exist
599  * at this point.
600  */
601 /* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file,
602  * 8 = rmdir existing dir, 16 = return true/false
603  */
604 static int install(struct config *conf, struct nitem *item, unsigned int flags) {
605         int rv = 1;
606         struct zpm *source;
607
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;
613         int success = 0;
614
615         if (flags & 16) {
616                 failure = 0;
617                 success = 1;
618         }
619
620         if (conf->dryrun) {
621                 if (unlink_file) {
622                         printf("unlink %s\n", item->dest);
623                 } else if (rm_dir) {
624                         printf("rmdir %s\n", item->dest);
625                 }
626
627                 printf("install %c%o %d:%d %s -> %s\n", item->ftype,
628                                 item->mode, item->uid, item->gid, item->path,
629                                 item->dest);
630                 fflush(stdout);
631                 return success;
632         }
633
634         source = conf->src ? conf->src : conf->log;
635
636         if (unlink_file) {
637                 rv = remove_existing(conf, item->dest);
638         } else if (rm_dir) {
639                 rv = remove_dir(conf, item->dest);
640         }
641
642         if (rv != 1) {
643                 return failure;
644         }
645
646         if (mkleading) {
647                 rv = create_leading_dirs(item->dest);
648                 if (!rv) {
649                         setsyserr(conf, "can't create leading dirs for %s", item->dest);
650                         return failure;
651                 }
652         }
653
654         if (item->ftype == 'r') {
655                 rv = zpm_extract(source, item->hash, item->dest, item->mode);
656                 if (rv == 0) {
657                         seterror(conf, "can't extract %s", item->dest);
658                         return failure;
659                 }
660                 return success;
661         }
662
663         switch (item->ftype) {
664                 case 'd': rv = mkdir(item->dest, item->mode);
665                           break;
666                 case 'l': rv = symlink(item->target, item->dest);
667                           break;
668                 default: /* error */
669                           break;
670         }
671
672         if (rv == -1) {
673                 setsyserr(conf, "installing %s failed", item->dest);
674                 return failure;
675         }
676
677         if (setmd) {
678                 return set_md(conf, item) == 0 ? success : failure;
679         }
680
681         return success;
682 }
683
684 static int install_files(void *f, int ncols, char **vals, char **cols) {
685         struct config *conf = f;
686         struct nitem nitem;
687         struct stat existing;
688         int update = 0;
689
690         /* TODO put the result row in a hash table.  May not actually
691          * be faster
692          */
693         if (!read_item(conf, ncols, vals, cols, &nitem)) {
694                 fprintf(stderr, "can't read item\n");
695                 return conf->errabort;
696         }
697
698         if (conf->verbose) {
699                 fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype,
700                                 nitem.dest);
701         }
702
703         unsigned int diffs = file_compare(&nitem, &existing);
704         if (diffs >= D_ERROR) {
705                 return seterror(conf, "can't check %s", nitem.dest);
706         }
707
708         /* updates:
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
717          *
718          * installs:
719          * no exist: create leading dirs, install
720          *
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
725          *
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
731          */
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);
742
743         if (update) {
744                 if (!exist) {
745                         /* warn, it should exist */
746                         fprintf(stderr, "%s missing, installing", nitem.dest);
747                         return install(conf, &nitem, 3);
748                 }
749
750                 /* file exists in filesystem */
751                 if (sametype) {
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
757                                  * as an update
758                                  */
759                                 /* do nothing */
760                                 return 0;
761                         }
762                         if (!mdsame && hashsame) {
763                                 /* fix md */
764                                 return set_md(conf, &nitem);
765                         }
766                         if (mdsame && !hashsame) {
767                                 /* install */
768                                 return install(conf, &nitem, 3);
769                         }
770                         if (!mdsame && !hashsame) {
771                                 /* install */
772                                 return install(conf, &nitem, 3);
773                         }
774                 }
775
776                 /* file exists, and is not the same type */
777
778                 if (isdir && !eisdir) {
779                         /* remove existing */
780                         /* mkdir */
781                         return install(conf, &nitem, 7);
782                 }
783                 if (!isdir && eisdir) {
784                         /* remove dir, or error */
785                         /* install */
786                         return install(conf, &nitem, 11);
787                 }
788                 if (!isdir && !isdir) {
789                         /* necessarily !sametype, sametype handled above */
790                         /* remove existing */
791                         /* install */
792                         return install(conf, &nitem, 7);
793                 }
794                 /* error, should not be possible, assert(0)? */
795         }
796
797         if (installing) {
798                 if (!exist) {
799                         return install(conf, &nitem, 3);
800                 }
801
802                 /* file exists in filesystem */
803                 if (sametype) {
804                         if (mdsame && hashsame && (accept || overwrite)) {
805                                 /* do nothing */
806                                 if (conf->dryrun || conf->verbose) {
807                                         fprintf(stderr, "accepting existing file: %s\n", nitem.dest);
808                                 }
809                                 return 0;
810                         }
811                         if (mdsame && hashsame && !(accept || overwrite)) {
812                                 /* error */
813                                 return seterror(conf, "will not accept or overwrite existing file: %s", nitem.dest);
814                         }
815                         if (mdsame && !hashsame && overwrite) {
816                                 /* install */
817                                 return install(conf, &nitem, eisdir ? 11 : 7);
818                         }
819                         if (mdsame && !hashsame && !overwrite) {
820                                 /* accept doesn't matter, since it's
821                                  * not an acceptable file */
822                                 /* error */
823                                 return seterror(conf, "%s (hashdiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
824                         }
825                         if (!mdsame && hashsame && overwrite) {
826                                 /* fix md */
827                                 return set_md(conf, &nitem);
828                         }
829                         if (!mdsame && hashsame && !overwrite) {
830                                 /* accept doesn't matter, since it's
831                                  * not an acceptable file */
832                                 /* error */
833                                 return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
834                         }
835                         if (!mdsame && !hashsame && overwrite) {
836                                 /* install */
837                                 return install(conf, &nitem, eisdir ? 11 : 7);
838                         }
839                         if (!mdsame && !hashsame && !overwrite) {
840                                 /* accept doesn't matter, since it's
841                                  * not an acceptable file */
842                                 /* error */
843                                 return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
844                         }
845                         /* TODO error, should be impossible */
846                         return seterror(conf, "impossible state reached");
847                 }
848
849                 /* file exists, and is not the same type */
850                 if (!overwrite) {
851                         /* error */
852                                 return seterror(conf, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
853                 }
854
855                 /* not the same type, but ok to overwrite */
856                 if (!eisdir) {
857                         /* remove existing */
858                         return install(conf, &nitem, 7);
859                 }
860
861                 /* existing path is a directory */
862                 if (isdir) {
863                         /* fix md */
864                         /* impossible, if isdir and eisdir, would
865                          * be same type
866                          * TODO
867                          */
868                         return set_md(conf, &nitem);
869                 } else {
870                         /* remove empty dir or error */
871                         /* install */
872                         return install(conf, &nitem, 11);
873                 }
874                 /* if we get here, we missed a case */
875                 /* TODO error */
876                 return seterror(conf, "impossible state 2 reached");
877         }
878
879         /* TODO extra verbose print perms, mtime, etc, probably ls -l
880          * format
881          */ 
882         if (conf->verbose) {
883                 printf("%s\n", nitem.path);
884         }
885
886         return 0;
887 }
888
889 static void check_conflicts(struct config *conf, char *conflict_type,
890                 int (callback)(void *, int, char **, char **)) {
891         int rv;
892         char *errmsg;
893         sqlite3_str *s;
894         char *sql;
895
896         s = sqlite3_str_new(conf->log->db);
897         sqlite3_str_appendall(s, "select *, ");
898         if (conf->rootdir) {
899                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
900         } else {
901                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
902         }
903         sqlite3_str_appendall(s, " as dest from syncconflicts");
904
905         if (conflict_type) {
906                 sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
907         }
908         if (conf->reverse) {
909                 sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
910         } else {
911                 sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
912
913         }
914
915         sql = sqlite3_str_value(s);
916
917         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
918
919         sqlite3_str_finish(s);
920
921         if (rv) {
922                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
923                 if (errmsg) {
924                         fprintf(stderr, "database error: %s\n", errmsg);
925                         conf->errors++;
926                 }
927                 if (conf->log->error == 1) {
928                         fprintf(stderr, "unable to allocate memory\n");
929                 }
930                 fprintf(stderr, "zpm_exec failure: %s\n",
931                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
932                 conf->errors++;
933         }
934         if (conf->log->errmsg) {
935                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
936         }
937         if (conf->errors && conf->exitonerror) {
938                 zpm_close(conf->log);
939                 zpm_close(conf->src);
940                 exit(EXIT_FAILURE);
941         }
942         /* TODO final report function in conf var */
943 }
944
945 static void runstage(struct config *conf, char *stage,
946                 int (callback)(void *, int, char **, char **)) {
947         int rv;
948         char *errmsg;
949         sqlite3_str *s;
950         char *sql;
951
952         s = sqlite3_str_new(conf->log->db);
953         sqlite3_str_appendall(s, "select *, ");
954         if (conf->rootdir) {
955                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
956         } else {
957                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
958         }
959         sqlite3_str_appendall(s, " as dest from syncinfo");
960
961         if (stage) {
962                 sqlite3_str_appendf(s," where op = %Q", stage);
963         }
964         if (conf->reverse) {
965                 sqlite3_str_appendall(s," order by length(path) desc, path desc");
966         }
967
968         sql = sqlite3_str_value(s);
969
970         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
971
972         sqlite3_str_finish(s);
973
974         if (rv) {
975                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
976                 if (errmsg) {
977                         fprintf(stderr, "database error: %s\n", errmsg);
978                         conf->errors++;
979                 }
980                 if (conf->log->error == 1) {
981                         fprintf(stderr, "unable to allocate memory\n");
982                 }
983                 fprintf(stderr, "zpm_exec failure: %s\n",
984                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
985                 conf->errors++;
986         }
987 #if 0
988         if (conf->log->errmsg) {
989                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
990         }
991 #endif
992         if (conf->errors && conf->exitonerror) {
993                 zpm_close(conf->log);
994                 zpm_close(conf->src);
995                 exit(EXIT_FAILURE);
996         }
997         /* TODO final report function in conf var */
998 }
999
1000 int main(int ac, char **av){
1001         struct zpm localdb;
1002         struct zpm pkgdb;
1003         int opt;
1004         char *pkgdbfile = 0, *localdbfile = 0;
1005         char *s;
1006
1007         struct config conf;
1008
1009         conf.errabort = 1;
1010         conf.errors = 0;
1011         conf.conflicts = 0;
1012         conf.verbose = 0;
1013         conf.dryrun = 0;
1014         conf.setuser = 1;
1015         conf.setgroup = 1;
1016         conf.log = 0;
1017         conf.src = 0;
1018         conf.rootdir = 0;
1019         conf.reverse = 0;
1020         conf.overwrite = 0;
1021         conf.absorb = 0;
1022
1023         if (geteuid() != 0) {
1024                 conf.setuser = 0;
1025                 conf.setgroup = 0;
1026         }
1027
1028         localdbfile = ZPM_LOCAL_DB;
1029         if ((s = getenv("ZPMDB"))) {
1030                 /* TODO does this need to be copied ? */
1031                 localdbfile = s;
1032         }
1033
1034         if ((s = getenv("ZPM_ROOT_DIR"))) {
1035                 /* TODO does this need to be copied ? */
1036                 conf.rootdir = s;
1037         }
1038
1039         /*
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
1044          *
1045          *  args are pkgid triple, but will do a pkg find on the pkgdb
1046          */
1047
1048         while ((opt = getopt(ac, av, "f:d:c:nCR:vOA")) != -1) {
1049                 switch (opt) {
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;
1059                         default:
1060                                   usage();
1061                                   exit(EXIT_FAILURE);
1062                                   break;
1063                 }
1064         }
1065
1066         /* verify root dir exists */
1067         if (conf.rootdir && !exists(conf.rootdir, NULL)) {
1068                 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
1069         }
1070
1071         if (!zpm_open(&localdb, localdbfile)) {
1072                 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
1073                 exit(EXIT_FAILURE);
1074         }
1075         conf.log = &localdb;
1076
1077         if (pkgdbfile) {
1078                 if (!zpm_open(&pkgdb, pkgdbfile)) {
1079                         fprintf(stderr, "can't open src db %s\n", localdbfile);
1080                         exit(EXIT_FAILURE);
1081                 } else {
1082                         conf.src = &pkgdb;
1083                 }
1084         }
1085
1086         /* TODO find pkgid from arg */
1087
1088         /* TODO set conf var to finalize error reporting */
1089         if (conf.verbose) {
1090                 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
1091                                 conf.rootdir ? conf.rootdir : "/",
1092                                 localdbfile, pkgdbfile);
1093         }
1094
1095         conf.errors = 0;
1096         conf.exitonerror = 0;
1097         check_conflicts(&conf, NULL, report_conflicts);
1098
1099         if (conf.conflicts) {
1100                 fprintf(stderr, "%d conflicts reported, aborting sync\n",
1101                                 conf.conflicts);
1102                 conf.errors++;
1103         } else {
1104                 /* no point in running it if we're just going to
1105                  * overwrite everything
1106                  */
1107                 if ((conf.overwrite == 0 || conf.absorb == 0) && !conf.dryrun) {
1108                         runstage(&conf, "new", check_existing);
1109                 }
1110
1111                 if (conf.verbose) {
1112                         fprintf(stderr, "beginning %ssync\n", conf.dryrun ?
1113                                         "dryrun " : "");
1114                 }
1115                 /* have to do the removes first otherwise
1116                  * old files may conflict with update file
1117                  * type changes
1118                  */
1119                 if (!conf.errors) {
1120                         conf.exitonerror = conf.dryrun ? 0 : 1;
1121                         conf.errabort = conf.dryrun ? 0 : 1;
1122                         conf.reverse = 1;
1123                         if (conf.verbose) {
1124                                 fprintf(stderr, "removing old files\n");
1125                         }
1126                         runstage(&conf, "remove", remove_files);
1127                         conf.reverse = 0;
1128                         if (conf.verbose) {
1129                                 fprintf(stderr, "updating files\n");
1130                         }
1131                         runstage(&conf, "update", install_files);
1132                         if (conf.verbose) {
1133                                 fprintf(stderr, "installing files\n");
1134                         }
1135                         runstage(&conf, "new", install_files);
1136                 }
1137         }
1138
1139         zpm_close(&localdb);
1140         zpm_close(conf.src);
1141         return conf.errors ? 1 : 0;
1142 }