]> pd.if.org Git - zpackage/blob - zpm-syncfs.c
add error checks for unlink
[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 > 1) {
371                         fprintf(stderr, "symlink %s -> %s\n", path, target);
372                 }
373                 if (symlink(target, path) == -1) {
374                         IERR("can't symlink");
375                 }
376         } else {
377                 fprintf(stderr, "unhandled filetype %c\n", ftype);
378         }
379
380         if (conf->setuser && conf->setgroup) {
381                 if (chown(dest, uid, gid) == -1) {
382                         IERR("can't chown");
383                 }
384         }
385
386         struct timespec times[2] = { 0 };
387         double mtime = strtod(COL("mtime"),NULL);
388
389         times[0].tv_nsec = UTIME_OMIT;
390         times[1].tv_sec = (time_t)llrint(floor(mtime));
391         times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000));
392
393         utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW);
394
395         if (conf->verbose) {
396                 printf("%s\n", path);
397         }
398
399         return 0;
400 }
401
402 static void runstage(struct config *conf, char *stage,
403                 int (callback)(void *, int, char **, char **)) {
404         int rv;
405         char *errmsg;
406         sqlite3_str *s;
407         char *sql;
408
409         s = sqlite3_str_new(conf->log->db);
410         sqlite3_str_appendall(s, "select *, ");
411         if (conf->rootdir) {
412                 sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir);
413         } else {
414                 sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))");
415         }
416         sqlite3_str_appendall(s, " as dest from install_status");
417
418         if (stage) {
419                 sqlite3_str_appendf(s," where op = %Q", stage);
420         }
421         if (conf->reverse) {
422                 sqlite3_str_appendall(s," order by length(path) desc, path desc");
423         }
424
425         sql = sqlite3_str_value(s);
426         if (conf->verbose > 2) {
427                 fprintf(stderr, "stage query: %s\n", sql);
428         }
429
430         rv = zpm_exec(conf->log, sql, callback, conf, &errmsg);
431
432         sqlite3_str_finish(s);
433
434         if (rv) {
435                 fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv));
436                 if (errmsg) {
437                         fprintf(stderr, "database error: %s\n", errmsg);
438                         conf->errors++;
439                 }
440                 if (conf->log->error == 1) {
441                         fprintf(stderr, "unable to allocate memory\n");
442                 }
443                 fprintf(stderr, "zpm_exec failure: %s\n",
444                                 conf->log->errmsg ? conf->log->errmsg : "unknown");
445                 conf->errors++;
446         }
447         if (conf->log->errmsg) {
448                 fprintf(stderr, "error: %s\n", conf->log->errmsg);
449         }
450         if (conf->errors && conf->exitonerror) {
451                 zpm_close(conf->log);
452                 zpm_close(conf->src);
453                 exit(EXIT_FAILURE);
454         }
455         /* TODO final report function in conf var */
456 }
457
458 int main(int ac, char **av){
459         struct zpm localdb;
460         struct zpm pkgdb;
461         int opt;
462         char *pkgdbfile = 0, *localdbfile = 0;
463         char *s;
464
465         struct config conf;
466
467         conf.errabort = 1;
468         conf.errors = 0;
469         conf.verbose = 0;
470         conf.dryrun = 0;
471         conf.setuser = 1;
472         conf.setgroup = 1;
473         conf.log = 0;
474         conf.src = 0;
475         conf.rootdir = 0;
476         conf.reverse = 0;
477
478         if (geteuid() != 0) {
479                 conf.setuser = 0;
480                 conf.setgroup = 0;
481         }
482
483         localdbfile = ZPM_LOCAL_DB;
484         if ((s = getenv("ZPMDB"))) {
485                 /* TODO does this need to be copied ? */
486                 localdbfile = s;
487         }
488
489         if ((s = getenv("ZPM_ROOT_DIR"))) {
490                 /* TODO does this need to be copied ? */
491                 conf.rootdir = s;
492         }
493
494         /*
495          * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die
496          * -f 'package database', otherwise regular default of env
497          *  ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found
498          * -R root of pkg, will just chdir there
499          *
500          *  args are pkgid triple, but will do a pkg find on the pkgdb
501          */
502
503         while ((opt = getopt(ac, av, "f:d:c:nCR:v")) != -1) {
504                 switch (opt) {
505                         case 'd': localdbfile = optarg; break;
506                         case 'f': pkgdbfile = optarg; break;
507                         case 'n': conf.dryrun = 1; break;
508                         case 'v': conf.verbose++; break;
509                         case 'C': conf.errabort = 0; break;
510                         case 'R': conf.rootdir = optarg; break;
511                         case 'N': conf.setuser = 0; conf.setgroup = 0; break;
512                         default:
513                                   usage();
514                                   exit(EXIT_FAILURE);
515                                   break;
516                 }
517         }
518
519         /* verify root dir exists */
520         if (conf.rootdir && !exists(conf.rootdir, NULL)) {
521                 fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir);
522         }
523
524         if (!zpm_open(&localdb, localdbfile)) {
525                 fprintf(stderr, "can't open zpm db %s\n", localdbfile);
526                 exit(EXIT_FAILURE);
527         }
528         conf.log = &localdb;
529
530         if (pkgdbfile) {
531                 if (!zpm_open(&pkgdb, pkgdbfile)) {
532                         fprintf(stderr, "can't open src db %s\n", localdbfile);
533                         exit(EXIT_FAILURE);
534                 } else {
535                         conf.src = &pkgdb;
536                 }
537         }
538
539         /* TODO find pkgid from arg */
540
541         /* TODO set conf var to finalize error reporting */
542         if (conf.verbose) {
543                 fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n",
544                                 conf.rootdir ? conf.rootdir : "/",
545                                 localdbfile, pkgdbfile);
546         }
547
548         conf.errors = 0;
549         conf.exitonerror = 0;
550         runstage(&conf, "conflict", report_conflicts);
551         runstage(&conf, "new", check_existing);
552
553         if (!conf.errors) {
554                 conf.exitonerror = 1;
555                 runstage(&conf, "new", install_files);
556                 runstage(&conf, "update", update_files);
557                 conf.reverse = 1;
558                 runstage(&conf, "remove", remove_files);
559                 conf.reverse = 0;
560         }
561
562         zpm_close(&localdb);
563         zpm_close(conf.src);
564         return conf.errors ? 1 : 0;
565 }