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