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