X-Git-Url: https://pd.if.org/git/?a=blobdiff_plain;f=zpm-syncfs.c;h=ce0c0bb85f20fa5777a092f0dec92e8731b12b0e;hb=8725eb08d773c71a6d9ec2ca6770193e978d3a8d;hp=c77b310308e21843449a6c701249137665af925e;hpb=fb90e164dd3560402a7acb757aea5abb888f7869;p=zpackage diff --git a/zpm-syncfs.c b/zpm-syncfs.c index c77b310..ce0c0bb 100644 --- a/zpm-syncfs.c +++ b/zpm-syncfs.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include /* needed for S_IFMT and AT_FDCWD */ #include @@ -28,12 +30,90 @@ struct config { int errabort, errors, verbose, dryrun, conflicts; int setuser, setgroup; int reverse, exitonerror; + 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"); } +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; @@ -73,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; @@ -102,7 +183,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 +198,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,10 +253,13 @@ 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); + fflush(stdout); return 0; } @@ -178,7 +268,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 +284,14 @@ 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; + int flags = 0; dest = COL("dest"); - if (!dest) IERR("no file dest"); + if (!dest) return seterror(conf,"no file dest"); if (conf->dryrun) { char *ftype = COL("filetype"); @@ -238,29 +301,31 @@ 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; } if (lstat(dest, &st) == -1) { - IERR("can't stat"); + return seterror(conf,"can't stat"); } if (S_ISDIR(st.st_mode)) { - if (conf->verbose) { - fprintf(stderr, "rmdir(%s)\n", dest); - } - rmdir(dest); - } else if (S_ISREG(st.st_mode)) { - /* TODO conf to import before removal */ - if (conf->verbose) { - fprintf(stderr, "unlink(%s)\n", dest); - } - if (unlink(dest) == -1) { - IERR("can't unlink"); - } - } else { - if (unlink(dest) == -1) { - IERR("can't unlink"); + 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"); } } @@ -269,146 +334,786 @@ 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; +/* 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 */ +/* 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->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 { + link[lsize] = 0; + 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; + long lval; 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->opstr = val; + 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); - - val = COL("filetype"); - if (!val || strlen(val) == 0) { - IERR("can't determine file type"); + if (!val) { + seterror(conf, "can't determine mode"); + return 0; } - ftype = *val; - if (ftype == 'r') { - hash = COL("hash"); - if (!hash) IERR("can't get hash"); - } + n->mode = strtoul(val, NULL, 8); - if (conf->verbose) { - fprintf(stderr, "installing '%c' %s\n", ftype, dest); + 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); - uid = getuid(); - gid = getgid(); + val = COL("filetype"); + if (!val || strlen(val) == 0) { + seterror(conf, "can't determine file type"); + return 0; + } + 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) { + 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; + } 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 for %s", val); + 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); + 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; +} + +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)) { - fprintf(stderr, "unable to create leading directories for %s\n", - dest); - IERR("cld failure"); + 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; + int success = 0; - if (ftype == 'd') { - if (mkdir(dest, mode) == -1) { - IERR("can't mkdir"); + if (conf->dryrun) { + if (item->ftype != 'l') { + printf("chmod %o %s\n", item->mode, item->dest); } - } else if (ftype == 'r') { - struct zpm *source; - source = conf->src ? conf->src : conf->log; - if (conf->verbose > 1) { - fprintf(stderr, "extracting %8.8s to %s with mode %o\n", - hash, dest, mode); + if (conf->setuser && conf->setgroup) { + printf("lchown %d:%d %s\n", item->uid, item->gid, + item->dest); } - if (!zpm_extract(source, hash, dest, mode)) { - IERR("can't extract file"); + 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; } - } else if (ftype == 'l') { - char *target = COL("target"); - if (!target) { - fprintf(stderr, "no target for symlink %s\n", path); - conf->errors++; + } + + if (conf->setuser && conf->setgroup) { + rv = lchown(item->dest, item->uid, item->gid); + if (rv == -1) { + 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 %.0f %s", (double)item->mtime, + item->dest); + 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 + */ +#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; + + 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; + } + + 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) { + 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 (strlen(target) == 0) { - IERR("zero length symlink not allowed"); + 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; + } + + 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 (conf->verbose > 0) { - fprintf(stderr, "symlink %s -> %s\n", dest, target); + if (rv == -1) { + setsyserr(conf, "installing %s failed", item->dest); + return failure; + } + + if (setmd) { + return set_md(conf, item) == 0 ? success : failure; + } + + 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); } - if (symlink(target, dest) == -1) { - perror("symlink failed"); - IERR("can't symlink"); + 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, "unhandled filetype %c\n", ftype); + fprintf(stderr, "dry-run: default config file %s update\n", + n->dest); } + return 1; - if (conf->setuser && conf->setgroup) { - if (chown(dest, uid, gid) == -1) { - IERR("can't chown"); +} + +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 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 + if (conf->verbose && !conf->dryrun) { + fprintf(stderr, "%s '%c' %s\n", nitem.opstr, nitem.ftype, + nitem.dest); + } + + unsigned int diffs = file_compare(&nitem, &existing); + if (diffs >= D_ERROR) { + return seterror(conf, "can't check %s", nitem.dest); + } + + /* 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); + } + + 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) { + /* 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; + } + 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); + } + } + + /* file exists, and is not the same type */ + + if (isdir && !eisdir) { + /* remove existing */ + /* mkdir */ + return install(conf, &nitem, 7); + } + if (!isdir && eisdir) { + /* remove dir, or error */ + /* install */ + return install(conf, &nitem, 11); } + if (!isdir && !isdir) { + /* necessarily !sametype, sametype handled above */ + /* remove existing */ + /* install */ + return install(conf, &nitem, 7); + } + /* error, should not be possible, assert(0)? */ + fprintf(stderr,"impossible state: %s:%d\n", __func__, __LINE__); } - struct timespec times[2] = { {0}, {0} }; - double mtime = strtod(COL("mtime"),NULL); + if (installing) { + if (!exist) { + return install(conf, &nitem, 3); + } + + /* file exists in filesystem */ + 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; + } - 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)); + if (mdsame && hashsame && !(accept || overwrite)) { + /* error */ + 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; + } - utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW); + if (mdsame && !hashsame && !overwrite) { + /* accept doesn't matter, since it's + * not an acceptable file */ + /* error */ + 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 */ + return set_md(conf, &nitem); + } + if (!mdsame && hashsame && !overwrite) { + /* accept doesn't matter, since it's + * not an acceptable file */ + /* error */ + return seterror(conf, "%s (mddiff): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); + } + 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, "%s (md+hash): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); + } + /* 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, "%s (difftype): %s", accept ? "existing file not acceptable" : "file exists", nitem.dest); + } + /* not the same type, but ok to overwrite */ + if (!eisdir) { + /* remove existing */ + return install(conf, &nitem, 7); + } + + /* 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"); + } + + /* TODO extra verbose print perms, mtime, etc, probably ls -l + * format + */ if (conf->verbose) { - printf("%s\n", path); + printf("%s\n", nitem.path); } return 0; @@ -441,9 +1146,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); @@ -497,9 +1199,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); @@ -518,9 +1217,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); @@ -549,6 +1250,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 +1278,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 +1287,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); @@ -603,6 +1308,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); @@ -623,20 +1329,44 @@ 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); conf.errors++; } else { - runstage(&conf, "new", check_existing); + /* no point in running it if we're just going to + * overwrite everything + */ + if (!conf.overwrite && !conf.absorb && !conf.dryrun) { + runstage(&conf, "new", check_existing); + } + if (conf.verbose) { + 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; - runstage(&conf, "new", install_files); - runstage(&conf, "update", update_files); + conf.exitonerror = conf.dryrun ? 0 : 1; + conf.errabort = conf.dryrun ? 0 : 1; 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); } }