X-Git-Url: https://pd.if.org/git/?a=blobdiff_plain;f=zpm-syncfs.c;h=c2ee8463b9eeeef7cd522b13ceb544616dec414f;hb=63339873b48653db56a74dc6b08e73608d793bdf;hp=0a0f1d51d1343d6c636f5f8c8719bdcc61cde37a;hpb=6adc79aeafb90e90c9923b3eeedab662888927e9;p=zpackage diff --git a/zpm-syncfs.c b/zpm-syncfs.c index 0a0f1d5..c2ee846 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 @@ -25,15 +27,93 @@ struct config { struct zpm *src; char *dbfile; char *rootdir; - int errabort, errors, verbose, dryrun; + int errabort, errors, verbose, dryrun, conflicts; int setuser, setgroup; int reverse, exitonerror; + int overwrite, accept, acceptdir, ignoredirmd; +}; + +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; @@ -137,75 +224,368 @@ static int getop(char *opstr) { static int report_conflicts(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; - char *path; - char *pkg; + char *path, *hash, *pkg, *conflict_type, *mds; pkg = COL("pkgid"); path = COL("path"); + conflict_type = COL("conflict"); + if (!strcmp(conflict_type, "hash")) { + hash = COL("hash"); + fprintf(stderr, "hash conflict: package %s path %s hash %.8s\n", + pkg, path, hash); + } else + if (!strcmp(conflict_type, "md")) { + mds = COL("mds"); + fprintf(stderr, "md conflict: package %s path %s md %s\n", + pkg, path, mds); + } else { + fprintf(stderr, "%s conflict: package %s path %s\n", + conflict_type, pkg, path); + } - conf->errors++; - fprintf(stderr, "%s owned by %s\n", path, pkg); + conf->conflicts++; 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 }; - path = COL("dest"); - if (!path) IERR("can't check for existing"); + *n = zero; - if (conf->dryrun) { - printf("checkfor %s\n", 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->verbose) { - fprintf(stderr, "check for existing %s\n", path); + 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 (lstat(path, &st) == 0) { - fprintf(stderr, "%s exists \n", path); - conf->errors++; + /* TODO config to dishonor setuid/setgid */ + n->dest = COL("dest"); + if (!n->dest) { + seterror(conf, "no file dest"); + return 0; + } + + if (strlen(n->dest) == 0) { + seterror(conf, "zero length dest not allowed"); + return 0; + } + + val = COL("mode"); + + if (!val) { + seterror(conf, "can't determine mode"); + return 0; + } + + 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"); + 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) { + 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 (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(); + } + + 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 +/* 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) { - /* 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; + case ENOENT: diff |= D_NOEXIST; break; + default: diff |= (D_STATERROR|D_ERROR); break; } } - return 0; + + return diff; +} + + +/* 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); + + if (!exist) { + return op == OP_REMOVE ? 2 : 1; + } + + if (op == OP_UPDATE) { + return sametype ? 4 : 3; + } + + if (op == OP_REMOVE) { + return 1; + } + + /* the hard cases, should be installing new, but already exists */ + + if (!sametype) { + return conf->overwrite ? 3 : 0; + } + + if (mdsame && (conf->accept || conf->overwrite)) { + return 1; + } + + if (isdir) { + if (mdsame || conf->ignoredirmd) { + return conf->acceptdir ? 2 : 0; + } + if (conf->overwrite) { + return 4; + } + } + + if (hashsame && (conf->accept || conf->overwrite)) { + return 1; + } + + return conf->overwrite ? 3 : 0; } -static int update_files(void *f, int ncols, char **vals, char **cols) { +static int check_existing(void *f, int ncols, char **vals, char **cols) { struct config *conf = f; - char *pkg; - char *path; - char *dest; + struct stat st; + struct nitem nitem; - 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"); + if (!read_item(conf, ncols, vals, cols, &nitem)) { + fprintf(stderr, "can't read item\n"); + return conf->errabort; + } - /* hash is different, so no different than install, - * but we don't need to create leading directories - */ + if (conf->verbose > 1) { + fprintf(stderr, "check for existing %s\n", nitem.path); + } - if (conf->dryrun) { - fprintf(stderr, "update %s\n", dest); + 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; } - fprintf(stderr, "update not implemented: %s", dest); - conf->errors++; + unsigned int diffs = file_compare(&nitem, &st); + if (diffs >= D_ERROR) { + return seterror(conf, "can't check %s", nitem.dest); + } + + 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); + } + conf->errors++; + } return 0; } @@ -214,9 +594,10 @@ 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"); @@ -226,173 +607,644 @@ 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)) { - 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); - } - unlink(dest); - } else { - unlink(dest); + 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"); + } } return 0; } -static int install_files(void *f, int ncols, char **vals, char **cols) { - struct config *conf = f; - struct passwd *pw; - struct group *gr; +#define MARK fprintf(stderr, "%s %d: mark\n", __func__, __LINE__) - 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; - /* 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"); +static int remove_dir(struct config *conf, char *path) { + int rv; - /* 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"); + rv = rmdir(path); + if (rv == -1) { + setsyserr(conf, "can't rmdir %s", path); + return 0; } - dest = COL("dest"); - if (!dest) IERR("no file dest"); - if (strlen(dest) == 0) { - IERR("zero length dest not allowed"); + return 1; +} + +static int remove_existing(struct config *conf, char *path) { + int rv; + + rv = unlink(path); + if (rv == -1) { + setsyserr(conf, "can't unlink %s", path); + return 0; } + return 1; +} - val = COL("mode"); +static int set_md(struct config *conf, struct nitem *item) { + int rv; + int success = 0; - if (!val) IERR("can't determine mode"); - mode = strtoul(val, NULL, 8); + 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; + } - val = COL("filetype"); - if (!val || strlen(val) == 0) { - IERR("can't determine file type"); + /* 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; + } } - ftype = *val; - if (ftype == 'r') { - hash = COL("hash"); - if (!hash) IERR("can't get hash"); + 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; + } } - if (conf->verbose) { - fprintf(stderr, "installing '%c' %s\n", ftype, dest); + 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; +} - uid = getuid(); - gid = getgid(); +/* 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->setuser) { - val = COL("username"); - if (!val) IERR("no username"); - pw = getpwnam(val); - if (!pw) IERR("no passwd entry"); - uid = pw->pw_uid; + 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; } - if (conf->setgroup) { - val = COL("groupname"); - if (!val) IERR("no groupname"); - gr = getgrnam(val); - if (!gr) IERR("no group entry"); - gid = gr->gr_gid; + + 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 (conf->dryrun) { - //printf("cld %s\n", path); - printf("new %c%o %d:%d %s -> %s\n", ftype, mode, uid, gid, path, dest); - return 0; + if (rv != 1) { + return failure; } - /* 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"); + if (mkleading) { + rv = create_leading_dirs(item->dest); + if (!rv) { + setsyserr(conf, "can't create leading dirs for %s", item->dest); + return failure; + } } - if (ftype == 'd') { - if (mkdir(dest, mode) == -1) { - IERR("can't mkdir"); + 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 == '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); + 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; +} + +/* + * 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 (!zpm_extract(source, hash, dest, mode)) { - IERR("can't extract file"); + 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); } - } else if (ftype == 'l') { - char *target = COL("target"); - if (!target) { - fprintf(stderr, "no target for symlink %s\n", path); - conf->errors++; - return conf->errabort; + return 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; + 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->accept; + 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"); + 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; } - if (conf->verbose > 1) { - fprintf(stderr, "symlink %s -> %s\n", path, target); + /* 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 (symlink(target, path) == -1) { - IERR("can't symlink"); + if (!isdir && eisdir) { + /* remove dir, or error */ + /* install */ + return install(conf, &nitem, 11); } - } else { - fprintf(stderr, "unhandled filetype %c\n", ftype); + if (!isdir && !isdir) { + /* necessarily !sametype, sametype handled above */ + /* remove existing */ + /* install */ + return install(conf, &nitem, 7); + } + /* error, should not be possible, assert(0)? */ + fprintf(stderr,"impossible state: %s:%d\n", __func__, __LINE__); } - if (conf->setuser && conf->setgroup) { - if (chown(dest, uid, gid) == -1) { - IERR("can't chown"); + if (installing) { + if (!exist) { + return install(conf, &nitem, 3); } - } - struct timespec times[2] = { 0 }; - double mtime = strtod(COL("mtime"),NULL); + /* 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; + } + + if (mdsame && isdir && conf->acceptdir) { + return 0; + } + + if (!mdsame && isdir && conf->ignoredirmd) { + /* TODO warn ignoring dir md */ + return 0; + } + + 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; + } - 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 && !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); + } - utimensat(AT_FDCWD, dest, times, AT_SYMLINK_NOFOLLOW); + /* 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; } +static void check_conflicts(struct config *conf, char *conflict_type, + int (callback)(void *, int, char **, char **)) { + 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 syncconflicts"); + + if (conflict_type) { + sqlite3_str_appendf(s," where conflict = %Q", conflict_type); + } + if (conf->reverse) { + sqlite3_str_appendall(s," order by length(path) desc, path desc,pkgid collate vercmp desc, conflict desc"); + } else { + sqlite3_str_appendall(s," order by length(path), path, pkgid collate vercmp, conflict"); + + } + + sql = sqlite3_str_value(s); + + rv = zpm_exec(conf->log, sql, callback, 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++; + } + if (conf->log->errmsg) { + fprintf(stderr, "error: %s\n", conf->log->errmsg); + } + if (conf->errors && conf->exitonerror) { + zpm_close(conf->log); + zpm_close(conf->src); + exit(EXIT_FAILURE); + } + /* TODO final report function in conf var */ +} + static void runstage(struct config *conf, char *stage, int (callback)(void *, int, char **, char **)) { int rv; @@ -407,7 +1259,7 @@ static void runstage(struct config *conf, char *stage, } else { sqlite3_str_appendf(s, "printf('/%%s', trim(path, '/'))"); } - sqlite3_str_appendall(s, " as dest from install_status"); + sqlite3_str_appendall(s, " as dest from syncinfo"); if (stage) { sqlite3_str_appendf(s," where op = %Q", stage); @@ -417,9 +1269,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); @@ -438,9 +1287,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); @@ -449,7 +1300,7 @@ static void runstage(struct config *conf, char *stage, /* TODO final report function in conf var */ } -int main(int ac, char **av){ +int main(int ac, char **av) { struct zpm localdb; struct zpm pkgdb; int opt; @@ -460,6 +1311,7 @@ int main(int ac, char **av){ conf.errabort = 1; conf.errors = 0; + conf.conflicts = 0; conf.verbose = 0; conf.dryrun = 0; conf.setuser = 1; @@ -468,6 +1320,9 @@ int main(int ac, char **av){ conf.src = 0; conf.rootdir = 0; conf.reverse = 0; + conf.overwrite = 0; + conf.accept = 0; + conf.acceptdir = 1; if (geteuid() != 0) { conf.setuser = 0; @@ -494,7 +1349,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:vOAMD")) != -1) { switch (opt) { case 'd': localdbfile = optarg; break; case 'f': pkgdbfile = optarg; break; @@ -503,6 +1358,10 @@ 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.accept = 1; break; + case 'M': conf.ignoredirmd = 1; + case 'D': conf.acceptdir = 0; default: usage(); exit(EXIT_FAILURE); @@ -522,6 +1381,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); @@ -541,16 +1401,46 @@ int main(int ac, char **av){ conf.errors = 0; conf.exitonerror = 0; - runstage(&conf, "conflict", report_conflicts); - runstage(&conf, "new", check_existing); - - if (!conf.errors) { - conf.exitonerror = 1; - runstage(&conf, "new", install_files); - runstage(&conf, "update", update_files); - conf.reverse = 1; - runstage(&conf, "remove", remove_files); - conf.reverse = 0; + check_conflicts(&conf, NULL, report_conflicts); + + if (conf.conflicts) { + fprintf(stderr, "%d conflicts reported, aborting sync\n", + conf.conflicts); + conf.errors++; + } else { + /* no point in running it if we're just going to + * overwrite everything + */ + if (!conf.overwrite && !conf.accept && !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 = 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); + } } zpm_close(&localdb);