X-Git-Url: https://pd.if.org/git/?a=blobdiff_plain;f=zpm-syncfs.c;h=c08407e8c455efb0aa2d0be18da794323d64e7ff;hb=e7ce7c71c84e3e2a394562a6adec73062759f5df;hp=c77b310308e21843449a6c701249137665af925e;hpb=fb90e164dd3560402a7acb757aea5abb888f7869;p=zpackage diff --git a/zpm-syncfs.c b/zpm-syncfs.c index c77b310..c08407e 100644 --- a/zpm-syncfs.c +++ b/zpm-syncfs.c @@ -11,6 +11,7 @@ #include #include #include +#include /* needed for S_IFMT and AT_FDCWD */ #include @@ -28,12 +29,72 @@ struct config { int errabort, errors, verbose, dryrun, conflicts; int setuser, setgroup; int reverse, exitonerror; + int overwrite, absorb; }; static void usage() { printf("usage: zpm $scriptname [-fncC] args ...\n"); } +static int seterror(struct config *conf, char *msgfmt, ...) { + char msg[1024]; + va_list ap; + + conf->errors++; + + va_start(ap, msgfmt); + vsnprintf(msg, sizeof msg, msgfmt, ap); + va_end(ap); + + msg[1023] = 0; + if (conf->log->errmsg) { + free(conf->log->errmsg); + } + + conf->log->errmsg = strdup(msg); + + if (conf->verbose) { + fprintf(stderr, "%s\n", msg); + } + + return conf->errabort; +} + +static int setsyserr(struct config *conf, char *msgfmt, ...) { + char msg[1024]; + va_list ap; + int printed; + + conf->errors++; + + va_start(ap, msgfmt); + printed = vsnprintf(msg, sizeof msg, msgfmt, ap); + va_end(ap); + + if (printed < 1) { + /* nothing we can really do */ + return conf->errabort; + } + + if ((size_t)printed < sizeof msg) { + snprintf(msg+printed, sizeof msg - printed, ": %s", + strerror(errno)); + } + + msg[1023] = 0; + if (conf->log->errmsg) { + free(conf->log->errmsg); + } + + conf->log->errmsg = strdup(msg); + + if (conf->verbose) { + fprintf(stderr, "%s\n", msg); + } + + return conf->errabort; +} + static int exists(char *path, mode_t *mode) { struct stat st; @@ -102,7 +163,7 @@ static int create_leading_dirs(char *path) { return 1; } -char *column(char *col, int ncols, char **vals, char **cols) { +static char *column(char *col, int ncols, char **vals, char **cols) { int i = 0; char *val = NULL; @@ -117,12 +178,18 @@ char *column(char *col, int ncols, char **vals, char **cols) { 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 }; +enum op { + OP_NEW = 1, + OP_REMOVE = 2, + OP_UPDATE = 3 +}; + static int getop(char *opstr) { int i; @@ -166,7 +233,9 @@ static int check_existing(void *f, int ncols, char **vals, char **cols) { struct stat st; path = COL("dest"); - if (!path) IERR("can't check for existing"); + if (!path) { + return seterror(conf, "no path"); + } if (conf->dryrun) { printf("checkfor %s\n", path); @@ -178,7 +247,7 @@ static int check_existing(void *f, int ncols, char **vals, char **cols) { } if (lstat(path, &st) == 0) { - fprintf(stderr, "%s exists \n", path); + fprintf(stderr, "%s exists\n", path); conf->errors++; } else { switch(errno) { @@ -194,41 +263,13 @@ static int check_existing(void *f, int ncols, char **vals, char **cols) { 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 (!dest) return seterror(conf,"no file dest"); if (conf->dryrun) { char *ftype = COL("filetype"); @@ -242,7 +283,7 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { } if (lstat(dest, &st) == -1) { - IERR("can't stat"); + return seterror(conf,"can't stat"); } if (S_ISDIR(st.st_mode)) { @@ -256,11 +297,11 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { fprintf(stderr, "unlink(%s)\n", dest); } if (unlink(dest) == -1) { - IERR("can't unlink"); + return seterror(conf, "can't unlink"); } } else { if (unlink(dest) == -1) { - IERR("can't unlink"); + return seterror(conf, "can't unlink"); } } @@ -269,146 +310,541 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__) -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; +struct nitem { + int op; uid_t uid; gid_t gid; + char *dest; + char *path; + char *hash; + char *target; + time_t mtime; + mode_t mode; int ftype; + struct timespec times[2]; +}; + +#define D_NOEXIST 0x1 +#define D_TYPE 0x2 +#define D_MD 0x4 +#define D_HASH 0x8 +#define D_ISDIR 0x10 +#define D_EISDIR 0x20 +#define D_UID 0x40 +#define D_GID 0x80 +#define D_MODE 0x100 +#define D_MTIME 0x200 +#define D_ERROR 0x1000 +#define D_STATERROR 0x2000 +#define D_RLERROR 0x4000 + +/* 1 = file doesn't exist, 2 = file is a directory, target isn't */ +/* 4 == ftype different */ +/* 8 = hash different when both are regular files */ +static unsigned int file_compare(struct nitem *n, struct stat *st) { + int etype = 0, stat_type; + char ehash[ZPM_HASH_STRLEN+1]; + unsigned int diff = 0; + char link[1024]; + ssize_t lsize; + + switch (n->ftype) { + case 'd': etype = S_IFDIR; diff |= D_ISDIR ; break; + case 'r': etype = S_IFREG; break; + case 'l': etype = S_IFLNK; break; + default: etype = 0; break; + } + + errno = 0; + /* new file, so check type, hash, etc */ + if (lstat(n->dest, st) == 0) { + stat_type = st->st_mode & S_IFMT; + if (stat_type != etype) { + diff |= D_TYPE; + } + if (stat_type == S_IFDIR) { + diff |= D_EISDIR; + } + + if (n->hash && etype == S_IFREG && stat_type == S_IFREG) { + zpm_hash(n->dest, ehash, 0); + if (strcmp(n->hash, ehash) != 0) { + diff |= D_HASH; + } + } + if (n->hash && etype == S_IFLNK && stat_type == S_IFLNK) { + lsize = readlink(n->dest, link, sizeof link); + if (lsize == -1 || lsize == sizeof link) { + diff |= D_RLERROR; + diff |= D_ERROR; + } else if (strcmp(n->target, link) != 0) { + diff |= D_HASH; + } + } + if (n->uid != st->st_uid) { + diff |= D_UID; + diff |= D_MD; + } + if (n->gid != st->st_gid) { + diff |= D_GID; + diff |= D_MD; + } + if (n->mode != (st->st_mode & 07777)) { + diff |= D_MODE; + diff |= D_MD; + } + } else { + switch(errno) { + case ENOENT: diff |= D_NOEXIST; break; + default: diff |= (D_STATERROR|D_ERROR); break; + } + } + + return diff; +} + +static int read_item(struct config *conf, int ncols, char **vals, char **cols, + struct nitem *n) { char *val; - char *hash = 0; - char *opstr; - int op = 0; + struct passwd *pw; + struct group *gr; + struct nitem zero = { 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"); + *n = zero; + + val = COL("op"); + if (!val) { + seterror(conf, "can't determine op"); + return 0; + } + n->op = getop(val); + if (!n->op) { + seterror(conf, "can't determine op"); + return 0; + } + + n->path = COL("path"); + if (!n->path) { + seterror(conf, "no file path"); + return 0; + } + if (strlen(n->path) == 0) { + seterror(conf, "zero length path not allowed"); + return 0; + } /* 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"); + n->dest = COL("dest"); + if (!n->dest) { + seterror(conf, "no file dest"); + return 0; } - dest = COL("dest"); - if (!dest) IERR("no file dest"); - if (strlen(dest) == 0) { - IERR("zero length dest not allowed"); + + if (strlen(n->dest) == 0) { + seterror(conf, "zero length dest not allowed"); + return 0; } val = COL("mode"); - if (!val) IERR("can't determine mode"); - mode = strtoul(val, NULL, 8); + if (!val) { + seterror(conf, "can't determine mode"); + return 0; + } + + n->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"); + seterror(conf, "can't determine file type"); + return 0; } + n->ftype = *val; - if (conf->verbose) { - fprintf(stderr, "installing '%c' %s\n", ftype, dest); + if (n->ftype == 'r') { + n->hash = COL("hash"); + if (!n->hash) { + seterror(conf, "can't get hash"); + return 0; + } + } else if (n->ftype == 'l') { + n->target = COL("target"); + if (!n->target) { + seterror(conf, "can't get target"); + return 0; + } + if (strlen(n->target) == 0) { + seterror(conf, "zero length target not allowed"); + return 0; + } + n->hash = n->target; } - uid = getuid(); - gid = getgid(); - if (conf->setuser) { val = COL("username"); - if (!val) IERR("no username"); + if (!val) { + seterror(conf, "no username"); + return 0; + } pw = getpwnam(val); - if (!pw) IERR("no passwd entry"); - uid = pw->pw_uid; + if (!pw) { + seterror(conf, "no passwd entry"); + return 0; + } + n->uid = pw->pw_uid; + } else { + n->uid = geteuid(); } + if (conf->setgroup) { val = COL("groupname"); - if (!val) IERR("no groupname"); + if (!val) { + seterror(conf, "no groupname"); + return 0; + } gr = getgrnam(val); - if (!gr) IERR("no group entry"); - gid = gr->gr_gid; + if (!gr) { + seterror(conf, "no group entry"); + return 0; + } + n->gid = gr->gr_gid; + } else { + n->gid = getegid(); } - if (conf->dryrun) { - //printf("cld %s\n", path); - printf("new %c%o %d:%d %s -> %s\n", ftype, mode, uid, gid, path, dest); + double mtime = strtod(COL("mtime"),NULL); + + n->times[0].tv_sec = 0; + n->times[0].tv_nsec = UTIME_OMIT; + n->times[1].tv_sec = (time_t)llrint(floor(mtime)); + n->times[1].tv_nsec = lrint(floor(fmod(mtime,1.0)*1000000000)); + + return 1; +} + +static int remove_dir(struct config *conf, char *path) { + int rv; + + rv = rmdir(path); + if (rv == -1) { + setsyserr(conf, "can't rmdir %s", path); return 0; } + return 1; +} + +static int remove_existing(struct config *conf, char *path) { + int rv; + + rv = unlink(path); + if (rv == -1) { + setsyserr(conf, "can't unlink %s", path); + return 0; + } + return 1; +} + +static int set_md(struct config *conf, struct nitem *item) { + int rv; - /* 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"); + rv = chmod(item->path, item->mode); + if (rv == -1) { + setsyserr(conf, "can't chmod"); + return conf->errabort; } - if (ftype == 'd') { - if (mkdir(dest, mode) == -1) { - IERR("can't mkdir"); + if (conf->setuser && conf->setgroup) { + rv = chown(item->path, item->uid, item->gid); + if (rv == -1) { + setsyserr(conf, "can't chown %s", item->dest); + return conf->errabort; } - } 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); + } + + rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW); + if (rv == -1) { + setsyserr(conf, "can't set mtime"); + return conf->errabort; + } + return 0; +} + +/* install a file or create a directory or symlink. path should not exist + * at this point. + */ +/* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file, + * 8 = rmdir existing dir, 16 = return true/false + */ +static int install(struct config *conf, struct nitem *item, unsigned int flags) { + int rv = 1; + struct zpm *source; + + int mkleading = (flags & 2); + int setmd = (flags & 1); + int unlink_file = (flags & 4); + int rm_dir = (flags & 8); + int failure = conf->errabort; + int success = 0; + + if (flags & 16) { + failure = 0; + success = 1; + } + + source = conf->src ? conf->src : conf->log; + + if (unlink_file) { + rv = remove_existing(conf, item->dest); + } else if (rm_dir) { + rv = remove_dir(conf, item->dest); + } + + if (rv != 1) { + return failure; + } + + if (mkleading) { + rv = create_leading_dirs(item->dest); + if (!rv) { + setsyserr(conf, "can't create leading dirs for %s", item->dest); + return failure; } - if (!zpm_extract(source, hash, dest, mode)) { - IERR("can't extract file"); + } + + if (item->ftype == 'r') { + rv = zpm_extract(source, item->hash, item->dest, item->mode); + if (rv == 0) { + seterror(conf, "can't extract %s", item->dest); + return failure; } - } else if (ftype == 'l') { - char *target = COL("target"); - if (!target) { - fprintf(stderr, "no target for symlink %s\n", path); - conf->errors++; - return conf->errabort; + return success; + } + + switch (item->ftype) { + case 'd': rv = mkdir(item->dest, item->mode); + break; + case 'l': rv = symlink(item->target, item->dest); + break; + default: /* error */ + break; + } + + if (rv == -1) { + setsyserr(conf, "installing %s failed", item->dest); + return failure; + } + + if (setmd) { + return set_md(conf, item) == 0 ? success : failure; + } + + return success; +} + +static int install_files(void *f, int ncols, char **vals, char **cols) { + struct config *conf = f; + struct nitem nitem; + struct stat existing; + int update = 0; + + /* TODO put the result row in a hash table. May not actually + * be faster + */ + if (!read_item(conf, ncols, vals, cols, &nitem)) { + fprintf(stderr, "can't read item\n"); + return conf->errabort; + } + + if (conf->verbose) { + fprintf(stderr, "%d '%c' %s\n", nitem.op, nitem.ftype, + nitem.dest); + } + + if (conf->dryrun) { + printf("new %c%o %d:%d %s -> %s\n", nitem.ftype, nitem.mode, + nitem.uid, nitem.gid, nitem.path, nitem.dest); + return 0; + } + + unsigned int diffs = file_compare(&nitem, &existing); + if (diffs >= D_ERROR) { + return seterror(conf, "can't check %s", nitem.dest); + } + + if (conf->verbose) { + fprintf(stderr, "diffs = %u\n", diffs); + } + + /* updates: + * exist & same type & md same & hash same: do nothing, but warn bug + * exist & same type & md diff & hash same: fix md + * exist & same type & md same & hash diff: replace + * exist & same type & md diff & hash diff: replace & fix + * no exist: install and warn + * dir & not dir : remove, mkdir + * not dir & not dir & diff type: remove, install + * not dir & dir : remove dir if empty, error if not empty, install + * + * installs: + * no exist: create leading dirs, install + * + * exist & same type & md same & hash same & accept or over: do nothing + * exist & same & md diff or hash diff & overwrite : update + * exist & same & md diff or hash diff & accept : error, can't accept + * exist & same & md diff or hash diff & not accept : error + * + * exist & different type & not overwrite : error + * not dir & not dir & overwrite : remove and install + * not dir & dir & overwrite: remove empty or error, install + * dir & dir & overwrite: fix md + * dir & not dir & overwrite: remove and mkdir + */ + int exist = (!(diffs & D_NOEXIST)); + int sametype = (!(diffs & D_TYPE)); + int mdsame = (!(diffs & D_MD)); + int hashsame = (!(diffs & D_HASH)); + int isdir = (diffs & D_ISDIR); + int eisdir = (diffs & D_EISDIR); + int accept = conf->absorb; + int overwrite = conf->overwrite; + int installing = (nitem.op == OP_NEW); + update = (nitem.op == OP_UPDATE); + + if (update) { + if (!exist) { + /* warn, it should exist */ + fprintf(stderr, "%s missing, installing", nitem.dest); + return install(conf, &nitem, 3); } - if (strlen(target) == 0) { - IERR("zero length symlink not allowed"); + /* file exists in filesystem */ + if (sametype) { + if (mdsame && hashsame) { + fprintf(stderr, "%s should not be an update", nitem.dest); + /* warn, bug in logic. This shouldn't + * occur, because if there is nothing + * to do, it shouldn't be listed + * as an update + */ + /* do nothing */ + return 0; + } + if (!mdsame && hashsame) { + /* fix md */ + return set_md(conf, &nitem); + } + if (mdsame && !hashsame) { + /* install */ + return install(conf, &nitem, 3); + } + if (!mdsame && !hashsame) { + /* install */ + return install(conf, &nitem, 3); + } } - if (conf->verbose > 0) { - fprintf(stderr, "symlink %s -> %s\n", dest, target); + /* file exists, and is not the same type */ + + if (isdir && !eisdir) { + /* remove existing */ + /* mkdir */ + return install(conf, &nitem, 7); } - if (symlink(target, dest) == -1) { - perror("symlink failed"); - IERR("can't symlink"); + if (!isdir && eisdir) { + /* remove dir, or error */ + /* install */ + return install(conf, &nitem, 11); } - } else { - fprintf(stderr, "unhandled filetype %c\n", ftype); + if (!isdir && !isdir) { + /* necessarily !sametype, sametype handled above */ + /* remove existing */ + /* install */ + return install(conf, &nitem, 7); + } + /* error, should not be possible, assert(0)? */ } - if (conf->setuser && conf->setgroup) { - if (chown(dest, uid, gid) == -1) { - IERR("can't chown"); + if (installing) { + if (!exist) { + return install(conf, &nitem, 3); } - } - struct timespec times[2] = { {0}, {0} }; - double mtime = strtod(COL("mtime"),NULL); + /* file exists in filesystem */ + if (sametype) { + if (mdsame && hashsame && (accept || overwrite)) { + /* do nothing */ + return 0; + } + if (mdsame && hashsame && !(accept || overwrite)) { + /* error */ + return seterror(conf, "bad conditions"); + } + if (mdsame && !hashsame && overwrite) { + /* install */ + return install(conf, &nitem, eisdir ? 11 : 7); + } + if (mdsame && !hashsame && !overwrite) { + /* accept doesn't matter, since it's + * not an acceptable file */ + /* error */ + return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + } + if (!mdsame && hashsame && overwrite) { + /* fix md */ + return set_md(conf, &nitem); + } + if (!mdsame && hashsame && !overwrite) { + /* accept doesn't matter, since it's + * not an acceptable file */ + /* error */ + return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + } + if (!mdsame && !hashsame && overwrite) { + /* install */ + return install(conf, &nitem, eisdir ? 11 : 7); + } + if (!mdsame && !hashsame && !overwrite) { + /* accept doesn't matter, since it's + * not an acceptable file */ + /* error */ + return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + } + /* TODO error, should be impossible */ + return seterror(conf, "impossible state reached"); + } + + /* file exists, and is not the same type */ + if (!overwrite) { + /* error */ + return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + } - 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)); + /* not the same type, but ok to overwrite */ + if (!eisdir) { + /* remove existing */ + return install(conf, &nitem, 7); + } - utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW); + /* existing path is a directory */ + if (isdir) { + /* fix md */ + /* impossible, if isdir and eisdir, would + * be same type + * TODO + */ + return set_md(conf, &nitem); + } else { + /* remove empty dir or error */ + /* install */ + return install(conf, &nitem, 11); + } + /* if we get here, we missed a case */ + /* TODO error */ + return seterror(conf, "impossible state 2 reached"); + } if (conf->verbose) { - printf("%s\n", path); + printf("%s\n", nitem.path); } return 0; @@ -549,6 +985,8 @@ int main(int ac, char **av){ conf.src = 0; conf.rootdir = 0; conf.reverse = 0; + conf.overwrite = 0; + conf.absorb = 0; if (geteuid() != 0) { conf.setuser = 0; @@ -575,7 +1013,7 @@ int main(int ac, char **av){ * args are pkgid triple, but will do a pkg find on the pkgdb */ - while ((opt = getopt(ac, av, "f:d:c:nCR:v")) != -1) { + while ((opt = getopt(ac, av, "f:d:c:nCR:vOA")) != -1) { switch (opt) { case 'd': localdbfile = optarg; break; case 'f': pkgdbfile = optarg; break; @@ -584,6 +1022,8 @@ int main(int ac, char **av){ case 'C': conf.errabort = 0; break; case 'R': conf.rootdir = optarg; break; case 'N': conf.setuser = 0; conf.setgroup = 0; break; + case 'O': conf.overwrite = 1; break; + case 'A': conf.absorb = 1; break; default: usage(); exit(EXIT_FAILURE); @@ -628,15 +1068,36 @@ int main(int ac, char **av){ conf.conflicts); conf.errors++; } else { - runstage(&conf, "new", check_existing); + /* no point in running it if we're just going to + * overwrite everything + */ + if (conf.overwrite == 0 || conf.absorb == 0) { + runstage(&conf, "new", check_existing); + } + if (conf.verbose) { + fprintf(stderr, "beginning sync\n"); + } + /* have to do the removes first otherwise + * old files may conflict with update file + * type changes + */ if (!conf.errors) { conf.exitonerror = 1; - runstage(&conf, "new", install_files); - runstage(&conf, "update", update_files); conf.reverse = 1; + if (conf.verbose) { + fprintf(stderr, "removing old files\n"); + } runstage(&conf, "remove", remove_files); conf.reverse = 0; + if (conf.verbose) { + fprintf(stderr, "updating files\n"); + } + runstage(&conf, "update", install_files); + if (conf.verbose) { + fprintf(stderr, "installing files\n"); + } + runstage(&conf, "new", install_files); } }