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