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