]> pd.if.org Git - zpackage/blob - zpm-syncfs.c
improve error reporting in syncfs
[zpackage] / zpm-syncfs.c
1 #define _POSIX_C_SOURCE 200809L
2
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <unistd.h>
8 #include <limits.h>
9 #include <errno.h>
10 #include <ctype.h>
11 #include <pwd.h>
12 #include <grp.h>
13 #include <math.h>
14 #include <stdarg.h>
15
16 /* needed for S_IFMT and AT_FDCWD */
17 #include <fcntl.h>
18
19 #include <string.h>
20
21 #include "sqlite3.h"
22 #include "zpm.h"
23
24 struct config {
25         struct zpm *log; /* logging db will be attached as "log" */
26         struct zpm *src;
27         char *dbfile;
28         char *rootdir;
29         int errabort, errors, verbose, dryrun, conflicts;
30         int setuser, setgroup;
31         int reverse, exitonerror;
32         int overwrite, absorb;
33 };
34
35 static void usage() {
36         printf("usage: zpm $scriptname [-fncC] args ...\n");
37 }
38
39 static int exists(char *path, mode_t *mode) {
40         struct stat st;
41
42         if (lstat(path, &st) == -1) {
43                 return 0;
44         }
45         if (mode) *mode = st.st_mode;
46         return 1;
47 }
48
49 /* TODO maintain a list of already created directories */
50 static int create_leading_dirs(char *path) {
51         char *delim, *s;
52         int ch = 0;
53         char pcopy[ZPM_PATH_MAX];
54         struct stat st;
55         
56         strcpy(pcopy, path);
57
58         delim = strrchr(pcopy, '/');
59         if (!delim) return 1; /* not an error, but no leading dirs */
60
61         /* cut off last component */
62         *delim = 0;
63
64         s = pcopy;
65         do {
66                 while (*s == '/') {
67                         s++;
68                 }
69
70                 delim = strchr(s, '/');
71                 if (delim) {
72                         ch = *delim;
73                         *delim = 0;
74                 }
75
76                 /* try to create the directory, if it exists
77                  * and is a directory or a symlink, that's ok
78                  */
79                 if (mkdir(pcopy, 0755) == -1) {
80                         switch (errno) {
81                                 case EEXIST:
82                                         if (lstat(pcopy, &st) == -1) {
83                                                 /* can't stat? */
84                                                 return 0;
85                                         }
86                                         switch (st.st_mode & S_IFMT) {
87                                                 case S_IFDIR:
88                                                 case S_IFLNK:
89                                                         break;
90                                                 default:
91                                                         return 0;
92                                         }
93                                         break;
94                                 default:
95                                         return 0;
96                         }
97                 }
98                 if (delim) {
99                         *delim = ch;
100                 }
101                 s = delim;
102         } while (delim);
103         
104         return 1;
105 }
106
107 char *column(char *col, int ncols, char **vals, char **cols) {
108         int i = 0;
109         char *val = NULL;
110
111         for (i=0; i < ncols; i++) {
112 //              fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]);
113                 
114                 if (!strcmp(col, cols[i])) {
115                         val = vals[i];
116                         break;
117                 }
118         }
119         return val;
120 }
121
122 #define COL(x) column(x, ncols, vals, cols)
123 #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0)
124
125 static int seterror(struct config *conf, char *msgfmt, ...) {
126         char msg[1024];
127         va_list ap;
128
129         conf->errors++;
130
131         va_start(ap, msgfmt);
132         vsnprintf(msg, sizeof msg, msgfmt, ap);
133         va_end(ap);
134
135         msg[1023] = 0;
136         if (conf->log->errmsg) {
137                 free(conf->log->errmsg);
138         }
139
140         conf->log->errmsg = strdup(msg);
141
142         if (conf->verbose) {
143                 fprintf(stderr, "%s\n", msg);
144         }
145
146         return conf->errabort;
147 }
148
149 static int setsyserr(struct config *conf, char *msgfmt, ...) {
150         char msg[1024];
151         va_list ap;
152         int printed;
153
154         conf->errors++;
155
156         va_start(ap, msgfmt);
157         printed = vsnprintf(msg, sizeof msg, msgfmt, ap);
158         va_end(ap);
159
160         if (printed < 1) {
161                 /* nothing we can really do */
162                 return conf->errabort;
163         }
164
165         if ((size_t)printed < sizeof msg) {
166                 snprintf(msg+printed, sizeof msg - printed, ": %s",
167                                 strerror(errno));
168         }
169
170         msg[1023] = 0;
171         if (conf->log->errmsg) {
172                 free(conf->log->errmsg);
173         }
174
175         conf->log->errmsg = strdup(msg);
176
177         if (conf->verbose) {
178                 fprintf(stderr, "%s\n", msg);
179         }
180
181         return conf->errabort;
182 }
183
184 #define IERR(x) return seterror(conf, x)
185
186 static char *ops[] = { "new", "remove", "update", 0 };
187
188 static int getop(char *opstr) {
189         int i;
190
191         if (!opstr) return 0;
192         for (i=0;ops[i];i++) {
193                 if (!strcmp(opstr, ops[i])) {
194                         return i+1;
195                 }
196         }
197         return 0;
198 }
199
200 static int report_conflicts(void *f, int ncols, char **vals, char **cols) {
201         struct config *conf = f;
202         char *path, *hash, *pkg, *conflict_type, *mds;
203
204         pkg = COL("pkgid");
205         path = COL("path");
206         conflict_type = COL("conflict");
207         if (!strcmp(conflict_type, "hash")) {
208                 hash = COL("hash");
209                 fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n",
210                                 pkg, path, hash);
211         } else
212         if (!strcmp(conflict_type, "md")) {
213                 mds = COL("mds");
214                 fprintf(stderr, "md conflict: package %s path %s md %s\n",
215                                 pkg, path, mds);
216         } else {
217                 fprintf(stderr, "%s conflict: package %s path %s\n",
218                                 conflict_type, pkg, path);
219         }
220
221         conf->conflicts++;
222         return 0;
223 }
224
225 static int check_existing(void *f, int ncols, char **vals, char **cols) {
226         struct config *conf = f;
227         char *path;
228         struct stat st;
229
230         path = COL("dest");
231         if (!path) IERR("can't check for existing");
232
233         if (conf->dryrun) {
234                 printf("checkfor %s\n", path);
235                 return 0;
236         }
237
238         if (conf->verbose) {
239                 fprintf(stderr, "check for existing %s\n", path);
240         }
241
242         if (lstat(path, &st) == 0) {
243                 fprintf(stderr, "%s exists \n", path);
244                 conf->errors++;
245         } else {
246                 switch(errno) {
247                         /* not an error, file shouldn't exist*/
248                         case ENOENT: break;
249                         default:
250                                 fprintf(stderr, "unable to check %s: %s\n",
251                                                 path, strerror(errno));
252                                 conf->errors++;
253                                 break;
254                 }
255         }
256         return 0;
257 }
258
259 static int update_files(void *f, int ncols, char **vals, char **cols) {
260         struct config *conf = f;
261         char *pkg;
262         char *path;
263         char *dest;
264
265         pkg = COL("pkgid");
266         if (!pkg) IERR("can't get pkgid");
267         path = COL("path");
268         if (!path) IERR("can't get path");
269         dest = COL("dest");
270         if (!dest) IERR("no file dest");
271
272         /* hash is different, so no different than install,
273          * but we don't need to create leading directories
274          */
275
276         if (conf->dryrun) {
277                 fprintf(stderr, "update %s\n", dest);
278                 return 0;
279         }
280
281         fprintf(stderr, "update not implemented: %s", dest);
282         conf->errors++;
283
284         return 0;
285 }
286
287 static int remove_files(void *f, int ncols, char **vals, char **cols) {
288         struct config *conf = f;
289         char *dest;
290         struct stat st;
291
292         dest = COL("dest");
293         if (!dest) IERR("no file dest");
294
295         if (conf->dryrun) {
296                 char *ftype = COL("filetype");
297                 int t = *ftype;
298
299                 switch(t) {
300                         case 'd': printf("rmdir %s\n", dest); break;
301                         default: printf("unlink %s\n", dest); break;
302                 }
303                 return 0;
304         }
305
306         if (lstat(dest, &st) == -1) {
307                 IERR("can't stat");
308         }
309
310         if (S_ISDIR(st.st_mode)) {
311                 if (conf->verbose) {
312                         fprintf(stderr, "rmdir(%s)\n", dest);
313                 }
314                 rmdir(dest);
315         } else if (S_ISREG(st.st_mode)) {
316                 /* TODO conf to import before removal */
317                 if (conf->verbose) {
318                         fprintf(stderr, "unlink(%s)\n", dest);
319                 }
320                 if (unlink(dest) == -1) {
321                         IERR("can't unlink");
322                 }
323         } else {
324                 if (unlink(dest) == -1) {
325                         IERR("can't unlink");
326                 }
327         }
328         
329         return 0;
330 }
331
332 #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__)
333
334 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
335 /* 4 == ftype different */
336 /* 8 = hash different when both are regular files */
337 static unsigned int file_compare(char *path, char *hash, int ftype, uid_t uid,
338                 gid_t gid, int perms) {
339         struct stat st;
340         int etype = 0, stat_type;
341         char ehash[ZPM_HASH_STRLEN+1];
342         unsigned int diff = 0;
343
344         switch (ftype) {
345                 case 'd': etype = S_IFDIR; break;
346                 case 'r': etype = S_IFREG; break;
347                 default: etype = 0; break;
348         }
349
350         errno = 0;
351         /* new file, so check type, hash, etc */
352         if (lstat(path, &st) == 0) {
353                 stat_type = st.st_mode & S_IFMT;
354                 if (stat_type != etype) {
355                         diff &= 4;
356                 }
357                 if (stat_type == S_IFDIR && etype != S_IFDIR) {
358                         diff &= 2;
359                 }
360                 if (hash && etype == S_IFREG && stat_type == S_IFREG) {
361                         zpm_hash(path, ehash, 0);
362                         if (strcmp(hash, ehash) != 0) {
363                                 diff &= 8;
364                         }
365                 }
366                 if (uid != st.st_uid) {
367                         diff &= 16;
368                 }
369                 if (gid != st.st_gid) {
370                         diff &= 32;
371                 }
372                 if (perms != (st.st_mode & 07777)) {
373                         diff &= 64;
374                 }
375         } else {
376                 switch(errno) {
377                         case ENOENT: diff &= 1; break;
378                         default: diff &= 128; break;
379                 }
380         }
381
382         return diff;
383 }
384
385 static int install_files(void *f, int ncols, char **vals, char **cols) {
386         struct config *conf = f;
387         struct passwd *pw;
388         struct group *gr;
389
390         mode_t mode = 0;
391         char *path, *dest;
392         uid_t uid;
393         gid_t gid;
394         int ftype;
395         char *val;
396         char *hash = 0;
397         char *opstr;
398         int op = 0;
399
400         /* TODO put the result row in a hash table.  May not actually
401          * be faster
402          */
403         opstr = COL("op");
404         op = getop(opstr);
405         if (op == 0) IERR("invalid operation");
406
407         /* TODO config to dishonor setuid/setgid */
408         path = COL("path");
409         if (!path) IERR("no file path");
410         if (strlen(path) == 0) {
411                 IERR("zero length path not allowed");
412         }
413         dest = COL("dest");
414         if (!dest) IERR("no file dest");
415         if (strlen(dest) == 0) {
416                 IERR("zero length dest not allowed");
417         }
418
419         val = COL("mode");
420
421         if (!val) IERR("can't determine mode");
422         mode = strtoul(val, NULL, 8);
423
424         val = COL("filetype");
425         if (!val || strlen(val) == 0) {
426                 IERR("can't determine file type");
427         }
428         ftype = *val;
429
430         if (ftype == 'r') {
431                 hash = COL("hash");
432                 if (!hash) IERR("can't get hash");
433         }
434
435         if (conf->verbose) {
436                 fprintf(stderr, "installing '%c' %s\n", ftype, dest);
437         }
438
439         uid = getuid();
440         gid = getgid();
441
442         if (conf->setuser) {
443                 val = COL("username");
444                 if (!val) IERR("no username");
445                 pw = getpwnam(val);
446                 if (!pw) IERR("no passwd entry");
447                 uid = pw->pw_uid;
448         }
449         if (conf->setgroup) {
450                 val = COL("groupname");
451                 if (!val) IERR("no groupname");
452                 gr = getgrnam(val);
453                 if (!gr) IERR("no group entry");
454                 gid = gr->gr_gid;
455         }
456
457         if (conf->dryrun) {
458                 //printf("cld %s\n", path);
459                 printf("new %c%o %d:%d %s -> %s\n", ftype, mode, uid, gid, path, dest);
460                 return 0;
461         }
462
463         /* TODO should these be owned by the path owner if they don't exist? */
464         /* probably, though they then belong to the package, sort of */
465         if (!create_leading_dirs(dest)) {
466                 return setsyserr(conf, "unable to create leading directories for %s\n", dest);
467         }
468
469         unsigned int diffs = file_compare(dest, hash, ftype, uid, gid, mode);
470
471         /* 1 = file doesn't exist, 2 = file is a directory, target isn't */
472         /* 4 == ftype different */
473         /* 8 = hash different when both are regular files */
474         if (diffs == 128) {
475                 return seterror(conf, "can't check %s", dest);
476         }
477
478         if (diffs > 1) {
479                 return seterror(conf, "absorb and overwrite not implemented");
480         }
481
482         if (ftype == 'd') {
483                 if (mkdir(dest, mode) == -1) {
484                         return setsyserr(conf, "can't create directory %s",
485                                         dest);
486                 }
487         } else if (ftype == 'r') {
488                 struct zpm *source;
489                 source = conf->src ? conf->src : conf->log;
490
491                 if (conf->verbose > 1) {
492                         fprintf(stderr, "extracting %8.8s to %s with mode %o\n",
493                                         hash, dest, mode);
494                 }
495
496                 if (!zpm_extract(source, hash, dest, mode)) {
497                         IERR("can't extract file");
498                 }
499         } else if (ftype == 'l') {
500                 char *target = COL("target");
501                 if (!target) {
502                         return seterror(conf, "no target for symlink %s\n",
503                                         path);
504                 }
505
506                 if (strlen(target) == 0) {
507                         IERR("zero length symlink not allowed");
508                 }
509
510                 if (conf->verbose > 0) {
511                         fprintf(stderr, "symlink %s -> %s\n", dest, target);
512                 }
513                 if (symlink(target, dest) == -1) {
514                         return setsyserr(conf, "symlink failed");
515                 }
516         } else {
517                 fprintf(stderr, "unhandled filetype %c\n", ftype);
518         }
519
520         if (conf->setuser && conf->setgroup) {
521                 if (chown(dest, uid, gid) == -1) {
522                         return setsyserr(conf, "can't chown %s", dest);
523                 }
524         }
525
526         struct timespec times[2] = { {0}, {0} };
527         double mtime = strtod(COL("mtime"),NULL);
528
529         times[0].tv_nsec = UTIME_OMIT;
530         times[1].tv_sec = (time_t)llrint(floor(mtime));
531         times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
532
533         utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW);
534
535         if (conf->verbose) {
536                 printf("%s\n", path);
537         }
538
539         return 0;
540 }
541
542 static void check_conflicts(struct config *conf, char *conflict_type,
543                 int (callback)(void *, int, char **, char **)) {
544         int rv;
545         char *errmsg;
546         sqlite3_str *s;
547         char *sql;
548
549         s = sqlite3_str_new(conf->log->db);
550         sqlite3_str_appendall(s, "select *, ");
551         if (conf->rootdir) {
552                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
553         } else {
554                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
555         }
556         sqlite3_str_appendall(s, " as dest from syncconflicts");
557
558         if (conflict_type) {
559                 sqlite3_str_appendf(s," where conflict = %Q", conflict_type);
560         }
561         if (conf->reverse) {
562                 sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc");
563         } else {
564                 sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict");
565
566         }
567
568         sql = sqlite3_str_value(s);
569         if (conf->verbose > 2) {
570                 fprintf(stderr, "stage query: %s\n", sql);
571         }
572
573         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
574
575         sqlite3_str_finish(s);
576
577         if (rv) {
578                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
579                 if (errmsg) {
580                         fprintf(stderr, "database error: %s\n", errmsg);
581                         conf->errors++;
582                 }
583                 if (conf->log->error == 1) {
584                         fprintf(stderr, "unable to allocate memory\n");
585                 }
586                 fprintf(stderr, "zpm_exec failure: %s\n",
587                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
588                 conf->errors++;
589         }
590         if (conf->log->errmsg) {
591                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
592         }
593         if (conf->errors && conf->exitonerror) {
594                 zpm_close(conf->log);
595                 zpm_close(conf->src);
596                 exit(EXIT_FAILURE);
597         }
598         /* TODO final report function in conf var */
599 }
600
601 static void runstage(struct config *conf, char *stage,
602                 int (callback)(void *, int, char **, char **)) {
603         int rv;
604         char *errmsg;
605         sqlite3_str *s;
606         char *sql;
607
608         s = sqlite3_str_new(conf->log->db);
609         sqlite3_str_appendall(s, "select *, ");
610         if (conf->rootdir) {
611                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
612         } else {
613                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
614         }
615         sqlite3_str_appendall(s, " as dest from syncinfo");
616
617         if (stage) {
618                 sqlite3_str_appendf(s," where op = %Q", stage);
619         }
620         if (conf->reverse) {
621                 sqlite3_str_appendall(s," order by length(path) desc, path desc");
622         }
623
624         sql = sqlite3_str_value(s);
625         if (conf->verbose > 2) {
626                 fprintf(stderr, "stage query: %s\n", sql);
627         }
628
629         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
630
631         sqlite3_str_finish(s);
632
633         if (rv) {
634                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
635                 if (errmsg) {
636                         fprintf(stderr, "database error: %s\n", errmsg);
637                         conf->errors++;
638                 }
639                 if (conf->log->error == 1) {
640                         fprintf(stderr, "unable to allocate memory\n");
641                 }
642                 fprintf(stderr, "zpm_exec failure: %s\n",
643                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
644                 conf->errors++;
645         }
646         if (conf->log->errmsg) {
647                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
648         }
649         if (conf->errors && conf->exitonerror) {
650                 zpm_close(conf->log);
651                 zpm_close(conf->src);
652                 exit(EXIT_FAILURE);
653         }
654         /* TODO final report function in conf var */
655 }
656
657 int main(int ac, char **av){
658         struct zpm localdb;
659         struct zpm pkgdb;
660         int opt;
661         char *pkgdbfile = 0, *localdbfile = 0;
662         char *s;
663
664         struct config conf;
665
666         conf.errabort = 1;
667         conf.errors = 0;
668         conf.conflicts = 0;
669         conf.verbose = 0;
670         conf.dryrun = 0;
671         conf.setuser = 1;
672         conf.setgroup = 1;
673         conf.log = 0;
674         conf.src = 0;
675         conf.rootdir = 0;
676         conf.reverse = 0;
677         conf.overwrite = 0;
678         conf.absorb = 0;
679
680         if (geteuid() != 0) {
681                 conf.setuser = 0;
682                 conf.setgroup = 0;
683         }
684
685         localdbfile = ZPM_LOCAL_DB;
686         if ((s = getenv("ZPMDB"))) {
687                 /* TODO does this need to be copied ? */
688                 localdbfile = s;
689         }
690
691         if ((s = getenv("ZPM_ROOT_DIR"))) {
692                 /* TODO does this need to be copied ? */
693                 conf.rootdir = s;
694         }
695
696         /*
697          * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
698          * -f 'package database', otherwise regular default of env
699          *  ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
700          * -R root of pkg, will just chdir there
701          *
702          *  args are pkgid triple, but will do a pkg find on the pkgdb
703          */
704
705         while ((opt = getopt(ac, av, "f:d:c:nCR:vOA")) != -1) {
706                 switch (opt) {
707                         case 'd': localdbfile = optarg; break;
708                         case 'f': pkgdbfile = optarg; break;
709                         case 'n': conf.dryrun = 1; break;
710                         case 'v': conf.verbose++; break;
711                         case 'C': conf.errabort = 0; break;
712                         case 'R': conf.rootdir = optarg; break;
713                         case 'N': conf.setuser = 0; conf.setgroup = 0; break;
714                         case 'O': conf.overwrite = 1; break;
715                         case 'A': conf.absorb = 1; break;
716                         default:
717                                   usage();
718                                   exit(EXIT_FAILURE);
719                                   break;
720                 }
721         }
722
723         /* verify root dir exists */
724         if (conf.rootdir && !exists(conf.rootdir, NULL)) {
725                 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
726         }
727
728         if (!zpm_open(&localdb, localdbfile)) {
729                 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
730                 exit(EXIT_FAILURE);
731         }
732         conf.log = &localdb;
733
734         if (pkgdbfile) {
735                 if (!zpm_open(&pkgdb, pkgdbfile)) {
736                         fprintf(stderr, "can't open src db %s\n", localdbfile);
737                         exit(EXIT_FAILURE);
738                 } else {
739                         conf.src = &pkgdb;
740                 }
741         }
742
743         /* TODO find pkgid from arg */
744
745         /* TODO set conf var to finalize error reporting */
746         if (conf.verbose) {
747                 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
748                                 conf.rootdir ? conf.rootdir : "/",
749                                 localdbfile, pkgdbfile);
750         }
751
752         conf.errors = 0;
753         conf.exitonerror = 0;
754         check_conflicts(&conf, NULL, report_conflicts);
755         if (conf.conflicts) {
756                 fprintf(stderr, "%d conflicts reported, aborting sync\n",
757                                 conf.conflicts);
758                 conf.errors++;
759         } else {
760                 /* no point in running it if we're just going to
761                  * overwrite everything
762                  */
763                 if (conf.overwrite == 0 || conf.absorb == 0) {
764                         runstage(&conf, "new", check_existing);
765                 }
766
767                 if (!conf.errors) {
768                         conf.exitonerror = 1;
769                         runstage(&conf, "new", install_files);
770                         runstage(&conf, "update", update_files);
771                         conf.reverse = 1;
772                         runstage(&conf, "remove", remove_files);
773                         conf.reverse = 0;
774                 }
775         }
776
777         zpm_close(&localdb);
778         zpm_close(conf.src);
779         return conf.errors ? 1 : 0;
780 }