]> pd.if.org Git - zpackage/blob - zpm-syncfs.c
rename pkgfiles to 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
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;
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;
141         char *pkg;
142
143         pkg = COL("pkgid");
144         path = COL("path");
145
146         conf->errors++;
147         fprintf(stderr, "%s owned by %s\n", path, pkg);
148         return 0;
149 }
150
151 static int check_existing(void *f, int ncols, char **vals, char **cols) {
152         struct config *conf = f;
153         char *path;
154         struct stat st;
155
156         path = COL("dest");
157         if (!path) IERR("can't check for existing");
158
159         if (conf->dryrun) {
160                 printf("checkfor %s\n", path);
161                 return 0;
162         }
163
164         if (conf->verbose) {
165                 fprintf(stderr, "check for existing %s\n", path);
166         }
167
168         if (lstat(path, &st) == 0) {
169                 fprintf(stderr, "%s exists \n", path);
170                 conf->errors++;
171         } else {
172                 switch(errno) {
173                         /* not an error, file shouldn't exist*/
174                         case ENOENT: break;
175                         default:
176                                 fprintf(stderr, "unable to check %s: %s\n",
177                                                 path, strerror(errno));
178                                 conf->errors++;
179                                 break;
180                 }
181         }
182         return 0;
183 }
184
185 static int update_files(void *f, int ncols, char **vals, char **cols) {
186         struct config *conf = f;
187         char *pkg;
188         char *path;
189         char *dest;
190
191         pkg = COL("pkgid");
192         if (!pkg) IERR("can't get pkgid");
193         path = COL("path");
194         if (!path) IERR("can't get path");
195         dest = COL("dest");
196         if (!dest) IERR("no file dest");
197
198         /* hash is different, so no different than install,
199          * but we don't need to create leading directories
200          */
201
202         if (conf->dryrun) {
203                 fprintf(stderr, "update %s\n", dest);
204                 return 0;
205         }
206
207         fprintf(stderr, "update not implemented: %s", dest);
208         conf->errors++;
209
210         return 0;
211 }
212
213 static int remove_files(void *f, int ncols, char **vals, char **cols) {
214         struct config *conf = f;
215         char *dest;
216         struct stat st;
217
218         dest = COL("dest");
219         if (!dest) IERR("no file dest");
220
221         if (conf->dryrun) {
222                 char *ftype = COL("filetype");
223                 int t = *ftype;
224
225                 switch(t) {
226                         case 'd': printf("rmdir %s\n", dest); break;
227                         default: printf("unlink %s\n", dest); break;
228                 }
229                 return 0;
230         }
231
232         if (lstat(dest, &st) == -1) {
233                 IERR("can't stat");
234         }
235
236         if (S_ISDIR(st.st_mode)) {
237                 fprintf(stderr, "rmdir %s\n", dest);
238                 rmdir(dest);
239         } else if (S_ISREG(st.st_mode)) {
240                 /* TODO conf to import before removal */
241                 if (conf->verbose) {
242                         fprintf(stderr, "unlink(%s)\n", dest);
243                 }
244                 unlink(dest);
245         } else {
246                 unlink(dest);
247         }
248         
249         return 0;
250 }
251
252 static int install_files(void *f, int ncols, char **vals, char **cols) {
253         struct config *conf = f;
254         struct passwd *pw;
255         struct group *gr;
256
257         mode_t mode = 0;
258         char *path, *dest;
259         uid_t uid;
260         gid_t gid;
261         int ftype;
262         char *val;
263         char *hash = 0;
264         char *opstr;
265         int op = 0;
266
267         /* TODO put the result row in a hash table.  May not actually
268          * be faster
269          */
270         opstr = COL("op");
271         op = getop(opstr);
272         if (op == 0) IERR("invalid operation");
273
274         /* TODO config to dishonor setuid/setgid */
275         path = COL("path");
276         if (!path) IERR("no file path");
277         if (strlen(path) == 0) {
278                 IERR("zero length path not allowed");
279         }
280         dest = COL("dest");
281         if (!dest) IERR("no file dest");
282         if (strlen(dest) == 0) {
283                 IERR("zero length dest not allowed");
284         }
285
286         val = COL("mode");
287
288         if (!val) IERR("can't determine mode");
289         mode = strtoul(val, NULL, 8);
290
291         val = COL("filetype");
292         if (!val || strlen(val) == 0) {
293                 IERR("can't determine file type");
294         }
295         ftype = *val;
296
297         if (ftype == 'r') {
298                 hash = COL("hash");
299                 if (!hash) IERR("can't get hash");
300         }
301
302         if (conf->verbose) {
303                 fprintf(stderr, "installing '%c' %s\n", ftype, dest);
304         }
305
306         uid = getuid();
307         gid = getgid();
308
309         if (conf->setuser) {
310                 val = COL("username");
311                 if (!val) IERR("no username");
312                 pw = getpwnam(val);
313                 if (!pw) IERR("no passwd entry");
314                 uid = pw->pw_uid;
315         }
316         if (conf->setgroup) {
317                 val = COL("groupname");
318                 if (!val) IERR("no groupname");
319                 gr = getgrnam(val);
320                 if (!gr) IERR("no group entry");
321                 gid = gr->gr_gid;
322         }
323
324         if (conf->dryrun) {
325                 //printf("cld %s\n", path);
326                 printf("new %c%o %d:%d %s -> %s\n", ftype, mode, uid, gid, path, dest);
327                 return 0;
328         }
329
330         /* TODO should these be owned by the path owner if they don't exist? */
331         /* probably, though they then belong to the package, sort of */
332         if (!create_leading_dirs(dest)) {
333                 fprintf(stderr, "unable to create leading directories for %s\n",
334                                 dest);
335                 IERR("cld failure");
336         }
337
338         if (ftype == 'd') {
339                 if (mkdir(dest, mode) == -1) {
340                         IERR("can't mkdir");
341                 }
342         } else if (ftype == 'r') {
343                 struct zpm *source;
344                 source = conf->src ? conf->src : conf->log;
345                 if (conf->verbose > 1) {
346                         fprintf(stderr, "extracting %8.8s to %s with mode %o\n",
347                                         hash, dest, mode);
348                 }
349                 if (!zpm_extract(source, hash, dest, mode)) {
350                         IERR("can't extract file");
351                 }
352         } else if (ftype == 'l') {
353                 char *target = COL("target");
354                 if (!target) {
355                         fprintf(stderr, "no target for symlink %s\n", path);
356                         conf->errors++;
357                         return conf->errabort;
358                 }
359
360                 if (strlen(target) == 0) {
361                         IERR("zero length symlink not allowed");
362                 }
363
364                 if (conf->verbose > 1) {
365                         fprintf(stderr, "symlink %s -> %s\n", path, target);
366                 }
367                 if (symlink(target, path) == -1) {
368                         IERR("can't symlink");
369                 }
370         } else {
371                 fprintf(stderr, "unhandled filetype %c\n", ftype);
372         }
373
374         if (conf->setuser && conf->setgroup) {
375                 if (chown(dest, uid, gid) == -1) {
376                         IERR("can't chown");
377                 }
378         }
379
380         struct timespec times[2] = { 0 };
381         double mtime = strtod(COL("mtime"),NULL);
382
383         times[0].tv_nsec = UTIME_OMIT;
384         times[1].tv_sec = (time_t)llrint(floor(mtime));
385         times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
386
387         utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW);
388
389         if (conf->verbose) {
390                 printf("%s\n", path);
391         }
392
393         return 0;
394 }
395
396 static void runstage(struct config *conf, char *stage,
397                 int (callback)(void *, int, char **, char **)) {
398         int rv;
399         char *errmsg;
400         sqlite3_str *s;
401         char *sql;
402
403         s = sqlite3_str_new(conf->log->db);
404         sqlite3_str_appendall(s, "select *, ");
405         if (conf->rootdir) {
406                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
407         } else {
408                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
409         }
410         sqlite3_str_appendall(s, " as dest from install_status");
411
412         if (stage) {
413                 sqlite3_str_appendf(s," where op = %Q", stage);
414         }
415         if (conf->reverse) {
416                 sqlite3_str_appendall(s," order by length(path) desc, path desc");
417         }
418
419         sql = sqlite3_str_value(s);
420         if (conf->verbose > 2) {
421                 fprintf(stderr, "stage query: %s\n", sql);
422         }
423
424         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
425
426         sqlite3_str_finish(s);
427
428         if (rv) {
429                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
430                 if (errmsg) {
431                         fprintf(stderr, "database error: %s\n", errmsg);
432                         conf->errors++;
433                 }
434                 if (conf->log->error == 1) {
435                         fprintf(stderr, "unable to allocate memory\n");
436                 }
437                 fprintf(stderr, "zpm_exec failure: %s\n",
438                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
439                 conf->errors++;
440         }
441         if (conf->log->errmsg) {
442                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
443         }
444         if (conf->errors && conf->exitonerror) {
445                 zpm_close(conf->log);
446                 zpm_close(conf->src);
447                 exit(EXIT_FAILURE);
448         }
449         /* TODO final report function in conf var */
450 }
451
452 int main(int ac, char **av){
453         struct zpm localdb;
454         struct zpm pkgdb;
455         int opt;
456         char *pkgdbfile = 0, *localdbfile = 0;
457         char *s;
458
459         struct config conf;
460
461         conf.errabort = 1;
462         conf.errors = 0;
463         conf.verbose = 0;
464         conf.dryrun = 0;
465         conf.setuser = 1;
466         conf.setgroup = 1;
467         conf.log = 0;
468         conf.src = 0;
469         conf.rootdir = 0;
470         conf.reverse = 0;
471
472         if (geteuid() != 0) {
473                 conf.setuser = 0;
474                 conf.setgroup = 0;
475         }
476
477         localdbfile = ZPM_LOCAL_DB;
478         if ((s = getenv("ZPMDB"))) {
479                 /* TODO does this need to be copied ? */
480                 localdbfile = s;
481         }
482
483         if ((s = getenv("ZPM_ROOT_DIR"))) {
484                 /* TODO does this need to be copied ? */
485                 conf.rootdir = s;
486         }
487
488         /*
489          * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
490          * -f 'package database', otherwise regular default of env
491          *  ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
492          * -R root of pkg, will just chdir there
493          *
494          *  args are pkgid triple, but will do a pkg find on the pkgdb
495          */
496
497         while ((opt = getopt(ac, av, "f:d:c:nCR:v")) != -1) {
498                 switch (opt) {
499                         case 'd': localdbfile = optarg; break;
500                         case 'f': pkgdbfile = optarg; break;
501                         case 'n': conf.dryrun = 1; break;
502                         case 'v': conf.verbose++; break;
503                         case 'C': conf.errabort = 0; break;
504                         case 'R': conf.rootdir = optarg; break;
505                         case 'N': conf.setuser = 0; conf.setgroup = 0; break;
506                         default:
507                                   usage();
508                                   exit(EXIT_FAILURE);
509                                   break;
510                 }
511         }
512
513         /* verify root dir exists */
514         if (conf.rootdir && !exists(conf.rootdir, NULL)) {
515                 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
516         }
517
518         if (!zpm_open(&localdb, localdbfile)) {
519                 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
520                 exit(EXIT_FAILURE);
521         }
522         conf.log = &localdb;
523
524         if (pkgdbfile) {
525                 if (!zpm_open(&pkgdb, pkgdbfile)) {
526                         fprintf(stderr, "can't open src db %s\n", localdbfile);
527                         exit(EXIT_FAILURE);
528                 } else {
529                         conf.src = &pkgdb;
530                 }
531         }
532
533         /* TODO find pkgid from arg */
534
535         /* TODO set conf var to finalize error reporting */
536         if (conf.verbose) {
537                 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
538                                 conf.rootdir ? conf.rootdir : "/",
539                                 localdbfile, pkgdbfile);
540         }
541
542         conf.errors = 0;
543         conf.exitonerror = 0;
544         runstage(&conf, "conflict", report_conflicts);
545         runstage(&conf, "new", check_existing);
546
547         if (!conf.errors) {
548                 conf.exitonerror = 1;
549                 runstage(&conf, "new", install_files);
550                 runstage(&conf, "update", update_files);
551                 conf.reverse = 1;
552                 runstage(&conf, "remove", remove_files);
553                 conf.reverse = 0;
554         }
555
556         zpm_close(&localdb);
557         zpm_close(conf.src);
558         return conf.errors ? 1 : 0;
559 }