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