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