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