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