]> pd.if.org Git - zpackage/blob - zpm-syncfs.c
allow partial hashes in zpm-extract
[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 <dirent.h>
9 #include <limits.h>
10 #include <errno.h>
11 #include <ctype.h>
12 #include <pwd.h>
13 #include <grp.h>
14 #include <math.h>
15 #include <stdarg.h>
16 #include <time.h>
17
18 /* needed for S_IFMT and AT_FDCWD */
19 #include <fcntl.h>
20
21 #include <string.h>
22
23 #include "sqlite3.h"
24 #include "zpm.h"
25
26 struct config {
27         struct zpm *log; /* logging db will be attached as "log" */
28         struct zpm *src;
29         char *dbfile;
30         char *rootdir;
31         int errabort, errors, verbose, dryrun, conflicts;
32         int setuser, setgroup;
33         int reverse, exitonerror;
34         int overwrite, accept, acceptdir, ignoredirmd;
35         int ops_total, ops_completed;
36         int ops_remove, ops_remove_completed;
37         int ops_update, ops_update_completed;
38         int ops_install, ops_install_completed;
39         int progress; /* type of progress meter */
40 };
41
42 struct nitem {
43         int op;
44         char *opstr;
45         uid_t uid;
46         gid_t gid;
47         char *dest;
48         char *path;
49         char *hash, *ohash;
50         char *mds, *omds;
51         char *target;
52         char *pkglist; /* space separated */
53         time_t mtime;
54         mode_t mode;
55         int ftype;
56         int configuration, oldwasconf;
57         struct timespec times[2];
58 };
59
60 static void usage() {
61         printf("usage: zpm $scriptname [-fncC] args ...\n");
62 }
63
64 static void warn(char *fmt, ...) {
65         va_list args;
66
67         va_start(args, fmt);
68         vfprintf(stderr, fmt, args);
69         va_end(args);
70         fprintf(stderr, "\n");
71 }
72
73 static void pdots(int len, int ch, int was, int now, int total) {
74         was = len * was / total;
75         if (now > total) {
76                 now = total;
77         }
78         now = len * now / total;
79         while (was++ < now) {
80                 putchar(ch);
81         }
82         fflush(stdout);
83 }
84
85 static int seterror(struct config *conf, char *msgfmt, ...) {
86         char msg[1024];
87         va_list ap;
88
89         conf->errors++;
90
91         va_start(ap, msgfmt);
92         vsnprintf(msg, sizeof msg, msgfmt, ap);
93         va_end(ap);
94
95         msg[1023] = 0;
96         if (conf->log->errmsg) {
97                 free(conf->log->errmsg);
98         }
99
100         conf->log->errmsg = strdup(msg);
101
102         if (conf->verbose) {
103                 fprintf(stderr, "%s\n", msg);
104         }
105
106         return conf->errabort;
107 }
108
109 static int setsyserr(struct config *conf, char *msgfmt, ...) {
110         char msg[1024];
111         va_list ap;
112         int printed;
113
114         conf->errors++;
115
116         va_start(ap, msgfmt);
117         printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
118         va_end(ap);
119
120         if (printed < 1) {
121                 /* nothing we can really do */
122                 return conf->errabort;
123         }
124
125         if ((size_t)printed < sizeof msg) {
126                 snprintf(msg+printed, sizeof msg - printed, ": %s",
127                                 strerror(errno));
128         }
129
130         msg[1023] = 0;
131         if (conf->log->errmsg) {
132                 free(conf->log->errmsg);
133         }
134
135         conf->log->errmsg = strdup(msg);
136
137         if (conf->verbose) {
138                 fprintf(stderr, "%s\n", msg);
139         }
140
141         return conf->errabort;
142 }
143
144 static int exists(char *path, mode_t *mode) {
145         struct stat st;
146
147         if (lstat(path, &st) == -1) {
148                 return 0;
149         }
150         if (mode) *mode = st.st_mode;
151         return 1;
152 }
153
154 /* TODO maintain a list of already created directories */
155 static int create_leading_dirs(char *path) {
156         char *delim, *s;
157         int ch = 0;
158         char pcopy[ZPM_PATH_MAX];
159         struct stat st;
160         
161         strcpy(pcopy, path);
162
163         delim = strrchr(pcopy, '/');
164         if (!delim) return 1; /* not an error, but no leading dirs */
165
166         /* cut off last component */
167         *delim = 0;
168
169         s = pcopy;
170         do {
171                 while (*s == '/') {
172                         s++;
173                 }
174
175                 delim = strchr(s, '/');
176                 if (delim) {
177                         ch = *delim;
178                         *delim = 0;
179                 }
180
181                 /* try to create the directory, if it exists
182                  * and is a directory or a symlink, that's ok
183                  * should be (eventually) a symlink to a directory
184                  * so we want stat here, not lstat
185                  */
186                 if (mkdir(pcopy, 0755) == -1) {
187                         switch (errno) {
188                                 case EEXIST:
189                                         if (stat(pcopy, &st) == -1) {
190                                                 /* can't stat? */
191                                                 return 0;
192                                         }
193                                         switch (st.st_mode & S_IFMT) {
194                                                 case S_IFDIR:
195                                                         break;
196                                                 default:
197                                                         return 0;
198                                         }
199                                         break;
200                                 default:
201                                         return 0;
202                         }
203                 }
204                 if (delim) {
205                         *delim = ch;
206                 }
207                 s = delim;
208         } while (delim);
209         
210         return 1;
211 }
212
213 static char *column(char *col, int ncols, char **vals, char **cols) {
214         int i = 0;
215         char *val = NULL;
216
217         for (i=0; i < ncols; i++) {
218 //              fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
219                 
220                 if (!strcmp(col, cols[i])) {
221                         val = vals[i];
222                         break;
223                 }
224         }
225         return val;
226 }
227
228 #define COL(x) column(x, ncols, vals, cols)
229 #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
230
231
232 static char *ops[] = { "new", "remove", "update", 0 };
233
234 enum op {
235         OP_NEW = 1,
236         OP_REMOVE = 2,
237         OP_UPDATE = 3
238 };
239
240 static int getop(char *opstr) {
241         int i;
242
243         if (!opstr) return 0;
244         for (i=0;ops[i];i++) {
245                 if (!strcmp(opstr, ops[i])) {
246                         return i+1;
247                 }
248         }
249         return 0;
250 }
251
252 static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
253         struct config *conf = f;
254         char *path, *hash, *pkg, *conflict_type, *mds;
255
256         pkg = COL("pkgid");
257         path = COL("path");
258         conflict_type = COL("conflict");
259         if (!strcmp(conflict_type, "hash")) {
260                 hash = COL("hash");
261                 fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
262                                 pkg, path, hash);
263         } else
264         if (!strcmp(conflict_type, "md")) {
265                 mds = COL("mds");
266                 fprintf(stderr, "md conflict: package %s path %s md %s\n",
267                                 pkg, path, mds);
268         } else {
269                 fprintf(stderr, "%s conflict: package %s path %s\n",
270                                 conflict_type, pkg, path);
271         }
272
273         conf->conflicts++;
274         return 0;
275 }
276
277 static int read_item(struct config *conf, int ncols, char **vals, char **cols,
278                 struct nitem *n) {
279         char *val;
280         long lval;
281         struct passwd *pw;
282         struct group *gr;
283         struct nitem zero = { 0 };
284
285         *n = zero;
286
287         val = COL("op");
288         if (!val) {
289                 seterror(conf, "can't determine op");
290                 return 0;
291         }
292         n->opstr = val;
293         n->op = getop(val);
294         if (!n->op) {
295                 seterror(conf, "can't determine op");
296                 return 0;
297         }
298
299         n->path = COL("path");
300         if (!n->path) {
301                 seterror(conf, "no file path");
302                 return 0;
303         }
304         if (strlen(n->path) == 0) {
305                 seterror(conf, "zero length path not allowed");
306                 return 0;
307         }
308
309         /* TODO config to dishonor setuid/setgid */
310         n->dest = COL("dest");
311         if (!n->dest) {
312                 seterror(conf, "no file dest");
313                 return 0;
314         }
315
316         if (strlen(n->dest) == 0) {
317                 seterror(conf, "zero length dest not allowed");
318                 return 0;
319         }
320
321         val = COL("mode");
322
323         if (!val) {
324                 seterror(conf, "can't determine mode");
325                 return 0;
326         }
327
328         n->mode = strtoul(val, NULL, 8);
329
330         val = COL("configuration");
331         if (!val) {
332                 seterror(conf, "can't determine config status");
333                 return 0;
334         }
335         lval = strtol(val, NULL, 10);
336
337         n->configuration = ((lval & 1) != 0);
338         n->oldwasconf = ((lval & 2) != 0);
339
340         val = COL("filetype");
341         if (!val || strlen(val) == 0) {
342                 seterror(conf, "can't determine file type");
343                 return 0;
344         }
345         n->ftype = *val;
346
347         /* these can be null */
348         n->ohash = COL("ohash");
349         n->mds = COL("mds");
350         n->omds = COL("omds");
351         n->pkglist = COL("pkglist");
352
353         if (n->ftype == 'r') {
354                 n->hash = COL("hash");
355                 if (!n->hash) {
356                         seterror(conf, "can't get hash");
357                         return 0;
358                 }
359         } else if (n->ftype == 'l') {
360                 n->target = COL("target");
361                 if (!n->target) {
362                         seterror(conf, "can't get target");
363                         return 0;
364                 }
365                 if (strlen(n->target) == 0) {
366                         seterror(conf, "zero length target not allowed");
367                         return 0;
368                 }
369                 n->hash = n->target;
370         }
371
372         if (conf->setuser) {
373                 val = COL("username");
374                 if (!val) {
375                         seterror(conf, "no username");
376                         return 0;
377                 }
378                 pw = getpwnam(val);
379                 if (!pw) {
380                         seterror(conf, "no passwd entry");
381                         return 0;
382                 }
383                 n->uid = pw->pw_uid;
384         } else {
385                 n->uid = geteuid();
386         }
387
388         if (conf->setgroup) {
389                 val = COL("groupname");
390                 if (!val) {
391                         seterror(conf, "no groupname");
392                         return 0;
393                 }
394                 gr = getgrnam(val);
395                 if (!gr) {
396                         seterror(conf, "no group entry for %s", val);
397                         return 0;
398                 }
399                 n->gid = gr->gr_gid;
400         } else {
401                 n->gid = getegid();
402         }
403
404         errno = 0;
405         double mtime = strtod(COL("mtime"),NULL);
406         if (errno) {
407                 mtime = (double)time(NULL);
408         }
409
410         n->mtime = (time_t)mtime;
411
412         n->times[0].tv_sec = 0;
413         n->times[0].tv_nsec = UTIME_OMIT;
414         n->times[1].tv_sec = (time_t)llrint(floor(mtime));
415         n->times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
416
417         return 1;
418 }
419
420 /* file does not exist */
421 #define D_NOEXIST 0x1
422 /* files are different types */
423 #define D_TYPE 0x2
424 /* metadata is different */
425 #define D_MD 0x4
426 /* content or link target is different */
427 #define D_HASH 0x8
428 /* file to be installed is a directory */
429 #define D_ISDIR  0x10
430 /* path on disk is a directory */
431 #define D_EISDIR 0x20
432 /* usernames different */
433 #define D_UID 0x40
434 /* group names different */
435 #define D_GID 0x80
436 /* file mode is different */
437 #define D_MODE 0x100
438 /* mtimes are different */
439 #define D_MTIME 0x200
440 /* the hash of the file we are supposedly replacing is different than
441  * the the hash of the file on disk
442  */
443 #define D_OHASH 0x400
444 /* file exists, and is a directory, and is empty */
445 #define D_ISEMPTY 0x800
446 /* an error occurred trying to compare the file (other than it doesn't exist */
447 #define D_ERROR 0x1000
448 /* there was a stat error */
449 #define D_STATERROR 0x2000
450 /* there was an error calling readlink */
451 #define D_RLERROR 0x4000
452
453 static int dir_is_empty(char *path) {
454         DIR *dir;
455         struct dirent *dp;
456         int empty;
457
458         dir = opendir(path);
459         if (!dir) {
460                 return -1;
461         }
462
463         dp = readdir(dir);
464         if (dp) {
465                 empty = 0;
466         } else {
467                 empty = 1;
468         }
469         closedir(dir);
470
471         return empty;
472 }
473
474 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
475 /* 4 == ftype different */
476 /* 8 = hash different when both are regular files */
477 static unsigned int file_compare(struct nitem *n, struct stat *st) {
478         int etype = 0, stat_type;
479         char ehash[ZPM_HASH_STRLEN+1];
480         unsigned int diff = 0;
481         char link[1024];
482         ssize_t lsize;
483
484         switch (n->ftype) {
485                 case 'd': etype = S_IFDIR; diff |= D_ISDIR ; break;
486                 case 'r': etype = S_IFREG; break;
487                 case 'l': etype = S_IFLNK; break;
488                 default: etype = 0; break;
489         }
490
491         errno = 0;
492         /* new file, so check type, hash, etc */
493         if (lstat(n->dest, st) == 0) {
494                 stat_type = st->st_mode & S_IFMT;
495                 if (stat_type != etype) {
496                         diff |= D_TYPE;
497                 }
498                 if (stat_type == S_IFDIR) {
499                         diff |= D_EISDIR;
500                         if (dir_is_empty(n->dest)) {
501                                 diff |= D_ISEMPTY;
502                         }
503                 }
504
505                 if (n->hash && etype == S_IFREG && stat_type == S_IFREG) {
506                         zpm_hash(n->dest, ehash, 0);
507                         if (strcmp(n->hash, ehash) != 0) {
508                                 diff |= D_HASH;
509                         }
510                         if (n->ohash && strcmp(n->ohash, ehash) != 0) {
511                                 diff |= D_OHASH;
512                         }
513                 }
514                 if (n->hash && etype == S_IFLNK && stat_type == S_IFLNK) {
515                         lsize = readlink(n->dest, link, sizeof link);
516
517                         if (lsize == -1 || lsize == sizeof link) {
518                                 diff |= D_RLERROR;
519                                 diff |= D_ERROR;
520                         } else {
521                                 link[lsize] = 0;
522                                 if (strcmp(n->target, link) != 0) {
523                                         diff |= D_HASH;
524                                 }
525                         }
526                 }
527                 if (n->uid != st->st_uid) {
528                         diff |= D_UID;
529                         diff |= D_MD;
530                 }
531                 if (n->gid != st->st_gid) {
532                         diff |= D_GID;
533                         diff |= D_MD;
534                 }
535                 if (n->mode != (st->st_mode & 07777)) {
536                         diff |= D_MODE;
537                         diff |= D_MD;
538                 }
539         } else {
540                 switch(errno) {
541                         case ENOENT: diff |= D_NOEXIST; break;
542                         default: diff |= (D_STATERROR|D_ERROR); break;
543                 }
544         }
545
546         return diff;
547 }
548
549
550 /* 0 = not acceptable
551  * 1 = accept and create/update/remove
552  * 2 = accept as is
553  * 3 = remove and overwrite
554  * 4 = update metadata
555  */
556 static int acceptable(struct config *conf, unsigned int diffs, int op) {
557         int exist = (!(diffs & D_NOEXIST));
558         int sametype = (!(diffs & D_TYPE));
559         int mdsame = (!(diffs & D_MD));
560         int hashsame = (!(diffs & D_HASH));
561         int isdir = (diffs & D_ISDIR);
562
563         if (!exist) {
564                 return op == OP_REMOVE ? 2 : 1;
565         }
566
567         if (op == OP_UPDATE) {
568                 return sametype ? 4 : 3;
569         }
570
571         if (op == OP_REMOVE) {
572                 return 1;
573         }
574
575         /* the hard cases, should be installing new, but already exists */
576
577         if (!sametype) {
578                 return conf->overwrite ? 3 : 0;
579         }
580
581         if (mdsame && (conf->accept || conf->overwrite)) {
582                 return 1;
583         }
584
585         if (isdir) {
586                 if (mdsame || conf->ignoredirmd) {
587                         return conf->acceptdir ? 2 : 0;
588                 }
589                 if (conf->overwrite) {
590                         return 4;
591                 }
592         }
593
594         if (hashsame && (conf->accept || conf->overwrite)) {
595                 return 1;
596         }
597
598         return conf->overwrite ? 3 : 0;
599 }
600
601 static int check_existing(void *f, int ncols, char **vals, char **cols) {
602         struct config *conf = f;
603         struct stat st;
604         struct nitem nitem;
605
606         if (!read_item(conf, ncols, vals, cols, &nitem)) {
607                 fprintf(stderr, "can't read item\n");
608                 return conf->errabort;
609         }
610
611         if (conf->verbose > 1) {
612                 fprintf(stderr, "check for existing %s\n", nitem.path);
613         }
614
615         if (lstat(nitem.path, &st) == -1) {
616                 switch(errno) {
617                         /* not an error, file shouldn't exist*/
618                         case ENOENT: break;
619                         default:
620                                 fprintf(stderr, "unable to check %s: %s\n",
621                                                 nitem.path, strerror(errno));
622                                 conf->errors++;
623                                 break;
624                 }
625                 return 0;
626         }
627
628         unsigned int diffs = file_compare(&nitem, &st);
629         int sametype = (!(diffs & D_TYPE));
630
631         if (diffs >= D_ERROR) {
632                 return seterror(conf, "can't check %s", nitem.dest);
633         }
634
635         if (sametype && nitem.configuration) {
636                 return 0;
637         }
638
639         int action = acceptable(conf, diffs, nitem.op);
640         if (!action) {
641                 if (conf->accept) {
642                         fprintf(stderr, "%s exists and is not acceptable\n", nitem.path);
643                 } else {
644                         fprintf(stderr, "%s exists\n", nitem.path);
645                 }
646                 conf->errors++;
647         }
648
649         return 0;
650 }
651
652 static int remove_files(void *f, int ncols, char **vals, char **cols) {
653         struct config *conf = f;
654         char *dest;
655         struct stat st;
656         int flags = 0;
657
658         dest = COL("dest");
659         if (!dest) return seterror(conf,"no file dest");
660
661         if (conf->dryrun) {
662                 char *ftype = COL("filetype");
663                 int t = *ftype;
664
665                 switch(t) {
666                         case 'd': printf("rmdir %s\n", dest); break;
667                         default: printf("unlink %s\n", dest); break;
668                 }
669                 fflush(stdout);
670                 return 0;
671         }
672
673         if (conf->verbose) {
674                 if (conf->progress == 2) {
675                         fprintf(stderr, "%s(%s)\n", flags ? "rmdir" : "unlink", dest);
676                 } else if (conf->progress == 1) {
677                         /* overwrite */
678                         pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
679                         conf->ops_completed++;
680                         conf->ops_completed++;
681                 } else {
682                         pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
683                         conf->ops_completed++;
684                 }
685         }
686
687         errno = 0;
688
689         if (lstat(dest, &st) == -1) {
690                 switch (errno) {
691                         case ENOENT:
692                                 /* TODO chatter if verbose */
693                                 break;
694                         default:
695                                 return seterror(conf, "can't stat %s: %s", dest, strerror(errno));
696                 }
697                 return 0;
698         }
699
700         if (S_ISDIR(st.st_mode)) {
701                 flags = AT_REMOVEDIR;
702         }
703         /* TODO check that expected filetype matches actual filetype */
704
705         errno = 0;
706
707         if (unlinkat(AT_FDCWD, dest, flags) == -1) {
708                 switch (errno) {
709                         case ENOENT:
710                                 break;
711                         case ENOTEMPTY: /* fall through */
712                         case EEXIST:
713                                 /* TODO chatter, or possibly require */
714                                 break;
715                         default:
716                                 return seterror(conf, "can't unlink %s: %s", dest, strerror(errno));
717                 }
718         }
719         
720         return 0;
721 }
722
723 #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
724
725 static int remove_dir(struct config *conf, char *path) {
726         int rv;
727
728         rv = rmdir(path);
729         if (rv == -1) {
730                 setsyserr(conf, "can't rmdir %s", path);
731                 return 0;
732         }
733         return 1;
734 }
735
736 static int remove_existing(struct config *conf, char *path) {
737         int rv;
738
739         rv = unlink(path);
740         if (rv == -1) {
741                 setsyserr(conf, "can't unlink %s", path);
742                 return 0;
743         }
744         return 1;
745 }
746
747 static int set_md(struct config *conf, struct nitem *item) {
748         int rv;
749         int success = 0;
750
751         if (conf->dryrun) {
752                 if (item->ftype != 'l') {
753                         printf("chmod %o %s\n", item->mode, item->dest);
754                 }
755                 if (conf->setuser && conf->setgroup) {
756                         printf("lchown %d:%d %s\n", item->uid, item->gid,
757                                         item->dest);
758                 }
759                 printf("mtime %.0f %s\n", (double)item->mtime, item->dest);
760                 fflush(stdout);
761                 return success;
762         }
763
764         if (conf->setuser && conf->setgroup) {
765                 rv = lchown(item->dest, item->uid, item->gid);
766                 if (rv == -1) {
767                         setsyserr(conf, "can't lchown %s", item->dest);
768                         return conf->errabort;
769                 }
770         }
771
772         /* have to chmod after the chown, setuid bits may (and will)
773          * be cleared after a chown
774          */
775         /* can't chmod a symlink */
776         if (item->ftype != 'l') {
777                 rv = chmod(item->dest, item->mode);
778
779                 if (rv == -1) {
780                         setsyserr(conf, "can't chmod %o %s", item->mode, item->dest);
781                         return conf->errabort;
782                 }
783         }
784
785
786         rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW);
787         if (rv == -1) {
788                 setsyserr(conf, "can't set mtime %.0f %s", (double)item->mtime,
789                                 item->dest);
790                 return conf->errabort;
791         }
792         return 0;
793 }
794
795 /* install a file or create a directory or symlink.  path should not exist
796  * at this point.
797  */
798 /* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file,
799  * 8 = rmdir existing dir, 16 = return true/false
800  */
801 #define INS_MD 0x1
802 #define INS_CLD 0x2
803 #define INS_UNLINK 0x4
804 #define INS_RMDIR 0x8
805 #define INS_RTF 0x10
806 #define INS_ZPMNEW 0x20
807 static int install(struct config *conf, struct nitem *item, unsigned int flags) {
808         int rv = 1;
809         struct zpm *source;
810
811         int mkleading = (flags & 2);
812         int setmd = (flags & 1);
813         int unlink_file = (flags & 4);
814         int rm_dir = (flags & 8);
815         int failure = conf->errabort;
816         int success = 0;
817
818         if (flags & INS_RTF) {
819                 failure = 0;
820                 success = 1;
821         }
822
823         if (conf->dryrun) {
824                 if (unlink_file) {
825                         printf("unlink %s\n", item->dest);
826                 } else if (rm_dir) {
827                         printf("rmdir %s\n", item->dest);
828                 }
829
830                 printf("install %c%o %d:%d %s", item->ftype,
831                                 item->mode, item->uid, item->gid,
832                                 item->dest);
833                 if (item->ftype == 'l') {
834                         printf(" -> %s", item->target);
835                 }
836                 printf("\n");
837                 fflush(stdout);
838                 return success;
839         }
840
841         source = conf->src ? conf->src : conf->log;
842
843         if (unlink_file) {
844                 rv = remove_existing(conf, item->dest);
845         } else if (rm_dir) {
846                 rv = remove_dir(conf, item->dest);
847         }
848
849         if (rv != 1) {
850                 return failure;
851         }
852
853         if (mkleading) {
854                 rv = create_leading_dirs(item->dest);
855                 if (!rv) {
856                         setsyserr(conf, "can't create leading dirs for %s", item->dest);
857                         return failure;
858                 }
859         }
860
861         errno = 0;
862         switch (item->ftype) {
863                 case 'r': rv = zpm_extract(source, item->hash, item->dest, item->mode);
864                           if (rv == 0) rv = -1;
865                           break;
866                 case 'd': rv = mkdir(item->dest, item->mode);
867                           break;
868                 case 'l': rv = symlink(item->target, item->dest);
869                           break;
870                 default: /* error */
871                           break;
872         }
873
874         if (rv == -1) {
875                 switch (item->ftype) {
876                         case 'r':
877                                 seterror(conf, "can't extract %s", item->dest);
878                                 break;
879                         case 'd':
880                                 setsyserr(conf, "install mkdir(\"%s\") failed", item->dest);
881                                 break;
882                         case 'l':
883                                 setsyserr(conf, "install symlink(\"%s\") failed", item->dest);
884                                 break;
885                 }
886                 setsyserr(conf, "installing %s failed", item->dest);
887                 return failure;
888         }
889
890         if (setmd) {
891                 return set_md(conf, item) == 0 ? success : failure;
892         }
893
894         return success;
895 }
896
897 static int save_config_file(struct config *conf, struct nitem *n, char *msgfmt) {
898         char hash[ZPM_HASH_STRLEN+1];
899
900         if (!msgfmt) {
901                 msgfmt = "saved config file %.8s";
902         }
903
904         if (zpm_import(conf->log, n->dest, 0, hash)) {
905                 zpm_note_add(conf->log, n->pkglist, n->path, hash, msgfmt, hash);
906         } else {
907                 warn("unable to import existing config file %s", n->dest);
908                 conf->errors++;
909                 return 0;
910         }
911         return 1;
912 }
913
914 #if 0
915 /*
916  * figure out what the difference is for a config file, only called
917  * for an update of a configuration file
918  * return -1 on an error
919  * return 1 if the new file should not be installed
920  * return 0 if the new file should be installed
921  */
922 static int adjust_for_config(struct nitem *n, unsigned int diffs) {
923
924         if (!n->oldwasconf) {
925                 return 0;
926         }
927
928         int sametype = (!(diffs & D_TYPE));
929         int isdir = (diffs & D_ISDIR);
930         int eisdir = (diffs & D_EISDIR);
931         
932         /* TODO what if old was a directory? */
933         if (!n->configuration) {
934                 /* replacing conf with non-conf */
935                 /* absorb file, mark todo */
936                 return 0;
937         }
938
939         /* both old and new are config files */
940         if (isdir && sametype) {
941                 /* both config directories, can only be changing
942                  * metadata, so no adjustment needed
943                  */
944                 return 0;
945         }
946
947         if (isdir) {
948                 return 0;
949         }
950
951         if (eisdir) {
952                 /* replacing old conf directory with a conf file.
953                  * nothing needs to be done, if the directory
954                  * is empty, it's ok to remove.  if it's not empty,
955                  * the install will fail
956                  */
957                 return 0;
958         }
959         
960         /* replacing old file with new file */
961         /* new is same as on disk */
962         if (!(diffs & D_HASH)) {
963                 return 0;
964         }
965
966         /* new is different than on disk, but on disk is same as old */
967         if (!(diffs & D_OHASH)) {
968                 return 0;
969         }
970
971         return 1;
972
973 }
974 #endif
975
976 static int config_handler(void *f, int ncols, char **vals, char **cols) {
977         struct config *conf = f;
978         struct nitem nitem;
979         struct stat existing;
980         char *save = 0;
981         char *note = 0;
982         char *notehash = 0;
983         int update = 0;
984
985         if (!read_item(conf, ncols, vals, cols, &nitem)) {
986                 fprintf(stderr, "can't read item\n");
987                 conf->errors++;
988                 return conf->errabort;
989         }
990
991         unsigned int diffs = file_compare(&nitem, &existing);
992         if (diffs >= D_ERROR) {
993                 return seterror(conf, "can't check %s", nitem.dest);
994         }
995
996         int exist = (!(diffs & D_NOEXIST));
997         int sametype = (!(diffs & D_TYPE));
998         //int mdsame = (!(diffs & D_MD));
999         int hashsame = (!(diffs & D_HASH));
1000         int oldhashsame = (!(diffs & D_OHASH));
1001         int isdir = (diffs & D_ISDIR);
1002         int eisdir = (diffs & D_EISDIR);
1003         update = (nitem.op == OP_UPDATE);
1004
1005         notehash = nitem.hash;
1006
1007         /* if the file doesn't exist in the system, nothing to do */
1008         /* could possibly note if we expected it, but the regular handling
1009          * should do that
1010          */
1011         if (!exist) {
1012                 return 0;
1013         }
1014
1015         if (nitem.op == OP_UPDATE && !nitem.oldwasconf) {
1016                 /* possibly save anyway */
1017                 return 0;
1018         }
1019
1020         /* so, old was conf, and something exists in the filesystem */
1021
1022         if (!sametype) {
1023                 warn("won't %s %s%s, %s exists",
1024                                 nitem.op == OP_NEW ? "install" : nitem.opstr,
1025                                 nitem.path,
1026                                 isdir ? "/" : "",
1027                                 eisdir ? "directory" : "file"
1028                     );
1029                 conf->errors++;
1030                 return conf->errabort;
1031         }
1032
1033         /* all below are same type of file */
1034         /* what about sametype, but old was different type */
1035
1036         if (isdir) {
1037                 return 0;
1038         }
1039
1040         /* save or note cases */
1041
1042         if (nitem.op == OP_REMOVE) {
1043                 save ="saved removed config file %.8s";
1044         } else 
1045
1046         if (nitem.op == OP_UPDATE) {
1047                 if (!nitem.configuration) {
1048                         /* replacing config with non-config */
1049                         save = "replacing configuration file %.8s with non-configuration file";
1050                 } else if (oldhashsame) {
1051                         /* config file hasn't changed from old default,
1052                          * so go ahead and install the new one
1053                          */
1054                         save = "replaced old default config (%.8s) with new one.";
1055                         notehash = nitem.ohash;
1056                 } else {
1057                         note = "kept existing config file.  new default version is %.8s";
1058                         save = 0;
1059                 }
1060         } else
1061
1062         if (nitem.op == OP_NEW && !hashsame) {
1063                 note = "config file already existed.  would have installed %.8s";
1064                 save = 0;
1065         }
1066
1067         /*
1068          * save files, add notes
1069          */
1070         if (!conf->dryrun) {
1071                 if (save) {
1072                         warn("saving config file: %s (root %s)", nitem.path, conf->rootdir ? conf->rootdir : "/");
1073                         save_config_file(conf, &nitem, save);
1074                 }
1075                 if (note) {
1076                         zpm_note_add(conf->log, nitem.pkglist, nitem.path, nitem.hash,
1077                                         note, nitem.hash);
1078                 }
1079         } else {
1080                 if (save) {
1081                         fprintf(stderr, "dry run: %s %s: ", nitem.pkglist,
1082                                         nitem.path);
1083                         warn(save, notehash);
1084                 }
1085                 if (note) {
1086                         fprintf(stderr, "dry run: %s %s: ", nitem.pkglist,
1087                                         nitem.path);
1088                         warn(note, notehash);
1089                 }
1090
1091         }
1092
1093         return 0;
1094 }
1095
1096 static void handle_config_files(struct config *conf) {
1097         int rv;
1098         char *errmsg;
1099         sqlite3_str *s;
1100         char *sql;
1101
1102         s = sqlite3_str_new(conf->log->db);
1103         sqlite3_str_appendall(s, "select *, ");
1104         if (conf->rootdir) {
1105                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
1106         } else {
1107                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
1108         }
1109         sqlite3_str_appendall(s, " as dest from syncinfo");
1110
1111         sqlite3_str_appendall(s," where configuration > 0");
1112
1113         if (conf->reverse) {
1114                 sqlite3_str_appendall(s," order by length(path) desc, path desc");
1115         }
1116
1117         sql = sqlite3_str_value(s);
1118
1119         rv = zpm_exec(conf->log, sql, config_handler, conf, &errmsg);
1120
1121         sqlite3_str_finish(s);
1122
1123         if (rv) {
1124                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
1125                 if (errmsg) {
1126                         fprintf(stderr, "database error: %s\n", errmsg);
1127                         conf->errors++;
1128                 }
1129                 if (conf->log->error == 1) {
1130                         fprintf(stderr, "unable to allocate memory\n");
1131                 }
1132                 fprintf(stderr, "zpm_exec failure: %s\n",
1133                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
1134                 conf->errors++;
1135         }
1136
1137         if (conf->errors && conf->exitonerror) {
1138                 zpm_close(conf->log);
1139                 zpm_close(conf->src);
1140                 exit(EXIT_FAILURE);
1141         }
1142 }
1143
1144 static int install_files(void *f, int ncols, char **vals, char **cols) {
1145         struct config *conf = f;
1146         struct nitem nitem;
1147         struct stat existing;
1148         int update = 0;
1149
1150         /* TODO put the result row in a hash table.  May not actually
1151          * be faster
1152          */
1153         if (!read_item(conf, ncols, vals, cols, &nitem)) {
1154                 fprintf(stderr, "can't read item\n");
1155                 return conf->errabort;
1156         }
1157
1158 #if 0
1159         int64_t used, high;
1160         used = sqlite3_memory_used()/1024/1024;
1161         high = sqlite3_memory_highwater(0)/1024/1024;
1162         fprintf(stderr, "memory = %ld MB / %ld MB\n", used, high);
1163 #endif
1164         if (conf->verbose && !conf->dryrun) {
1165                 if (conf->progress == 2) {
1166                         fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype,
1167                                         nitem.dest);
1168                 } else if (conf->progress == 1) {
1169                         /* overwrite */
1170                         pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
1171                         conf->ops_completed++;
1172                         conf->ops_completed++;
1173                 } else {
1174                         pdots(50, '.', conf->ops_completed, conf->ops_completed + 1, conf->ops_total);
1175                         conf->ops_completed++;
1176                 }
1177         }
1178
1179         unsigned int diffs = file_compare(&nitem, &existing);
1180         if (diffs >= D_ERROR) {
1181                 return seterror(conf, "can't check %s", nitem.dest);
1182         }
1183
1184         /* updates:
1185          * exist & same type & md same & hash same: do nothing, but warn bug
1186          * exist & same type & md diff & hash same: fix md
1187          * exist & same type & md same & hash diff: replace
1188          * exist & same type & md diff & hash diff: replace & fix
1189          * no exist: install and warn
1190          * dir & not dir : remove, mkdir
1191          * not dir & not dir & diff type: remove, install
1192          * not dir & dir : remove dir if empty, error if not empty, install
1193          *
1194          * installs:
1195          * no exist: create leading dirs, install
1196          *
1197          * exist & same type & md same & hash same & accept or over: do nothing
1198          * exist & same & md diff or hash diff & overwrite : update
1199          * exist & same & md diff or hash diff & accept : error, can't accept
1200          * exist & same & md diff or hash diff & not accept : error
1201          *
1202          * exist & different type & not overwrite : error
1203          * not dir & not dir & overwrite : remove and install
1204          * not dir & dir & overwrite: remove empty or error, install
1205          * dir & dir & overwrite: fix md
1206          * dir & not dir & overwrite: remove and mkdir
1207          */
1208         int exist = (!(diffs & D_NOEXIST));
1209         int sametype = (!(diffs & D_TYPE));
1210         int mdsame = (!(diffs & D_MD));
1211         int hashsame = (!(diffs & D_HASH));
1212         int ohashsame = (!(diffs & D_OHASH));
1213         int isdir = (diffs & D_ISDIR);
1214         int eisdir = (diffs & D_EISDIR);
1215         int accept = conf->accept;
1216         int overwrite = conf->overwrite;
1217         int installing = (nitem.op == OP_NEW);
1218         update = (nitem.op == OP_UPDATE);
1219
1220         /* if a config file doesn't exist on disk, go ahead and do
1221          * whatever you were going to do, this logic here just
1222          * needs to determine if we should skip what we were going to do
1223          *
1224          * if the old item was a configuration item and the new one isn't, it
1225          * will have been saved earlier, so we can just go ahead.  so we only
1226          * test for an existing file, where the item is a configuration file
1227          */
1228         if (nitem.configuration && exist) {
1229                 if (!sametype && !conf->overwrite) {
1230                         return seterror(conf, "configuration file exists with different type: %s", nitem.dest);
1231                 }
1232
1233                 if (conf->accept) {
1234                         return 0;
1235                 }
1236
1237                 if (isdir && !mdsame) return 0;
1238                 if (!isdir && !ohashsame) return 0;
1239         }
1240
1241         if (update) {
1242                 if (!exist) {
1243                         /* warn, it should exist */
1244                         fprintf(stderr, "%s missing, installing", nitem.dest);
1245                         return install(conf, &nitem, 3);
1246                 }
1247
1248                 /* file exists in filesystem */
1249                 if (sametype) {
1250                         if (mdsame && hashsame) {
1251                                 /* warn, bug in logic.  This shouldn't occur,
1252                                  * because if there is nothing to do, it
1253                                  * shouldn't be listed as an update
1254                                  */
1255                                 /* could be an update.  We're checking against
1256                                  * what's actually on disk, not what was
1257                                  * expected to have been on disk.  So, if
1258                                  * the admin has modified the file, or if
1259                                  * it had been installed ignoring the user
1260                                  * and group, it might be correct on disk
1261                                  * but not as in the local database
1262                                  */
1263                                 /* TODO detect whether this a logic bug or
1264                                  * an on-disk difference
1265                                  */
1266 #if 0
1267                                 fprintf(stderr, "%s should not be an update\n", nitem.dest);
1268                                 fprintf(stderr, "old hash: %s\n", nitem.ohash);
1269                                 fprintf(stderr, "new hash: %s\n", nitem.hash);
1270                                 fprintf(stderr, "old mds: %s\n", nitem.omds);
1271                                 fprintf(stderr, "new mds: %s\n", nitem.mds);
1272 #endif
1273                                 /* do nothing */
1274                                 return 0;
1275                         }
1276                         if (!mdsame && hashsame) {
1277                                 /* fix md */
1278                                 return set_md(conf, &nitem);
1279                         }
1280                         if (mdsame && !hashsame) {
1281                                 /* install */
1282                                 return install(conf, &nitem, 3);
1283                         }
1284                         if (!mdsame && !hashsame) {
1285                                 /* install */
1286                                 return install(conf, &nitem, 3);
1287                         }
1288                 }
1289
1290                 /* file exists, and is not the same type */
1291
1292                 if (isdir && !eisdir) {
1293                         /* remove existing */
1294                         /* mkdir */
1295                         return install(conf, &nitem, 7);
1296                 }
1297                 if (!isdir && eisdir) {
1298                         /* remove dir, or error */
1299                         /* install */
1300                         return install(conf, &nitem, 11);
1301                 }
1302                 if (!isdir && !isdir) {
1303                         /* necessarily !sametype, sametype handled above */
1304                         /* remove existing */
1305                         /* install */
1306                         return install(conf, &nitem, 7);
1307                 }
1308                 /* error, should not be possible, assert(0)? */
1309                 fprintf(stderr,"impossible state: %s:%d\n", __func__, __LINE__);
1310         }
1311
1312         if (installing) {
1313                 if (!exist) {
1314                         return install(conf, &nitem, 3);
1315                 }
1316
1317                 /* file exists in filesystem */
1318                 if (sametype) {
1319                         if (mdsame && hashsame && (accept || overwrite)) {
1320                                 /* do nothing */
1321                                 if (conf->dryrun || conf->verbose) {
1322                                         fprintf(stderr, "accept %s: %s\n",
1323                                                         eisdir ? "directory" : "file", nitem.dest);
1324                                 }
1325                                 return 0;
1326                         }
1327
1328                         if (mdsame && isdir && conf->acceptdir) {
1329                                 return 0;
1330                         }
1331
1332                         if (!mdsame && isdir && conf->ignoredirmd) {
1333                                 /* TODO warn ignoring dir md */
1334                                 return 0;
1335                         }
1336
1337                         if (mdsame && hashsame && !(accept || overwrite)) {
1338                                 /* error */
1339                                 return seterror(conf, "file exists: %s", nitem.dest);
1340                         }
1341
1342                         if (mdsame && !hashsame && overwrite) {
1343                                 /* install */
1344                                 return install(conf, &nitem, eisdir ? 11 : 7);
1345                         }
1346
1347                         if (nitem.configuration && accept) {
1348                                 /* accept a changed config file */
1349                                 if (conf->dryrun || conf->verbose) {
1350                                         fprintf(stderr, "accept %smodified config %s: %s\n", (!mdsame || !hashsame) ? "" : "un", 
1351                                                         eisdir ? "directory" : "file", nitem.dest);
1352                                 }
1353                                 return 0;
1354                         }
1355
1356                         if (mdsame && !hashsame && !overwrite) {
1357                                 /* accept doesn't matter, since it's
1358                                  * not an acceptable file */
1359                                 /* error */
1360                                 if (nitem.ftype == 'l') {
1361                                         char link[1024];
1362                                         ssize_t lsize;
1363                                         lsize = readlink(nitem.dest, link, sizeof link);
1364                                         if (lsize == -1 || (size_t)lsize >= sizeof link) {
1365                                                 return seterror(conf, "%s (linkdiff): expecting %s -> %s, unable to read link", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link);
1366                                         } else {
1367                                                 link[lsize] = 0;
1368                                                 /* links must be different */
1369                                                 return seterror(conf, "%s (linkdiff): expecting %s -> %s, have -> %s", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link);
1370                                         }
1371                                 } else {
1372                                         return seterror(conf, "%s (hashdiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1373                                 }
1374                         }
1375                         if (!mdsame && hashsame && overwrite) {
1376                                 /* fix md */
1377                                 return set_md(conf, &nitem);
1378                         }
1379                         if (!mdsame && hashsame && !overwrite) {
1380                                 /* accept doesn't matter, since it's
1381                                  * not an acceptable file */
1382                                 /* error */
1383                                 return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1384                         }
1385                         if (!mdsame && !hashsame && overwrite) {
1386                                 /* install */
1387                                 return install(conf, &nitem, eisdir ? 11 : 7);
1388                         }
1389                         if (!mdsame && !hashsame && !overwrite) {
1390                                 /* accept doesn't matter, since it's
1391                                  * not an acceptable file */
1392                                 /* error */
1393                                 return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1394                         }
1395                         /* TODO error, should be impossible */
1396                         return seterror(conf, "impossible state reached");
1397                 }
1398
1399                 /* file exists, and is not the same type */
1400                 if (!overwrite) {
1401                         /* error */
1402                                 return seterror(conf, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest);
1403                 }
1404
1405                 /* not the same type, but ok to overwrite */
1406                 if (!eisdir) {
1407                         /* remove existing */
1408                         return install(conf, &nitem, 7);
1409                 }
1410
1411                 /* existing path is a directory */
1412                 if (isdir) {
1413                         /* fix md */
1414                         /* impossible, if isdir and eisdir, would
1415                          * be same type
1416                          * TODO
1417                          */
1418                         return set_md(conf, &nitem);
1419                 } else {
1420                         /* remove empty dir or error */
1421                         /* install */
1422                         return install(conf, &nitem, 11);
1423                 }
1424                 /* if we get here, we missed a case */
1425                 /* TODO error */
1426                 return seterror(conf, "impossible state 2 reached");
1427         }
1428
1429         /* TODO extra verbose print perms, mtime, etc, probably ls -l
1430          * format
1431          */ 
1432         if (conf->verbose) {
1433                 printf("%s\n", nitem.path);
1434         }
1435
1436         return 0;
1437 }
1438
1439 static void check_conflicts(struct config *conf, char *conflict_type,
1440                 int (callback)(void *, int, char **, char **)) {
1441         int rv;
1442         char *errmsg;
1443         sqlite3_str *s;
1444         char *sql;
1445
1446         s = sqlite3_str_new(conf->log->db);
1447         sqlite3_str_appendall(s, "select *, ");
1448         if (conf->rootdir) {
1449                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
1450         } else {
1451                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
1452         }
1453         sqlite3_str_appendall(s, " as dest from syncconflicts");
1454
1455         if (conflict_type) {
1456                 sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
1457         }
1458         if (conf->reverse) {
1459                 sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
1460         } else {
1461                 sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
1462
1463         }
1464
1465         sql = sqlite3_str_value(s);
1466
1467         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
1468
1469         sqlite3_str_finish(s);
1470
1471         if (rv) {
1472                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
1473                 if (errmsg) {
1474                         fprintf(stderr, "database error: %s\n", errmsg);
1475                         conf->errors++;
1476                 }
1477                 if (conf->log->error == 1) {
1478                         fprintf(stderr, "unable to allocate memory\n");
1479                 }
1480                 fprintf(stderr, "zpm_exec failure: %s\n",
1481                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
1482                 conf->errors++;
1483         }
1484         if (conf->log->errmsg) {
1485                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
1486         }
1487         if (conf->errors && conf->exitonerror) {
1488                 zpm_close(conf->log);
1489                 zpm_close(conf->src);
1490                 exit(EXIT_FAILURE);
1491         }
1492         /* TODO final report function in conf var */
1493 }
1494
1495 static void runstage(struct config *conf, char *stage,
1496                 int (callback)(void *, int, char **, char **)) {
1497         int rv;
1498         char *errmsg;
1499         sqlite3_str *s;
1500         char *sql;
1501
1502         s = sqlite3_str_new(conf->log->db);
1503         sqlite3_str_appendall(s, "select *, ");
1504         if (conf->rootdir) {
1505                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
1506         } else {
1507                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
1508         }
1509         sqlite3_str_appendall(s, " as dest from syncinfo");
1510
1511         if (stage) {
1512                 sqlite3_str_appendf(s," where op = %Q", stage);
1513         }
1514         if (conf->reverse) {
1515                 sqlite3_str_appendall(s," order by length(path) desc, path desc");
1516         }
1517
1518         sql = sqlite3_str_value(s);
1519
1520         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
1521
1522         sqlite3_str_finish(s);
1523
1524         if (rv) {
1525                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
1526                 if (errmsg) {
1527                         fprintf(stderr, "database error: %s\n", errmsg);
1528                         conf->errors++;
1529                 }
1530                 if (conf->log->error == 1) {
1531                         fprintf(stderr, "unable to allocate memory\n");
1532                 }
1533                 fprintf(stderr, "zpm_exec failure: %s\n",
1534                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
1535                 conf->errors++;
1536         }
1537 #if 0
1538         if (conf->log->errmsg) {
1539                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
1540         }
1541 #endif
1542         if (conf->errors && conf->exitonerror) {
1543                 zpm_close(conf->log);
1544                 zpm_close(conf->src);
1545                 exit(EXIT_FAILURE);
1546         }
1547         /* TODO final report function in conf var */
1548 }
1549
1550 static void count_ops(struct config *conf) {
1551         conf->ops_remove = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'remove'");
1552         conf->ops_update = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'update'");
1553         conf->ops_install = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'new'");
1554         conf->ops_total = conf->ops_remove + conf->ops_update + conf->ops_install;
1555 }
1556
1557 int main(int ac, char **av) {
1558         struct zpm localdb;
1559         struct zpm pkgdb;
1560         int opt;
1561         char *pkgdbfile = 0, *localdbfile = 0;
1562         char *s;
1563
1564         struct config conf = { 0 };
1565
1566         conf.errabort = 1;
1567         conf.errors = 0;
1568         conf.conflicts = 0;
1569         conf.verbose = 0;
1570         conf.dryrun = 0;
1571         conf.setuser = 1;
1572         conf.setgroup = 1;
1573         conf.log = 0;
1574         conf.src = 0;
1575         conf.rootdir = 0;
1576         conf.reverse = 0;
1577         conf.overwrite = 0;
1578         conf.accept = 0;
1579         conf.acceptdir = 1;
1580
1581         if (geteuid() != 0) {
1582                 conf.setuser = 0;
1583                 conf.setgroup = 0;
1584         }
1585
1586         localdbfile = ZPM_LOCAL_DB;
1587         if ((s = getenv("ZPMDB"))) {
1588                 /* TODO does this need to be copied ? */
1589                 localdbfile = s;
1590         }
1591
1592         if ((s = getenv("ZPM_ROOT_DIR"))) {
1593                 /* TODO does this need to be copied ? */
1594                 conf.rootdir = s;
1595         }
1596
1597         /*
1598          * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
1599          * -f 'package database', otherwise regular default of env
1600          *  ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
1601          * -R root of pkg, will just chdir there
1602          *
1603          *  args are pkgid triple, but will do a pkg find on the pkgdb
1604          */
1605
1606         while ((opt = getopt(ac, av, "f:d:c:nCR:vOAMDp")) != -1) {
1607                 switch (opt) {
1608                         case 'd': localdbfile = optarg; break;
1609                         case 'f': pkgdbfile = optarg; break;
1610                         case 'n': conf.dryrun = 1; break;
1611                         case 'v': conf.verbose++; break;
1612                         case 'C': conf.errabort = 0; break;
1613                         case 'R': conf.rootdir = optarg; break;
1614                         case 'N': conf.setuser = 0; conf.setgroup = 0; break;
1615                         case 'O': conf.overwrite = 1; break;
1616                         case 'A': conf.accept = 1; break;
1617                         case 'M': conf.ignoredirmd = 1;
1618                         case 'D': conf.acceptdir = 0;
1619                         case 'p': conf.progress++;
1620                         default:
1621                                   usage();
1622                                   exit(EXIT_FAILURE);
1623                                   break;
1624                 }
1625         }
1626
1627         /* verify root dir exists */
1628         if (conf.rootdir && !exists(conf.rootdir, NULL)) {
1629                 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
1630         }
1631
1632         if (!zpm_open(&localdb, localdbfile)) {
1633                 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
1634                 exit(EXIT_FAILURE);
1635         }
1636         conf.log = &localdb;
1637
1638         if (pkgdbfile) {
1639                 /* TODO open read-only */
1640                 if (!zpm_open(&pkgdb, pkgdbfile)) {
1641                         fprintf(stderr, "can't open src db %s\n", localdbfile);
1642                         exit(EXIT_FAILURE);
1643                 } else {
1644                         conf.src = &pkgdb;
1645                 }
1646         }
1647
1648         /* TODO find pkgid from arg */
1649
1650         /* TODO set conf var to finalize error reporting */
1651         if (conf.verbose) {
1652                 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
1653                                 conf.rootdir ? conf.rootdir : "/",
1654                                 localdbfile, pkgdbfile);
1655         }
1656
1657         conf.errors = 0;
1658         conf.exitonerror = 0;
1659         check_conflicts(&conf, NULL, report_conflicts);
1660
1661         if (conf.conflicts) {
1662                 fprintf(stderr, "%d conflicts reported, aborting sync\n",
1663                                 conf.conflicts);
1664                 conf.errors++;
1665         } else {
1666                 /* no point in running it if we're just going to
1667                  * overwrite everything
1668                  */
1669                 if (!conf.overwrite && !conf.accept && !conf.dryrun) {
1670                         runstage(&conf, "new", check_existing);
1671                 }
1672
1673                 if (conf.verbose) {
1674                         fprintf(stderr, "beginning %ssync\n", conf.dryrun ?
1675                                         "dryrun " : "");
1676                 }
1677
1678                 if (!conf.errors) {
1679                         handle_config_files(&conf);
1680                 }
1681
1682                 /* have to do the removes first otherwise
1683                  * old files may conflict with update file
1684                  * type changes
1685                  */
1686                 if (!conf.errors) {
1687                         conf.exitonerror = conf.dryrun ? 0 : 1;
1688                         conf.errabort = conf.dryrun ? 0 : 1;
1689                         count_ops(&conf);
1690                         fprintf(stderr, "file ops: %d\n", conf.ops_total);
1691                         if (conf.ops_remove > 0) {
1692                                 if (conf.verbose) {
1693                                         fprintf(stderr, "removing %d file%s\n", conf.ops_remove, conf.ops_remove > 1 ? "s" : "");
1694                                 }
1695                                 conf.reverse = 1;
1696                                 conf.ops_completed = 0;
1697                                 conf.ops_total = conf.ops_remove;
1698                                 runstage(&conf, "remove", remove_files);
1699                                 if (conf.verbose && conf.progress < 2) {
1700                                         fprintf(stderr, " done\n");
1701                                         fflush(stderr);
1702                                 }
1703                         }
1704
1705                         if (conf.ops_update > 0) {
1706                                 if (conf.verbose) {
1707                                         fprintf(stderr, "updating %d file%s\n", conf.ops_update, conf.ops_update > 1 ? "s" : "");
1708                                 }
1709                                 conf.reverse = 0;
1710                                 conf.ops_completed = 0;
1711                                 conf.ops_total = conf.ops_update;
1712                                 runstage(&conf, "update", install_files);
1713                                 if (conf.verbose && conf.progress < 2) {
1714                                         fprintf(stderr, " done\n");
1715                                         fflush(stderr);
1716                                 }
1717                         }
1718
1719                         if (conf.ops_install > 0) {
1720                                 if (conf.verbose) {
1721                                         fprintf(stderr, "installing %d file%s\n", conf.ops_install, conf.ops_install > 1 ? "s" : "");
1722                                 }
1723                                 conf.reverse = 0;
1724                                 conf.ops_completed = 0;
1725                                 conf.ops_total = conf.ops_install;
1726                                 runstage(&conf, "new", install_files);
1727                                 if (conf.verbose && conf.progress < 2) {
1728                                         fprintf(stderr, " done\n");
1729                                         fflush(stderr);
1730                                 }
1731                         }
1732                 }
1733         }
1734
1735         zpm_close(&localdb);
1736         zpm_close(conf.src);
1737         return conf.errors ? 1 : 0;
1738 }