From cb9584394f5d9becfbfc140a79bf83836325add2 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Thu, 11 Oct 2018 21:23:02 +0000 Subject: [PATCH] rewrite syncfs to handle updates and overwrites --- zpm-syncfs.c | 757 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 542 insertions(+), 215 deletions(-) diff --git a/zpm-syncfs.c b/zpm-syncfs.c index ead450e..26414e9 100644 --- a/zpm-syncfs.c +++ b/zpm-syncfs.c @@ -36,6 +36,65 @@ 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; @@ -122,69 +181,15 @@ char *column(char *col, int ncols, char **vals, char **cols) { #define COL(x) column(x, ncols, vals, cols) #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0) -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; -} - -#define IERR(x) return seterror(conf, x) 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; @@ -228,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); @@ -240,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) { @@ -256,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"); @@ -304,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)) { @@ -318,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"); } } @@ -331,209 +310,541 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { #define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__) +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(char *path, char *hash, int ftype, uid_t uid, - gid_t gid, int perms) { - struct stat st; +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 (ftype) { - case 'd': etype = S_IFDIR; break; + 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(path, &st) == 0) { - stat_type = st.st_mode & S_IFMT; + if (lstat(n->dest, st) == 0) { + stat_type = st->st_mode & S_IFMT; if (stat_type != etype) { - diff &= 4; + diff |= D_TYPE; + } + if (stat_type == S_IFDIR) { + diff |= D_EISDIR; } - if (stat_type == S_IFDIR && etype != S_IFDIR) { - diff &= 2; + + 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 (hash && etype == S_IFREG && stat_type == S_IFREG) { - zpm_hash(path, ehash, 0); - if (strcmp(hash, ehash) != 0) { - diff &= 8; + 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 (uid != st.st_uid) { - diff &= 16; + if (n->uid != st->st_uid) { + diff |= D_UID; + diff |= D_MD; } - if (gid != st.st_gid) { - diff &= 32; + if (n->gid != st->st_gid) { + diff |= D_GID; + diff |= D_MD; } - if (perms != (st.st_mode & 07777)) { - diff &= 64; + if (n->mode != (st->st_mode & 07777)) { + diff |= D_MODE; + diff |= D_MD; } } else { switch(errno) { - case ENOENT: diff &= 1; break; - default: diff &= 128; break; + case ENOENT: diff |= D_NOEXIST; break; + default: diff |= (D_STATERROR|D_ERROR); break; } } return diff; } -static int install_files(void *f, int ncols, char **vals, char **cols) { - struct config *conf = f; +static int read_item(struct config *conf, int ncols, char **vals, char **cols, + struct nitem *n) { + char *val; struct passwd *pw; struct group *gr; + struct nitem zero = { 0 }; - 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; + *n = zero; - /* 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"); + 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; - /* 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)) { - return setsyserr(conf, "unable to create leading directories for %s\n", dest); + 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; - unsigned int diffs = file_compare(dest, hash, ftype, uid, gid, mode); + rv = chmod(item->path, item->mode); + if (rv == -1) { + setsyserr(conf, "can't chmod"); + return conf->errabort; + } - /* 1 = file doesn't exist, 2 = file is a directory, target isn't */ - /* 4 == ftype different */ - /* 8 = hash different when both are regular files */ - if (diffs == 128) { - return seterror(conf, "can't check %s", dest); + 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; + } } - if (diffs > 1) { - return seterror(conf, "absorb and overwrite not implemented"); + 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; +} - if (ftype == 'd') { - if (mkdir(dest, mode) == -1) { - return setsyserr(conf, "can't create directory %s", - dest); - } - } else if (ftype == 'r') { - struct zpm *source; - source = conf->src ? conf->src : conf->log; +/* 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 (conf->verbose > 1) { - fprintf(stderr, "extracting %8.8s to %s with mode %o\n", - hash, dest, mode); + 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) { - return seterror(conf, "no target for symlink %s\n", - path); + 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) { - return setsyserr(conf, "symlink failed"); + 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) { - return setsyserr(conf, "can't chown %s", dest); + 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"); + } - 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)); + /* file exists, and is not the same type */ + if (!overwrite) { + /* error */ + return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + } + + /* 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; @@ -764,13 +1075,29 @@ int main(int ac, char **av){ 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); } } -- 2.40.0