X-Git-Url: https://pd.if.org/git/?p=zpackage;a=blobdiff_plain;f=zpm-syncfs.c;h=220ee039c38ab0428266f29e8c2e8d5580bc16c4;hp=0b88928d456ae875bd940a8d1fdabdf65c0f30ec;hb=62f6ff407bc4f2cf03d1fa7cf3dc9a3f4026624a;hpb=5d06aa78f6500a05b4ac440a06534df7ac0d8182 diff --git a/zpm-syncfs.c b/zpm-syncfs.c index 0b88928..220ee03 100644 --- a/zpm-syncfs.c +++ b/zpm-syncfs.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -30,7 +31,12 @@ struct config { int errabort, errors, verbose, dryrun, conflicts; int setuser, setgroup; int reverse, exitonerror; - int overwrite, absorb; + int overwrite, accept, acceptdir, ignoredirmd; + unsigned long ops_total, ops_completed; + unsigned long ops_remove, ops_remove_completed; + unsigned long ops_update, ops_update_completed; + unsigned long ops_install, ops_install_completed; + int progress; /* type of progress meter */ }; struct nitem { @@ -55,6 +61,27 @@ static void usage() { printf("usage: zpm $scriptname [-fncC] args ...\n"); } +static void warn(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); +} + +static void pdots(int len, int ch, int was, int now, int total) { + was = len * was / total; + if (now > total) { + now = total; + } + now = len * now / total; + while (was++ < now) { + putchar(ch); + } + fflush(stdout); +} + static int seterror(struct config *conf, char *msgfmt, ...) { char msg[1024]; va_list ap; @@ -202,6 +229,7 @@ static char *column(char *col, int ncols, char **vals, char **cols) { #define SYSERR(x) do { conf->log->error = 2; return conf->errabort; } while (0) +/* TODO handle other ops properly */ static char *ops[] = { "new", "remove", "update", 0 }; enum op { @@ -247,92 +275,148 @@ static int report_conflicts(void *f, int ncols, char **vals, char **cols) { return 0; } -static int check_existing(void *f, int ncols, char **vals, char **cols) { - struct config *conf = f; - char *path; - struct stat st; +static int read_item(struct config *conf, int ncols, char **vals, char **cols, + struct nitem *n) { + char *val; + long lval; + struct passwd *pw; + struct group *gr; + struct nitem zero = { 0 }; + + *n = zero; - path = COL("dest"); - if (!path) { - return seterror(conf, "no path"); + val = COL("op"); + if (!val) { + seterror(conf, "can't determine op"); + return 0; + } + n->opstr = val; + n->op = getop(val); + if (!n->op) { + seterror(conf, "can't determine op"); + return 0; } - if (conf->dryrun) { - printf("checkfor %s\n", path); - fflush(stdout); + 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; } - if (conf->verbose) { - fprintf(stderr, "check for existing %s\n", path); + /* TODO config to dishonor setuid/setgid */ + n->dest = COL("dest"); + if (!n->dest) { + seterror(conf, "no file dest"); + return 0; } - 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; - } + if (strlen(n->dest) == 0) { + seterror(conf, "zero length dest not allowed"); + return 0; } - return 0; -} -static int remove_files(void *f, int ncols, char **vals, char **cols) { - struct config *conf = f; - char *dest; - struct stat st; - int flags = 0; + val = COL("mode"); - dest = COL("dest"); - if (!dest) return seterror(conf,"no file dest"); + if (!val) { + seterror(conf, "can't determine mode"); + return 0; + } - if (conf->dryrun) { - char *ftype = COL("filetype"); - int t = *ftype; + n->mode = strtoul(val, NULL, 8); - switch(t) { - case 'd': printf("rmdir %s\n", dest); break; - default: printf("unlink %s\n", dest); break; - } - fflush(stdout); + val = COL("configuration"); + if (!val) { + seterror(conf, "can't determine config status"); return 0; } + lval = strtol(val, NULL, 10); - if (lstat(dest, &st) == -1) { - return seterror(conf,"can't stat"); - } + n->configuration = ((lval & 1) != 0); + n->oldwasconf = ((lval & 2) != 0); - if (S_ISDIR(st.st_mode)) { - flags = AT_REMOVEDIR; + val = COL("filetype"); + if (!val || strlen(val) == 0) { + seterror(conf, "can't determine file type"); + return 0; } - /* TODO check that expected filetype matches actual filetype */ + n->ftype = *val; - if (conf->verbose) { - fprintf(stderr, "%s(%s)\n", flags ? "rmdir" : "unlink", dest); + /* these can be null */ + n->ohash = COL("ohash"); + n->mds = COL("mds"); + n->omds = COL("omds"); + n->pkglist = COL("pkglist"); + + 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; } - errno = 0; + if (conf->setuser) { + val = COL("username"); + if (!val) { + seterror(conf, "no username"); + return 0; + } + pw = getpwnam(val); + if (!pw) { + seterror(conf, "no passwd entry"); + return 0; + } + n->uid = pw->pw_uid; + } else { + n->uid = geteuid(); + } - if (unlinkat(AT_FDCWD, dest, flags) == -1) { - switch (errno) { - case ENOENT: - break; - default: - return seterror(conf, "can't unlink"); + if (conf->setgroup) { + val = COL("groupname"); + if (!val) { + seterror(conf, "no groupname"); + return 0; + } + gr = getgrnam(val); + if (!gr) { + seterror(conf, "no group entry for %s", val); + return 0; } + n->gid = gr->gr_gid; + } else { + n->gid = getegid(); } - - return 0; -} -#define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__) + errno = 0; + double mtime = strtod(COL("mtime"),NULL); + if (errno) { + mtime = (double)time(NULL); + } + + n->mtime = (time_t)mtime; + + 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; +} /* file does not exist */ #define D_NOEXIST 0x1 @@ -358,6 +442,8 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { * the the hash of the file on disk */ #define D_OHASH 0x400 +/* file exists, and is a directory, and is empty */ +#define D_ISEMPTY 0x800 /* an error occurred trying to compare the file (other than it doesn't exist */ #define D_ERROR 0x1000 /* there was a stat error */ @@ -365,6 +451,27 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { /* there was an error calling readlink */ #define D_RLERROR 0x4000 +static int dir_is_empty(char *path) { + DIR *dir; + struct dirent *dp; + int empty; + + dir = opendir(path); + if (!dir) { + return -1; + } + + dp = readdir(dir); + if (dp) { + empty = 0; + } else { + empty = 1; + } + closedir(dir); + + return empty; +} + /* 1 = file doesn't exist, 2 = file is a directory, target isn't */ /* 4 == ftype different */ /* 8 = hash different when both are regular files */ @@ -391,6 +498,9 @@ static unsigned int file_compare(struct nitem *n, struct stat *st) { } if (stat_type == S_IFDIR) { diff |= D_EISDIR; + if (dir_is_empty(n->dest)) { + diff |= D_ISEMPTY; + } } if (n->hash && etype == S_IFREG && stat_type == S_IFREG) { @@ -437,149 +547,194 @@ static unsigned int file_compare(struct nitem *n, struct stat *st) { return diff; } -static int read_item(struct config *conf, int ncols, char **vals, char **cols, - struct nitem *n) { - char *val; - long lval; - struct passwd *pw; - struct group *gr; - struct nitem zero = { 0 }; - *n = zero; +/* 0 = not acceptable + * 1 = accept and create/update/remove + * 2 = accept as is + * 3 = remove and overwrite + * 4 = update metadata + */ +static int acceptable(struct config *conf, unsigned int diffs, int op) { + 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); - val = COL("op"); - if (!val) { - seterror(conf, "can't determine op"); - return 0; + if (!exist) { + return op == OP_REMOVE ? 2 : 1; } - n->opstr = val; - n->op = getop(val); - if (!n->op) { - seterror(conf, "can't determine op"); - return 0; + + if (op == OP_UPDATE) { + return sametype ? 4 : 3; } - n->path = COL("path"); - if (!n->path) { - seterror(conf, "no file path"); - return 0; + if (op == OP_REMOVE) { + return 1; } - if (strlen(n->path) == 0) { - seterror(conf, "zero length path not allowed"); - return 0; + + /* the hard cases, should be installing new, but already exists */ + + if (!sametype) { + return conf->overwrite ? 3 : 0; } - /* TODO config to dishonor setuid/setgid */ - n->dest = COL("dest"); - if (!n->dest) { - seterror(conf, "no file dest"); - return 0; + if (mdsame && (conf->accept || conf->overwrite)) { + return 1; } - if (strlen(n->dest) == 0) { - seterror(conf, "zero length dest not allowed"); - return 0; + if (isdir) { + if (mdsame || conf->ignoredirmd) { + return conf->acceptdir ? 2 : 0; + } + if (conf->overwrite) { + return 4; + } } - val = COL("mode"); + if (hashsame && (conf->accept || conf->overwrite)) { + return 1; + } - if (!val) { - seterror(conf, "can't determine mode"); - return 0; + return conf->overwrite ? 3 : 0; +} + +static int check_existing(void *f, int ncols, char **vals, char **cols) { + struct config *conf = f; + struct stat st; + struct nitem nitem; + + if (!read_item(conf, ncols, vals, cols, &nitem)) { + fprintf(stderr, "can't read item\n"); + return conf->errabort; } - n->mode = strtoul(val, NULL, 8); + if (conf->verbose > 1) { + fprintf(stderr, "check for existing %s\n", nitem.path); + } - val = COL("configuration"); - if (!val) { - seterror(conf, "can't determine config status"); + if (lstat(nitem.path, &st) == -1) { + switch(errno) { + /* not an error, file shouldn't exist*/ + case ENOENT: break; + default: + fprintf(stderr, "unable to check %s: %s\n", + nitem.path, strerror(errno)); + conf->errors++; + break; + } return 0; } - lval = strtol(val, NULL, 10); - n->configuration = ((lval & 1) != 0); - n->oldwasconf = ((lval & 2) != 0); + unsigned int diffs = file_compare(&nitem, &st); + int sametype = (!(diffs & D_TYPE)); - val = COL("filetype"); - if (!val || strlen(val) == 0) { - seterror(conf, "can't determine file type"); - return 0; + if (diffs >= D_ERROR) { + return seterror(conf, "can't check %s", nitem.dest); } - n->ftype = *val; - /* these can be null */ - n->ohash = COL("ohash"); - n->mds = COL("mds"); - n->omds = COL("omds"); - n->pkglist = COL("pkglist"); + if (sametype && nitem.configuration) { + return 0; + } - 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; + int action = acceptable(conf, diffs, nitem.op); + if (!action) { + if (conf->accept) { + fprintf(stderr, "%s exists and is not acceptable\n", nitem.path); + } else { + fprintf(stderr, "%s exists\n", nitem.path); } - n->hash = n->target; + conf->errors++; } - if (conf->setuser) { - val = COL("username"); - if (!val) { - seterror(conf, "no username"); - return 0; - } - pw = getpwnam(val); - if (!pw) { - seterror(conf, "no passwd entry"); - return 0; - } - n->uid = pw->pw_uid; - } else { - n->uid = geteuid(); + return 0; +} + +static void update_progress(struct config *conf, char *op, char *path) { + if (!conf->verbose) { + return; } - if (conf->setgroup) { - val = COL("groupname"); - if (!val) { - seterror(conf, "no groupname"); - return 0; + if (conf->progress == 0) { + pdots(50, '.', conf->ops_completed-1, conf->ops_completed, conf->ops_total); + } else if (conf->progress == 1) { + size_t len = strlen(path); + int offset = 0; + if (len > 50) { + offset = len-50; } - gr = getgrnam(val); - if (!gr) { - seterror(conf, "no group entry"); - return 0; - } - n->gid = gr->gr_gid; - } else { - n->gid = getegid(); + printf("\r%lu/%lu %.10s %.50s\n", conf->ops_completed, + conf->ops_total, op, path+offset); + } else if (conf->progress == 2) { + printf("%lu/%lu %s %s\n", conf->ops_completed, + conf->ops_total, op, path); + } + fflush(stdout); +} + +static int remove_files(void *f, int ncols, char **vals, char **cols) { + struct config *conf = f; + char *dest; + struct stat st; + int flags = 0; + + conf->ops_completed++; + + dest = COL("dest"); + char *ftype = COL("filetype"); + + if (!dest) return seterror(conf,"no file dest"); + if (!ftype) return seterror(conf,"no file type"); + + update_progress(conf, *ftype == 'd' ? "rmdir" : "unlink", dest); + + if (conf->dryrun) { + return 0; } errno = 0; - double mtime = strtod(COL("mtime"),NULL); - if (errno) { - mtime = (double)time(NULL); + + if (lstat(dest, &st) == -1) { + switch (errno) { + case ENOENT: + if (conf->verbose > 1) { + fprintf(stderr, "expected file not found: '%s'\n", dest); + } + break; + default: + return seterror(conf, "can't stat %s: %s", dest, strerror(errno)); + } + return 0; } - n->mtime = (time_t)mtime; + if (S_ISDIR(st.st_mode)) { + flags = AT_REMOVEDIR; + } + /* TODO check that expected filetype matches actual filetype */ + /* alternatively, just use the expected type, skip the stat, + * and let it fail if the type is wrong + */ - 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)); + errno = 0; - return 1; + if (unlinkat(AT_FDCWD, dest, flags) == -1) { + switch (errno) { + case ENOENT: + break; + case ENOTEMPTY: /* fall through */ + case EEXIST: + fprintf(stderr, "expected empty directory: %s\n", dest); + break; + default: + return seterror(conf, "can't unlink %s: %s", dest, strerror(errno)); + } + } + + return 0; } +#define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__) + static int remove_dir(struct config *conf, char *path) { int rv; @@ -619,20 +774,23 @@ static int set_md(struct config *conf, struct nitem *item) { return success; } - /* can't chmod a symlink */ - if (item->ftype != 'l') { - rv = chmod(item->dest, item->mode); - + if (conf->setuser && conf->setgroup) { + rv = lchown(item->dest, item->uid, item->gid); if (rv == -1) { - setsyserr(conf, "can't chmod %o %s", item->mode, item->dest); + setsyserr(conf, "can't lchown %s", item->dest); return conf->errabort; } } - if (conf->setuser && conf->setgroup) { - rv = lchown(item->dest, item->uid, item->gid); + /* have to chmod after the chown, setuid bits may (and will) + * be cleared after a chown + */ + /* can't chmod a symlink */ + if (item->ftype != 'l') { + rv = chmod(item->dest, item->mode); + if (rv == -1) { - setsyserr(conf, "can't lchown %s", item->dest); + setsyserr(conf, "can't chmod %o %s", item->mode, item->dest); return conf->errabort; } } @@ -669,7 +827,7 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) int failure = conf->errabort; int success = 0; - if (flags & 16) { + if (flags & INS_RTF) { failure = 0; success = 1; } @@ -694,16 +852,6 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) 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) { @@ -712,16 +860,21 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) } } - 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; - } - return success; + if (unlink_file) { + rv = remove_existing(conf, item->dest); + } else if (rm_dir) { + rv = remove_dir(conf, item->dest); + } + + if (rv != 1) { + return failure; } + errno = 0; switch (item->ftype) { + case 'r': rv = zpm_extract(source, item->hash, item->dest, item->mode); + if (rv == 0) rv = -1; + break; case 'd': rv = mkdir(item->dest, item->mode); break; case 'l': rv = symlink(item->target, item->dest); @@ -731,6 +884,17 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) } if (rv == -1) { + switch (item->ftype) { + case 'r': + seterror(conf, "can't extract %s", item->dest); + break; + case 'd': + setsyserr(conf, "install mkdir(\"%s\") failed", item->dest); + break; + case 'l': + setsyserr(conf, "install symlink(\"%s\") failed", item->dest); + break; + } setsyserr(conf, "installing %s failed", item->dest); return failure; } @@ -742,6 +906,24 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) return success; } +static int save_config_file(struct config *conf, struct nitem *n, char *msgfmt) { + char hash[ZPM_HASH_STRLEN+1]; + + if (!msgfmt) { + msgfmt = "saved config file %.8s"; + } + + if (zpm_import(conf->log, n->dest, 0, hash)) { + zpm_note_add(conf->log, n->pkglist, n->path, hash, msgfmt, hash); + } else { + warn("unable to import existing config file %s", n->dest); + conf->errors++; + return 0; + } + return 1; +} + +#if 0 /* * figure out what the difference is for a config file, only called * for an update of a configuration file @@ -749,67 +931,33 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) * return 1 if the new file should not be installed * return 0 if the new file should be installed */ -static int adjust_for_config(struct config *conf, struct nitem *n, unsigned int - diffs) { +static int adjust_for_config(struct nitem *n, unsigned int diffs) { if (!n->oldwasconf) { return 0; } + + int sametype = (!(diffs & D_TYPE)); + int isdir = (diffs & D_ISDIR); + int eisdir = (diffs & D_EISDIR); - /* TODO what if old was a directory? */ + /* what if old was a directory? */ if (!n->configuration) { /* replacing conf with non-conf */ /* absorb file, mark todo */ - char hash[ZPM_HASH_STRLEN+1]; - if (!conf->dryrun) { - if (conf->verbose) { - fprintf(stderr, "importing old conf file\n"); - } - if (zpm_import(conf->log, n->dest, 0, hash)) { - zpm_note_add(conf->log, n->pkglist, n->dest, hash, - "replaced config file with non-config. zpm-cat %.8s", hash); - } else { - fprintf(stderr, "unable to import existing config file %s\n", n->dest); - return -1; - } - } else { - fprintf(stderr, "dry-run: would replace config file %s with non-config file\n", n->dest); - } return 0; } - int sametype = (!(diffs & D_TYPE)); - int isdir = (diffs & D_ISDIR); - int eisdir = (diffs & D_EISDIR); - /* both old and new are config files */ if (isdir && sametype) { /* both config directories, can only be changing * metadata, so no adjustment needed */ - if (conf->verbose) { - fprintf(stderr, "both config dirs, ok to update\n"); - } return 0; } if (isdir) { - char hash[ZPM_HASH_STRLEN+1]; - - /* replacing old file with new directory */ - /* absorb, make note */ - if (!conf->dryrun) { - if (zpm_import(conf->log, n->dest, 0, hash)) { - zpm_note_add(conf->log, n->pkglist, n->dest, hash, - "replaced config file with config directory. zpm-cat %.8s", hash); - } else { - fprintf(stderr, "unable to import existing config file %s\n", n->dest); - return -1; - } - } else { - fprintf(stderr, "dry-run: would replace config file %s with config directory\n", n->dest); - } - return 0; + return 0; } if (eisdir) { @@ -824,37 +972,185 @@ static int adjust_for_config(struct config *conf, struct nitem *n, unsigned int /* replacing old file with new file */ /* new is same as on disk */ if (!(diffs & D_HASH)) { - if (conf->verbose) { - fprintf(stderr, "new config file is already on disk, probably shouldn't happen\n"); - } return 0; } /* new is different than on disk, but on disk is same as old */ if (!(diffs & D_OHASH)) { - if (conf->verbose) { - fprintf(stderr, "old config file not changed from default, replacing with new default\n"); - } - /* ok to do the update, since same as default */ - fprintf(stderr, "updating default config %s\n", n->dest); return 0; } - /* new is different than on disk, and disk different than old */ - /* log */ - if (conf->verbose) { - fprintf(stderr, "new default config file is different than on disk, and old default was changed, should keep on-disk config\n"); + return 1; + +} +#endif + +static int config_handler(void *f, int ncols, char **vals, char **cols) { + struct config *conf = f; + struct nitem nitem; + struct stat existing; + char *save = 0; + char *note = 0; + char *notehash = 0; + int update = 0; + + if (!read_item(conf, ncols, vals, cols, &nitem)) { + fprintf(stderr, "can't read item\n"); + conf->errors++; + return conf->errabort; + } + + unsigned int diffs = file_compare(&nitem, &existing); + if (diffs >= D_ERROR) { + return seterror(conf, "can't check %s", nitem.dest); + } + + int exist = (!(diffs & D_NOEXIST)); + int sametype = (!(diffs & D_TYPE)); + //int mdsame = (!(diffs & D_MD)); + int hashsame = (!(diffs & D_HASH)); + int oldhashsame = (!(diffs & D_OHASH)); + int isdir = (diffs & D_ISDIR); + int eisdir = (diffs & D_EISDIR); + update = (nitem.op == OP_UPDATE); + + notehash = nitem.hash; + + /* if the file doesn't exist in the system, nothing to do */ + /* could possibly note if we expected it, but the regular handling + * should do that + */ + if (!exist) { + return 0; + } + + if (nitem.op == OP_UPDATE && !nitem.oldwasconf) { + /* possibly save anyway */ + return 0; + } + + /* so, old was conf, and something exists in the filesystem */ + + if (!sametype) { + warn("won't %s %s%s, %s exists", + nitem.op == OP_NEW ? "install" : nitem.opstr, + nitem.path, + isdir ? "/" : "", + eisdir ? "directory" : "file" + ); + conf->errors++; + return conf->errabort; + } + + /* all below are same type of file */ + /* what about sametype, but old was different type */ + + if (isdir) { + return 0; + } + + /* save or note cases */ + + if (nitem.op == OP_REMOVE) { + save ="saved removed config file %.8s"; + } else + + if (nitem.op == OP_UPDATE) { + if (!nitem.configuration) { + /* replacing config with non-config */ + save = "replacing configuration file %.8s with non-configuration file"; + } else if (oldhashsame) { + /* config file hasn't changed from old default, + * so go ahead and install the new one + */ + save = "replaced old default config (%.8s) with new one."; + notehash = nitem.ohash; + } else { + note = "kept existing config file. new default version is %.8s"; + save = 0; + } + } else + + if (nitem.op == OP_NEW && !hashsame) { + note = "config file already existed. would have installed %.8s"; + save = 0; } + + /* + * save files, add notes + */ if (!conf->dryrun) { - zpm_note_add(conf->log, n->pkglist, n->dest, n->hash, - "default config file update. zpm-cat %.8s", n->hash); - /* TODO check for note error */ + if (save) { + warn("saving config file: %s (root %s)", nitem.path, conf->rootdir ? conf->rootdir : "/"); + save_config_file(conf, &nitem, save); + } + if (note) { + zpm_note_add(conf->log, nitem.pkglist, nitem.path, nitem.hash, + note, nitem.hash); + } } else { - fprintf(stderr, "dry-run: default config file %s update\n", - n->dest); + if (save) { + fprintf(stderr, "dry run: %s %s: ", nitem.pkglist, + nitem.path); + warn(save, notehash); + } + if (note) { + fprintf(stderr, "dry run: %s %s: ", nitem.pkglist, + nitem.path); + warn(note, notehash); + } + + } + + return 0; +} + +static void handle_config_files(struct config *conf) { + 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 syncinfo"); + + sqlite3_str_appendall(s," where configuration > 0 and op in ('new','update','remove')"); + + if (conf->reverse) { + sqlite3_str_appendall(s," order by length(path) desc, path desc"); + } + + sql = sqlite3_str_value(s); + + rv = zpm_exec(conf->log, sql, config_handler, 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++; } - return 1; + if (conf->errors && conf->exitonerror) { + zpm_close(conf->log); + zpm_close(conf->src); + exit(EXIT_FAILURE); + } } static int install_files(void *f, int ncols, char **vals, char **cols) { @@ -863,7 +1159,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { struct stat existing; int update = 0; - /* TODO put the result row in a hash table. May not actually + /* put the result row in a hash table? May not actually * be faster */ if (!read_item(conf, ncols, vals, cols, &nitem)) { @@ -871,10 +1167,16 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { return conf->errabort; } - if (conf->verbose && !conf->dryrun) { - fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype, - nitem.dest); - } +#if 0 + int64_t used, high; + used = sqlite3_memory_used()/1024/1024; + high = sqlite3_memory_highwater(0)/1024/1024; + fprintf(stderr, "memory = %ld MB / %ld MB\n", used, high); +#endif + char action[40]; + sprintf(action, "%.8s %c", nitem.opstr, nitem.ftype); + conf->ops_completed++; + update_progress(conf, action, nitem.dest); unsigned int diffs = file_compare(&nitem, &existing); if (diffs >= D_ERROR) { @@ -909,28 +1211,42 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { int sametype = (!(diffs & D_TYPE)); int mdsame = (!(diffs & D_MD)); int hashsame = (!(diffs & D_HASH)); + int ohashsame = (!(diffs & D_OHASH)); int isdir = (diffs & D_ISDIR); int eisdir = (diffs & D_EISDIR); - int accept = conf->absorb; + int accept = conf->accept; int overwrite = conf->overwrite; int installing = (nitem.op == OP_NEW); update = (nitem.op == OP_UPDATE); + /* if a config file doesn't exist on disk, go ahead and do + * whatever you were going to do, this logic here just + * needs to determine if we should skip what we were going to do + * + * if the old item was a configuration item and the new one isn't, it + * will have been saved earlier, so we can just go ahead. so we only + * test for an existing file, where the item is a configuration file + */ + if (nitem.configuration && exist) { + if (!sametype && !conf->overwrite) { + return seterror(conf, "configuration file exists with different type: %s", nitem.dest); + } + + if (conf->accept) { + return 0; + } + + if (isdir && !mdsame) return 0; + if (!isdir && !ohashsame) return 0; + } + if (update) { if (!exist) { /* warn, it should exist */ - fprintf(stderr, "%s missing, installing", nitem.dest); + fprintf(stderr, "%s missing, installing\n", nitem.dest); return install(conf, &nitem, 3); } - switch (adjust_for_config(conf, &nitem, diffs)) { - case -1: return conf->errabort; break; - case 1: - fprintf(stderr, "skipping changed default config file: %s\n", nitem.dest); - return 0; break; - default: break; - } - /* file exists in filesystem */ if (sametype) { if (mdsame && hashsame) { @@ -946,6 +1262,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { * and group, it might be correct on disk * but not as in the local database */ + /* TODO detect whether this a logic bug or * an on-disk difference */ @@ -963,13 +1280,14 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { /* 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 (!hashsame) { + /* doesn't matter on the md */ + int flags = INS_MD | INS_CLD; + if (nitem.ftype == 'l') { + flags |= INS_UNLINK; + } + return install(conf, &nitem, flags); } } @@ -1004,13 +1322,24 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { if (sametype) { if (mdsame && hashsame && (accept || overwrite)) { /* do nothing */ - if (conf->dryrun || conf->verbose) { + if (conf->dryrun || conf->verbose > 1) { fprintf(stderr, "accept %s: %s\n", eisdir ? "directory" : "file", nitem.dest); } return 0; } + if (mdsame && isdir && conf->acceptdir) { + return 0; + } + + if (!mdsame && isdir && conf->ignoredirmd) { + if (conf->verbose > 1) { + fprintf(stderr, "ignoring directory metadata difference: %s\n", nitem.dest); + } + return 0; + } + if (mdsame && hashsame && !(accept || overwrite)) { /* error */ return seterror(conf, "file exists: %s", nitem.dest); @@ -1069,7 +1398,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { /* error */ return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); } - /* TODO error, should be impossible */ + /* error, should be impossible */ return seterror(conf, "impossible state reached"); } @@ -1088,9 +1417,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { /* existing path is a directory */ if (isdir) { /* fix md */ - /* impossible, if isdir and eisdir, would - * be same type - * TODO + /* impossible, if isdir and eisdir, would be same type */ return set_md(conf, &nitem); } else { @@ -1099,12 +1426,10 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { return install(conf, &nitem, 11); } /* if we get here, we missed a case */ - /* TODO error */ return seterror(conf, "impossible state 2 reached"); } - /* TODO extra verbose print perms, mtime, etc, probably ls -l - * format + /* TODO extra verbose print perms, mtime, etc, probably ls -l format */ if (conf->verbose) { printf("%s\n", nitem.path); @@ -1224,19 +1549,26 @@ static void runstage(struct config *conf, char *stage, /* TODO final report function in conf var */ } -int main(int ac, char **av){ +static void count_ops(struct config *conf) { + conf->ops_remove = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'remove'"); + conf->ops_update = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'update'"); + conf->ops_install = zpm_db_int(conf->log, "select count(*) from syncinfo where op = 'new'"); + conf->ops_total = conf->ops_remove + conf->ops_update + conf->ops_install; +} + +int main(int ac, char **av) { struct zpm localdb; struct zpm pkgdb; int opt; char *pkgdbfile = 0, *localdbfile = 0; char *s; - struct config conf; + struct config conf = { 0 }; conf.errabort = 1; conf.errors = 0; conf.conflicts = 0; - conf.verbose = 0; + conf.verbose = 1; conf.dryrun = 0; conf.setuser = 1; conf.setgroup = 1; @@ -1245,7 +1577,8 @@ int main(int ac, char **av){ conf.rootdir = 0; conf.reverse = 0; conf.overwrite = 0; - conf.absorb = 0; + conf.accept = 0; + conf.acceptdir = 1; if (geteuid() != 0) { conf.setuser = 0; @@ -1254,12 +1587,10 @@ int main(int ac, char **av){ 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; } @@ -1272,17 +1603,21 @@ 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:vOA")) != -1) { + while ((opt = getopt(ac, av, "f:d:c:nCR:vqOAMDp")) != -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 'q': conf.verbose--; break; 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; + case 'A': conf.accept = 1; break; + case 'M': conf.ignoredirmd = 1; + case 'D': conf.acceptdir = 0; + case 'p': conf.progress++; default: usage(); exit(EXIT_FAILURE); @@ -1311,8 +1646,6 @@ int main(int ac, char **av){ } } - /* 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", @@ -1332,7 +1665,7 @@ int main(int ac, char **av){ /* no point in running it if we're just going to * overwrite everything */ - if (!conf.overwrite && !conf.absorb && !conf.dryrun) { + if (!conf.overwrite && !conf.accept && !conf.dryrun) { runstage(&conf, "new", check_existing); } @@ -1340,6 +1673,11 @@ int main(int ac, char **av){ fprintf(stderr, "beginning %ssync\n", conf.dryrun ? "dryrun " : ""); } + + if (!conf.errors) { + handle_config_files(&conf); + } + /* have to do the removes first otherwise * old files may conflict with update file * type changes @@ -1347,20 +1685,49 @@ int main(int ac, char **av){ if (!conf.errors) { conf.exitonerror = conf.dryrun ? 0 : 1; conf.errabort = conf.dryrun ? 0 : 1; - conf.reverse = 1; - if (conf.verbose) { - fprintf(stderr, "removing old files\n"); + count_ops(&conf); + fprintf(stderr, "file ops: %lu\n", conf.ops_total); + if (conf.ops_remove > 0) { + if (conf.verbose) { + fprintf(stderr, "removing %lu file%s\n", conf.ops_remove, conf.ops_remove > 1 ? "s" : ""); + } + conf.reverse = 1; + conf.ops_completed = 0; + conf.ops_total = conf.ops_remove; + runstage(&conf, "remove", remove_files); + if (conf.verbose && conf.progress < 2) { + fprintf(stderr, " done\n"); + fflush(stderr); + } } - runstage(&conf, "remove", remove_files); - conf.reverse = 0; - if (conf.verbose) { - fprintf(stderr, "updating files\n"); + + if (conf.ops_update > 0) { + if (conf.verbose) { + fprintf(stderr, "updating %lu file%s\n", conf.ops_update, conf.ops_update > 1 ? "s" : ""); + } + conf.reverse = 0; + conf.ops_completed = 0; + conf.ops_total = conf.ops_update; + runstage(&conf, "update", install_files); + if (conf.verbose && conf.progress < 2) { + fprintf(stderr, " done\n"); + fflush(stderr); + } } - runstage(&conf, "update", install_files); - if (conf.verbose) { - fprintf(stderr, "installing files\n"); + + if (conf.ops_install > 0) { + if (conf.verbose) { + fprintf(stderr, "installing %lu file%s\n", conf.ops_install, conf.ops_install > 1 ? "s" : ""); + } + conf.reverse = 0; + conf.ops_completed = 0; + conf.ops_total = conf.ops_install; + runstage(&conf, "new", install_files); + if (conf.verbose && conf.progress < 2) { + fprintf(stderr, " done\n"); + fflush(stderr); + } } - runstage(&conf, "new", install_files); } }