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