#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include /* needed for S_IFMT and AT_FDCWD */ #include #include #include "sqlite3.h" #include "zpm.h" struct config { struct zpm *log; /* logging db will be attached as "log" */ struct zpm *src; char *dbfile; char *rootdir; int errabort, errors, verbose, dryrun; int setuser, setgroup; int reverse, exitonerror; }; static void usage() { printf("usage: zpm $scriptname [-fncC] args ...\n"); } static int exists(char *path, mode_t *mode) { struct stat st; if (lstat(path, &st) == -1) { return 0; } if (mode) *mode = st.st_mode; return 1; } /* TODO maintain a list of already created directories */ static int create_leading_dirs(char *path) { char *delim, *s; int ch = 0; char pcopy[ZPM_PATH_MAX]; struct stat st; strcpy(pcopy, path); delim = strrchr(pcopy, '/'); if (!delim) return 1; /* not an error, but no leading dirs */ /* cut off last component */ *delim = 0; s = pcopy; do { while (*s == '/') { s++; } delim = strchr(s, '/'); if (delim) { ch = *delim; *delim = 0; } /* try to create the directory, if it exists * and is a directory or a symlink, that's ok */ if (mkdir(pcopy, 0755) == -1) { switch (errno) { case EEXIST: if (lstat(pcopy, &st) == -1) { /* can't stat? */ return 0; } switch (st.st_mode & S_IFMT) { case S_IFDIR: case S_IFLNK: break; default: return 0; } break; default: return 0; } } if (delim) { *delim = ch; } s = delim; } while (delim); return 1; } char *column(char *col, int ncols, char **vals, char **cols) { int i = 0; char *val = NULL; for (i=0; i < ncols; i++) { // fprintf(stderr, "checking '%s' = '%s'\n", cols[i], vals[i]); if (!strcmp(col, cols[i])) { val = vals[i]; break; } } return val; } #define IERR(x) do { conf->errors++; conf->log->errmsg = strdup(x); return conf->errabort; } while (0) #define COL(x) column(x, ncols, vals, cols) #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0) static char *ops[] = { "new", "remove", "update", 0 }; static int getop(char *opstr) { int i; if (!opstr) return 0; for (i=0;ops[i];i++) { if (!strcmp(opstr, ops[i])) { return i+1; } } return 0; } static int report_conflicts(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; char *path; char *pkg; pkg = COL("pkgid"); path = COL("path"); conf->errors++; fprintf(stderr, "%s owned by %s\n", path, pkg); return 0; } static int check_existing(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; char *path; struct stat st; path = COL("dest"); if (!path) IERR("can't check for existing"); if (conf->dryrun) { printf("checkfor %s\n", path); return 0; } if (conf->verbose) { fprintf(stderr, "check for existing %s\n", path); } if (lstat(path, &st) == 0) { fprintf(stderr, "%s exists \n", path); conf->errors++; } else { switch(errno) { /* not an error, file shouldn't exist*/ case ENOENT: break; default: fprintf(stderr, "unable to check %s: %s\n", path, strerror(errno)); conf->errors++; break; } } return 0; } static int update_files(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; char *pkg; char *path; char *dest; pkg = COL("pkgid"); if (!pkg) IERR("can't get pkgid"); path = COL("path"); if (!path) IERR("can't get path"); dest = COL("dest"); if (!dest) IERR("no file dest"); /* hash is different, so no different than install, * but we don't need to create leading directories */ if (conf->dryrun) { fprintf(stderr, "update %s\n", dest); return 0; } fprintf(stderr, "update not implemented: %s", dest); conf->errors++; return 0; } static int remove_files(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; char *dest; struct stat st; dest = COL("dest"); if (!dest) IERR("no file dest"); if (conf->dryrun) { char *ftype = COL("filetype"); int t = *ftype; switch(t) { case 'd': printf("rmdir %s\n", dest); break; default: printf("unlink %s\n", dest); break; } return 0; } if (lstat(dest, &st) == -1) { IERR("can't stat"); } if (S_ISDIR(st.st_mode)) { if (conf->verbose) { fprintf(stderr, "rmdir(%s)\n", dest); } rmdir(dest); } else if (S_ISREG(st.st_mode)) { /* TODO conf to import before removal */ if (conf->verbose) { fprintf(stderr, "unlink(%s)\n", dest); } if (unlink(dest) == -1) { IERR("can't unlink"); } } else { if (unlink(dest) == -1) { IERR("can't unlink"); } } return 0; } static int install_files(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; struct passwd *pw; struct group *gr; mode_t mode = 0; char *path, *dest; uid_t uid; gid_t gid; int ftype; char *val; char *hash = 0; char *opstr; int op = 0; /* TODO put the result row in a hash table. May not actually * be faster */ opstr = COL("op"); op = getop(opstr); if (op == 0) IERR("invalid operation"); /* TODO config to dishonor setuid/setgid */ path = COL("path"); if (!path) IERR("no file path"); if (strlen(path) == 0) { IERR("zero length path not allowed"); } dest = COL("dest"); if (!dest) IERR("no file dest"); if (strlen(dest) == 0) { IERR("zero length dest not allowed"); } val = COL("mode"); if (!val) IERR("can't determine mode"); mode = strtoul(val, NULL, 8); val = COL("filetype"); if (!val || strlen(val) == 0) { IERR("can't determine file type"); } ftype = *val; if (ftype == 'r') { hash = COL("hash"); if (!hash) IERR("can't get hash"); } if (conf->verbose) { fprintf(stderr, "installing '%c' %s\n", ftype, dest); } uid = getuid(); gid = getgid(); if (conf->setuser) { val = COL("username"); if (!val) IERR("no username"); pw = getpwnam(val); if (!pw) IERR("no passwd entry"); uid = pw->pw_uid; } if (conf->setgroup) { val = COL("groupname"); if (!val) IERR("no groupname"); gr = getgrnam(val); if (!gr) IERR("no group entry"); gid = gr->gr_gid; } if (conf->dryrun) { //printf("cld %s\n", path); printf("new %c%o %d:%d %s -> %s\n", ftype, mode, uid, gid, path, dest); return 0; } /* TODO should these be owned by the path owner if they don't exist? */ /* probably, though they then belong to the package, sort of */ if (!create_leading_dirs(dest)) { fprintf(stderr, "unable to create leading directories for %s\n", dest); IERR("cld failure"); } if (ftype == 'd') { if (mkdir(dest, mode) == -1) { IERR("can't mkdir"); } } else if (ftype == 'r') { struct zpm *source; source = conf->src ? conf->src : conf->log; if (conf->verbose > 1) { fprintf(stderr, "extracting %8.8s to %s with mode %o\n", hash, dest, mode); } if (!zpm_extract(source, hash, dest, mode)) { IERR("can't extract file"); } } else if (ftype == 'l') { char *target = COL("target"); if (!target) { fprintf(stderr, "no target for symlink %s\n", path); conf->errors++; return conf->errabort; } if (strlen(target) == 0) { IERR("zero length symlink not allowed"); } if (conf->verbose > 0) { fprintf(stderr, "symlink %s -> %s\n", dest, target); } if (symlink(target, dest) == -1) { perror("symlink failed"); IERR("can't symlink"); } } else { fprintf(stderr, "unhandled filetype %c\n", ftype); } if (conf->setuser && conf->setgroup) { if (chown(dest, uid, gid) == -1) { IERR("can't chown"); } } struct timespec times[2] = { {0}, {0} }; double mtime = strtod(COL("mtime"),NULL); times[0].tv_nsec = UTIME_OMIT; times[1].tv_sec = (time_t)llrint(floor(mtime)); times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000)); utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW); if (conf->verbose) { printf("%s\n", path); } return 0; } static void runstage(struct config *conf, char *stage, int (callback)(void *, int, char **, char **)) { int rv; char *errmsg; sqlite3_str *s; char *sql; s = sqlite3_str_new(conf->log->db); sqlite3_str_appendall(s, "select *, "); if (conf->rootdir) { sqlite3_str_appendf(s, "printf('%%s/%%s',rtrim(%Q,'/'),ltrim(path,'/'))", conf->rootdir); } else { sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))"); } sqlite3_str_appendall(s, " as dest from install_status"); if (stage) { sqlite3_str_appendf(s," where op = %Q", stage); } if (conf->reverse) { sqlite3_str_appendall(s," order by length(path) desc, path desc"); } sql = sqlite3_str_value(s); if (conf->verbose > 2) { fprintf(stderr, "stage query: %s\n", sql); } rv = zpm_exec(conf->log, sql, callback, conf, &errmsg); sqlite3_str_finish(s); if (rv) { fprintf(stderr, "exec fail: %s\n", sqlite3_errstr(rv)); if (errmsg) { fprintf(stderr, "database error: %s\n", errmsg); conf->errors++; } if (conf->log->error == 1) { fprintf(stderr, "unable to allocate memory\n"); } fprintf(stderr, "zpm_exec failure: %s\n", conf->log->errmsg ? conf->log->errmsg : "unknown"); conf->errors++; } if (conf->log->errmsg) { fprintf(stderr, "error: %s\n", conf->log->errmsg); } if (conf->errors && conf->exitonerror) { zpm_close(conf->log); zpm_close(conf->src); exit(EXIT_FAILURE); } /* TODO final report function in conf var */ } int main(int ac, char **av){ struct zpm localdb; struct zpm pkgdb; int opt; char *pkgdbfile = 0, *localdbfile = 0; char *s; struct config conf; conf.errabort = 1; conf.errors = 0; conf.verbose = 0; conf.dryrun = 0; conf.setuser = 1; conf.setgroup = 1; conf.log = 0; conf.src = 0; conf.rootdir = 0; conf.reverse = 0; if (geteuid() != 0) { conf.setuser = 0; conf.setgroup = 0; } localdbfile = ZPM_LOCAL_DB; if ((s = getenv("ZPMDB"))) { /* TODO does this need to be copied ? */ localdbfile = s; } if ((s = getenv("ZPM_ROOT_DIR"))) { /* TODO does this need to be copied ? */ conf.rootdir = s; } /* * -d localdb or ZPMDB * or /var/lib/zpm/zpm.db, or die * -f 'package database', otherwise regular default of env * ZPM_PACKAGE_FILE, or use pkgdb if otherwise not found * -R root of pkg, will just chdir there * * args are pkgid triple, but will do a pkg find on the pkgdb */ while ((opt = getopt(ac, av, "f:d:c:nCR:v")) != -1) { switch (opt) { case 'd': localdbfile = optarg; break; case 'f': pkgdbfile = optarg; break; case 'n': conf.dryrun = 1; break; case 'v': conf.verbose++; break; case 'C': conf.errabort = 0; break; case 'R': conf.rootdir = optarg; break; case 'N': conf.setuser = 0; conf.setgroup = 0; break; default: usage(); exit(EXIT_FAILURE); break; } } /* verify root dir exists */ if (conf.rootdir && !exists(conf.rootdir, NULL)) { fprintf(stderr, "rootdir %s does not exist\n", conf.rootdir); } if (!zpm_open(&localdb, localdbfile)) { fprintf(stderr, "can't open zpm db %s\n", localdbfile); exit(EXIT_FAILURE); } conf.log = &localdb; if (pkgdbfile) { if (!zpm_open(&pkgdb, pkgdbfile)) { fprintf(stderr, "can't open src db %s\n", localdbfile); exit(EXIT_FAILURE); } else { conf.src = &pkgdb; } } /* TODO find pkgid from arg */ /* TODO set conf var to finalize error reporting */ if (conf.verbose) { fprintf(stderr, "syncing filesystem %s (ldb %s) from %s\n", conf.rootdir ? conf.rootdir : "/", localdbfile, pkgdbfile); } conf.errors = 0; conf.exitonerror = 0; runstage(&conf, "conflict", report_conflicts); runstage(&conf, "new", check_existing); if (!conf.errors) { conf.exitonerror = 1; runstage(&conf, "new", install_files); runstage(&conf, "update", update_files); conf.reverse = 1; runstage(&conf, "remove", remove_files); conf.reverse = 0; } zpm_close(&localdb); zpm_close(conf.src); return conf.errors ? 1 : 0; }