X-Git-Url: https://pd.if.org/git/?a=blobdiff_plain;f=zpm-syncfs.c;h=0b88928d456ae875bd940a8d1fdabdf65c0f30ec;hb=6fbdbc82ac30e6beea0e5a5ce0b6fd11311abe2c;hp=c08407e8c455efb0aa2d0be18da794323d64e7ff;hpb=e7ce7c71c84e3e2a394562a6adec73062759f5df;p=zpackage diff --git a/zpm-syncfs.c b/zpm-syncfs.c index c08407e..0b88928 100644 --- a/zpm-syncfs.c +++ b/zpm-syncfs.c @@ -12,6 +12,7 @@ #include #include #include +#include /* needed for S_IFMT and AT_FDCWD */ #include @@ -32,6 +33,24 @@ struct config { int overwrite, absorb; }; +struct nitem { + int op; + char *opstr; + uid_t uid; + gid_t gid; + char *dest; + char *path; + char *hash, *ohash; + char *mds, *omds; + char *target; + char *pkglist; /* space separated */ + time_t mtime; + mode_t mode; + int ftype; + int configuration, oldwasconf; + struct timespec times[2]; +}; + static void usage() { printf("usage: zpm $scriptname [-fncC] args ...\n"); } @@ -134,17 +153,18 @@ static int create_leading_dirs(char *path) { /* try to create the directory, if it exists * and is a directory or a symlink, that's ok + * should be (eventually) a symlink to a directory + * so we want stat here, not lstat */ if (mkdir(pcopy, 0755) == -1) { switch (errno) { case EEXIST: - if (lstat(pcopy, &st) == -1) { + if (stat(pcopy, &st) == -1) { /* can't stat? */ return 0; } switch (st.st_mode & S_IFMT) { case S_IFDIR: - case S_IFLNK: break; default: return 0; @@ -239,6 +259,7 @@ static int check_existing(void *f, int ncols, char **vals, char **cols) { if (conf->dryrun) { printf("checkfor %s\n", path); + fflush(stdout); return 0; } @@ -267,6 +288,7 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; char *dest; struct stat st; + int flags = 0; dest = COL("dest"); if (!dest) return seterror(conf,"no file dest"); @@ -279,6 +301,7 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { case 'd': printf("rmdir %s\n", dest); break; default: printf("unlink %s\n", dest); break; } + fflush(stdout); return 0; } @@ -287,21 +310,22 @@ static int remove_files(void *f, int ncols, char **vals, char **cols) { } 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) { - return seterror(conf, "can't unlink"); - } - } else { - if (unlink(dest) == -1) { - return seterror(conf, "can't unlink"); + flags = AT_REMOVEDIR; + } + /* TODO check that expected filetype matches actual filetype */ + + if (conf->verbose) { + fprintf(stderr, "%s(%s)\n", flags ? "rmdir" : "unlink", dest); + } + + errno = 0; + + if (unlinkat(AT_FDCWD, dest, flags) == -1) { + switch (errno) { + case ENOENT: + break; + default: + return seterror(conf, "can't unlink"); } } @@ -310,32 +334,35 @@ 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]; -}; - +/* file does not exist */ #define D_NOEXIST 0x1 +/* files are different types */ #define D_TYPE 0x2 +/* metadata is different */ #define D_MD 0x4 +/* content or link target is different */ #define D_HASH 0x8 +/* file to be installed is a directory */ #define D_ISDIR 0x10 +/* path on disk is a directory */ #define D_EISDIR 0x20 +/* usernames different */ #define D_UID 0x40 +/* group names different */ #define D_GID 0x80 +/* file mode is different */ #define D_MODE 0x100 +/* mtimes are different */ #define D_MTIME 0x200 +/* the hash of the file we are supposedly replacing is different than + * the the hash of the file on disk + */ +#define D_OHASH 0x400 +/* an error occurred trying to compare the file (other than it doesn't exist */ #define D_ERROR 0x1000 +/* there was a stat error */ #define D_STATERROR 0x2000 +/* there was an error calling readlink */ #define D_RLERROR 0x4000 /* 1 = file doesn't exist, 2 = file is a directory, target isn't */ @@ -371,14 +398,21 @@ static unsigned int file_compare(struct nitem *n, struct stat *st) { if (strcmp(n->hash, ehash) != 0) { diff |= D_HASH; } + if (n->ohash && strcmp(n->ohash, ehash) != 0) { + diff |= D_OHASH; + } } 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; + } else { + link[lsize] = 0; + if (strcmp(n->target, link) != 0) { + diff |= D_HASH; + } } } if (n->uid != st->st_uid) { @@ -406,6 +440,7 @@ static unsigned int file_compare(struct nitem *n, 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 }; @@ -417,6 +452,7 @@ static int read_item(struct config *conf, int ncols, char **vals, char **cols, seterror(conf, "can't determine op"); return 0; } + n->opstr = val; n->op = getop(val); if (!n->op) { seterror(conf, "can't determine op"); @@ -454,6 +490,16 @@ static int read_item(struct config *conf, int ncols, char **vals, char **cols, n->mode = strtoul(val, NULL, 8); + val = COL("configuration"); + if (!val) { + seterror(conf, "can't determine config status"); + return 0; + } + lval = strtol(val, NULL, 10); + + n->configuration = ((lval & 1) != 0); + n->oldwasconf = ((lval & 2) != 0); + val = COL("filetype"); if (!val || strlen(val) == 0) { seterror(conf, "can't determine file type"); @@ -461,6 +507,12 @@ static int read_item(struct config *conf, int ncols, char **vals, char **cols, } n->ftype = *val; + /* 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) { @@ -512,7 +564,13 @@ static int read_item(struct config *conf, int ncols, char **vals, char **cols, n->gid = getegid(); } + 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; @@ -546,24 +604,43 @@ static int remove_existing(struct config *conf, char *path) { static int set_md(struct config *conf, struct nitem *item) { int rv; + int success = 0; - rv = chmod(item->path, item->mode); - if (rv == -1) { - setsyserr(conf, "can't chmod"); - return conf->errabort; + if (conf->dryrun) { + if (item->ftype != 'l') { + printf("chmod %o %s\n", item->mode, item->dest); + } + if (conf->setuser && conf->setgroup) { + printf("lchown %d:%d %s\n", item->uid, item->gid, + item->dest); + } + printf("mtime %.0f %s\n", (double)item->mtime, item->dest); + fflush(stdout); + return success; + } + + /* can't chmod a symlink */ + if (item->ftype != 'l') { + rv = chmod(item->dest, item->mode); + + if (rv == -1) { + setsyserr(conf, "can't chmod %o %s", item->mode, item->dest); + return conf->errabort; + } } if (conf->setuser && conf->setgroup) { - rv = chown(item->path, item->uid, item->gid); + rv = lchown(item->dest, item->uid, item->gid); if (rv == -1) { - setsyserr(conf, "can't chown %s", item->dest); + setsyserr(conf, "can't lchown %s", item->dest); return conf->errabort; } } rv = utimensat(AT_FDCWD, item->dest, item->times, AT_SYMLINK_NOFOLLOW); if (rv == -1) { - setsyserr(conf, "can't set mtime"); + setsyserr(conf, "can't set mtime %.0f %s", (double)item->mtime, + item->dest); return conf->errabort; } return 0; @@ -575,6 +652,12 @@ static int set_md(struct config *conf, struct nitem *item) { /* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file, * 8 = rmdir existing dir, 16 = return true/false */ +#define INS_MD 0x1 +#define INS_CLD 0x2 +#define INS_UNLINK 0x4 +#define INS_RMDIR 0x8 +#define INS_RTF 0x10 +#define INS_ZPMNEW 0x20 static int install(struct config *conf, struct nitem *item, unsigned int flags) { int rv = 1; struct zpm *source; @@ -591,6 +674,24 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) success = 1; } + if (conf->dryrun) { + if (unlink_file) { + printf("unlink %s\n", item->dest); + } else if (rm_dir) { + printf("rmdir %s\n", item->dest); + } + + printf("install %c%o %d:%d %s", item->ftype, + item->mode, item->uid, item->gid, + item->dest); + if (item->ftype == 'l') { + printf(" -> %s", item->target); + } + printf("\n"); + fflush(stdout); + return success; + } + source = conf->src ? conf->src : conf->log; if (unlink_file) { @@ -641,6 +742,121 @@ static int install(struct config *conf, struct nitem *item, unsigned int flags) return success; } +/* + * figure out what the difference is for a config file, only called + * for an update of a configuration file + * return -1 on an error + * 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) { + + if (!n->oldwasconf) { + return 0; + } + + /* TODO 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; + } + + if (eisdir) { + /* replacing old conf directory with a conf file. + * nothing needs to be done, if the directory + * is empty, it's ok to remove. if it's not empty, + * the install will fail + */ + return 0; + } + + /* 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"); + } + 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 */ + } else { + fprintf(stderr, "dry-run: default config file %s update\n", + n->dest); + } + return 1; + +} + static int install_files(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; struct nitem nitem; @@ -655,26 +871,16 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { return conf->errabort; } - if (conf->verbose) { - fprintf(stderr, "%d '%c' %s\n", nitem.op, nitem.ftype, + if (conf->verbose && !conf->dryrun) { + fprintf(stderr, "%s '%c' %s\n", nitem.opstr, 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 @@ -717,15 +923,39 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { 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) { - 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 + /* warn, bug in logic. This shouldn't occur, + * because if there is nothing to do, it + * shouldn't be listed as an update + */ + /* could be an update. We're checking against + * what's actually on disk, not what was + * expected to have been on disk. So, if + * the admin has modified the file, or if + * it had been installed ignoring the user + * 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 + */ +#if 0 + fprintf(stderr, "%s should not be an update\n", nitem.dest); + fprintf(stderr, "old hash: %s\n", nitem.ohash); + fprintf(stderr, "new hash: %s\n", nitem.hash); + fprintf(stderr, "old mds: %s\n", nitem.omds); + fprintf(stderr, "new mds: %s\n", nitem.mds); +#endif /* do nothing */ return 0; } @@ -762,6 +992,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { return install(conf, &nitem, 7); } /* error, should not be possible, assert(0)? */ + fprintf(stderr,"impossible state: %s:%d\n", __func__, __LINE__); } if (installing) { @@ -773,21 +1004,50 @@ 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) { + fprintf(stderr, "accept %s: %s\n", + eisdir ? "directory" : "file", nitem.dest); + } return 0; } + if (mdsame && hashsame && !(accept || overwrite)) { /* error */ - return seterror(conf, "bad conditions"); + return seterror(conf, "file exists: %s", nitem.dest); } + if (mdsame && !hashsame && overwrite) { /* install */ return install(conf, &nitem, eisdir ? 11 : 7); } + + if (nitem.configuration && accept) { + /* accept a changed config file */ + if (conf->dryrun || conf->verbose) { + fprintf(stderr, "accept %smodified config %s: %s\n", (!mdsame || !hashsame) ? "" : "un", + eisdir ? "directory" : "file", nitem.dest); + } + return 0; + } + 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 (nitem.ftype == 'l') { + char link[1024]; + ssize_t lsize; + lsize = readlink(nitem.dest, link, sizeof link); + if (lsize == -1 || (size_t)lsize >= sizeof link) { + return seterror(conf, "%s (linkdiff): expecting %s -> %s, unable to read link", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link); + } else { + link[lsize] = 0; + /* links must be different */ + return seterror(conf, "%s (linkdiff): expecting %s -> %s, have -> %s", accept ? "existing file not acceptable" : "file exists", nitem.dest, nitem.target, link); + } + } else { + return seterror(conf, "%s (hashdiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); + } } if (!mdsame && hashsame && overwrite) { /* fix md */ @@ -797,7 +1057,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { /* accept doesn't matter, since it's * not an acceptable file */ /* error */ - return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); } if (!mdsame && !hashsame && overwrite) { /* install */ @@ -807,7 +1067,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { /* accept doesn't matter, since it's * not an acceptable file */ /* error */ - return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + return seterror(conf, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); } /* TODO error, should be impossible */ return seterror(conf, "impossible state reached"); @@ -816,7 +1076,7 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { /* file exists, and is not the same type */ if (!overwrite) { /* error */ - return seterror(conf, accept ? "existing file not acceptable" : "file exists"); + return seterror(conf, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); } /* not the same type, but ok to overwrite */ @@ -843,6 +1103,9 @@ static int install_files(void *f, int ncols, char **vals, char **cols) { return seterror(conf, "impossible state 2 reached"); } + /* TODO extra verbose print perms, mtime, etc, probably ls -l + * format + */ if (conf->verbose) { printf("%s\n", nitem.path); } @@ -877,9 +1140,6 @@ static void check_conflicts(struct config *conf, char *conflict_type, } 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); @@ -933,9 +1193,6 @@ static void runstage(struct config *conf, char *stage, } 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); @@ -954,9 +1211,11 @@ static void runstage(struct config *conf, char *stage, conf->log->errmsg ? conf->log->errmsg : "unknown"); conf->errors++; } +#if 0 if (conf->log->errmsg) { fprintf(stderr, "error: %s\n", conf->log->errmsg); } +#endif if (conf->errors && conf->exitonerror) { zpm_close(conf->log); zpm_close(conf->src); @@ -1043,6 +1302,7 @@ int main(int ac, char **av){ conf.log = &localdb; if (pkgdbfile) { + /* TODO open read-only */ if (!zpm_open(&pkgdb, pkgdbfile)) { fprintf(stderr, "can't open src db %s\n", localdbfile); exit(EXIT_FAILURE); @@ -1063,6 +1323,7 @@ int main(int ac, char **av){ conf.errors = 0; conf.exitonerror = 0; check_conflicts(&conf, NULL, report_conflicts); + if (conf.conflicts) { fprintf(stderr, "%d conflicts reported, aborting sync\n", conf.conflicts); @@ -1071,32 +1332,34 @@ int main(int ac, char **av){ /* no point in running it if we're just going to * overwrite everything */ - if (conf.overwrite == 0 || conf.absorb == 0) { + if (!conf.overwrite && !conf.absorb && !conf.dryrun) { runstage(&conf, "new", check_existing); } if (conf.verbose) { - fprintf(stderr, "beginning sync\n"); + fprintf(stderr, "beginning %ssync\n", conf.dryrun ? + "dryrun " : ""); } /* have to do the removes first otherwise * old files may conflict with update file * type changes */ if (!conf.errors) { - conf.exitonerror = 1; + conf.exitonerror = conf.dryrun ? 0 : 1; + conf.errabort = conf.dryrun ? 0 : 1; conf.reverse = 1; - if (conf.verbose) { - fprintf(stderr, "removing old files\n"); - } + 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"); - } + if (conf.verbose) { + fprintf(stderr, "updating files\n"); + } runstage(&conf, "update", install_files); - if (conf.verbose) { - fprintf(stderr, "installing files\n"); - } + if (conf.verbose) { + fprintf(stderr, "installing files\n"); + } runstage(&conf, "new", install_files); } }