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