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