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