From 25d2bb605809d23b691ec88a138bf7da9c7ee20b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20H=C3=A4rdeman?= <david@hardeman.nu>
Date: Fri, 18 May 2007 21:54:15 +0200
Subject: Initial project checkin

---
 Makefile    |  18 ++
 metastore.c | 833 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 metastore.h |  40 +++
 trash       |   0
 utils.c     | 115 +++++++++
 utils.h     |  13 +
 6 files changed, 1019 insertions(+)
 create mode 100644 Makefile
 create mode 100644 metastore.c
 create mode 100644 metastore.h
 delete mode 100644 trash
 create mode 100644 utils.c
 create mode 100644 utils.h

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..43783e8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+CC       = gcc
+CFLAGS   = -g -Wall
+LDFLAGS  =
+INCLUDES =
+COMPILE  = $(CC) $(INCLUDES) $(CFLAGS)
+LINK     = $(CC) $(CFLAGS) $(LDFLAGS)
+
+objects = utils.o metastore.o
+
+%.o: %.c
+	$(COMPILE) -o $@ -c $<
+
+metastore: $(objects)
+	$(LINK) -o $@ $^
+
+clean:
+	rm -f *.o metastore
+.PHONY: clean
diff --git a/metastore.c b/metastore.c
new file mode 100644
index 0000000..3e0068c
--- /dev/null
+++ b/metastore.c
@@ -0,0 +1,833 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <attr/xattr.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <utime.h>
+
+#include "metastore.h"
+#include "utils.h"
+
+int verbosity = 0;
+int do_mtime = 0;
+
+int
+msg(int level, const char *fmt, ...)
+{
+	int ret;
+	va_list ap;
+
+	if (level > verbosity)
+		return 0;
+
+	va_start(ap, fmt);
+	ret = vfprintf(stderr, fmt, ap);
+	va_end(ap);
+	return ret;
+}
+
+void
+mentry_free(struct metaentry *m)
+{
+	int i;
+
+	if (!m)
+		return;
+
+	free(m->path);
+	free(m->owner);
+	free(m->group);
+
+	for (i = 0; i < m->xattrs; i++) {
+		free(m->xattr_names[i]);
+		free(m->xattr_values[i]);
+	}
+
+	free(m->xattr_names);
+	free(m->xattr_values);
+	free(m->xattr_lvalues);
+
+	free(m);
+}
+
+struct metaentry *
+mentry_alloc()
+{
+	struct metaentry *mentry;
+	mentry = xmalloc(sizeof(struct metaentry));
+	memset(mentry, 0, sizeof(struct metaentry));
+	return mentry;
+}
+
+void
+mentry_insert(struct metaentry *mentry, struct metaentry **mhead)
+{
+	struct metaentry *prev;
+	struct metaentry *curr;
+	int comp;
+
+	if (!(*mhead)) {
+		*mhead = mentry;
+		return;
+	}
+
+	if (strcmp(mentry->path, (*mhead)->path) < 0) {
+		mentry->next = *mhead;
+		*mhead = mentry;
+		return;
+	}
+
+	prev = *mhead;
+	for (curr = prev->next; curr; curr = curr->next) {
+		comp = strcmp(mentry->path, curr->path);
+		if (!comp)
+			/* Two matching paths */
+			return;
+		if (comp < 0)
+			break;
+		prev = curr;
+	}
+
+	if (curr)
+		mentry->next = curr;
+	prev->next = mentry;
+}
+
+void
+mentry_print(const struct metaentry *mentry)
+{
+	int i;
+
+	if (!mentry || !mentry->path) {
+		fprintf(stderr, "Incorrect meta entry passed to printmetaentry\n");
+		return;
+	}
+
+	printf("===========================\n");
+	printf("Dump of metaentry %p\n", mentry);
+	printf("===========================\n");
+
+	printf("path\t\t: %s\n", mentry->path);
+	printf("owner\t\t: %s\n", mentry->owner);
+	printf("group\t\t: %s\n", mentry->group);
+	printf("mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
+	printf("mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
+	printf("mode\t\t: %ld\n", (unsigned long)mentry->mode);
+	for (i = 0; i < mentry->xattrs; i++) {
+		printf("xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
+		binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
+		printf("\"\n");
+	}
+
+	printf("===========================\n\n");
+}
+
+void
+mentries_print(const struct metaentry *mhead)
+{
+	const struct metaentry *mentry;
+	int i;
+
+	for (mentry = mhead; mentry; mentry = mentry->next) {
+		i++;
+		mentry_print(mentry);
+	}
+
+	printf("%i entries in total\n", i);
+}
+
+struct metaentry *
+mentry_create(const char *path)
+{
+	ssize_t lsize, vsize;
+	char *list, *attr;
+	struct stat sbuf;
+	struct passwd *pbuf;
+	struct group *gbuf;
+	int i;
+	struct metaentry *mentry;
+
+	if (lstat(path, &sbuf)) {
+		perror("lstat");
+		return NULL;
+	}
+
+	pbuf = getpwuid(sbuf.st_uid);
+	if (!pbuf) {
+		perror("getpwuid");
+		return NULL;
+	}
+
+	gbuf = getgrgid(sbuf.st_gid);
+	if (!gbuf) {
+		perror("getgrgid");
+		return NULL;
+	}
+
+	mentry = mentry_alloc();
+	mentry->path = xstrdup(path);
+	mentry->owner = xstrdup(pbuf->pw_name);
+	mentry->group = xstrdup(gbuf->gr_name);
+	mentry->mode = sbuf.st_mode & 0177777;
+	mentry->mtime = sbuf.st_mtim.tv_sec;
+	mentry->mtimensec = sbuf.st_mtim.tv_nsec;
+
+	/* symlinks have no xattrs */
+	if (S_ISLNK(mentry->mode))
+		return mentry;
+	
+	lsize = listxattr(path, NULL, 0);
+	if (lsize < 0) {
+		perror("listxattr");
+		return NULL;
+	}
+
+	list = xmalloc(lsize);
+	lsize = listxattr(path, list, lsize);
+	if (lsize < 0) {
+		perror("listxattr");
+		return NULL;
+	}
+
+	i = 0;
+	for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
+		if (*attr == '\0')
+			continue;
+		i++;
+	}
+
+	if (i == 0)
+		return mentry;
+
+	mentry->xattrs = i;
+	mentry->xattr_names = xmalloc(i * sizeof(char *));
+	mentry->xattr_values = xmalloc(i * sizeof(char *));
+	mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t));
+
+	i = 0;
+	for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
+		if (*attr == '\0')
+			continue;
+
+		mentry->xattr_names[i] = xstrdup(attr);
+		vsize = getxattr(path, attr, NULL, 0);
+		if (vsize < 0) {
+			perror("getxattr");
+			return NULL;
+		}
+
+		mentry->xattr_lvalues[i] = vsize;
+		mentry->xattr_values[i] = xmalloc(vsize);
+
+		vsize = getxattr(path, attr, mentry->xattr_values[i], vsize);
+		if (vsize < 0) {
+			perror("getxattr");
+			return NULL;
+		}
+		i++;
+	}
+
+	return mentry;
+}
+
+char *
+normalize_path(const char *orig)
+{
+	char *real = canonicalize_file_name(orig);
+	char cwd[PATH_MAX];
+	char *result;
+
+	getcwd(cwd, PATH_MAX);
+	if (!real)
+		return NULL;
+
+	if (!strncmp(real, cwd, strlen(cwd))) {
+		result = xmalloc(strlen(real) - strlen(cwd) + 1 + 1);
+		result[0] = '\0';
+		strcat(result, ".");
+		strcat(result, real + strlen(cwd));
+	} else {
+		result = xstrdup(real);
+	}
+
+	free(real);
+	return result;
+}
+
+void
+mentries_recurse(const char *opath, struct metaentry **mhead)
+{
+	struct stat sbuf;
+	struct metaentry *mentry;
+	char tpath[PATH_MAX];
+	DIR *dir;
+	struct dirent *dent;
+	char *path = normalize_path(opath);
+
+	if (!path)
+		return;
+
+	if (lstat(path, &sbuf)) {
+		printf("Failed to stat %s\n", path);
+		goto out;
+	}
+
+	mentry = mentry_create(path);
+	if (!mentry) {
+		printf("Failed to get metadata for %s\n", path);
+		goto out;
+	}
+
+	mentry_insert(mentry, mhead);
+
+	if (S_ISDIR(sbuf.st_mode)) {
+		dir = opendir(path);
+		if (!dir) {
+			printf("Failed to open dir %s\n", path);
+			return;
+		}
+
+		while ((dent = readdir(dir))) {
+			if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
+				continue;
+			snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
+			tpath[PATH_MAX - 1] = '\0';
+			mentries_recurse(tpath, mhead);
+		}
+
+		closedir(dir);
+	}
+
+out:
+	free(path);
+}
+
+void 
+mentries_tofile(const struct metaentry *mhead, const char *path)
+{
+	FILE *to;
+	const struct metaentry *mentry;
+	int i;
+
+	to = fopen(path, "w");
+	if (!to) {
+		perror("fopen");
+		exit(EXIT_FAILURE);
+	}
+
+	write_binary_string(SIGNATURE, SIGNATURELEN, to);
+	write_binary_string(VERSION, VERSIONLEN, to);
+
+	for (mentry = mhead; mentry; mentry = mentry->next) {
+		write_string(mentry->path, to);
+		write_string(mentry->owner, to);
+		write_string(mentry->group, to);
+		write_int((uint64_t)mentry->mtime, 8, to);
+		write_int((uint64_t)mentry->mtimensec, 8, to);
+		write_int((uint64_t)mentry->mode, 2, to);
+		write_int(mentry->xattrs, 4, to);
+		for (i = 0; i < mentry->xattrs; i++) {
+			write_string(mentry->xattr_names[i], to);
+			write_int(mentry->xattr_lvalues[i], 4, to);
+			write_binary_string(mentry->xattr_values[i], mentry->xattr_lvalues[i], to);
+		}
+	}
+
+	fclose(to);
+}
+
+void
+mentries_fromfile(struct metaentry **mhead, const char *path)
+{
+	struct metaentry *mentry;
+	char *mmapstart;
+	char *ptr;
+	char *max;
+	int fd;
+	struct stat sbuf;
+	int i;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		perror("open");
+		exit(EXIT_FAILURE);
+	}
+
+	if (fstat(fd, &sbuf)) {
+		perror("fstat");
+		exit(EXIT_FAILURE);
+	}
+
+	if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
+		fprintf(stderr, "Invalid size for file %s\n", path);
+		exit(EXIT_FAILURE);
+	}
+
+	mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
+	if (mmapstart == MAP_FAILED) {
+		perror("mmap");
+		exit(EXIT_FAILURE);
+	}
+	ptr = mmapstart;
+	max = mmapstart + sbuf.st_size;
+
+	if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
+		printf("Invalid signature for file %s\n", path);
+		goto out;
+	}
+	ptr += SIGNATURELEN;
+
+	if (strncmp(ptr, VERSION, VERSIONLEN)) {
+		printf("Invalid version for file %s\n", path);
+		goto out;
+	}
+	ptr += VERSIONLEN;
+
+	while (ptr < mmapstart + sbuf.st_size) {
+		if (*ptr == '\0') {
+			fprintf(stderr, "Invalid characters in file %s\n", path);
+			goto out;
+		}
+
+		mentry = mentry_alloc();
+		mentry->path = read_string(&ptr, max);
+		mentry->owner = read_string(&ptr, max);
+		mentry->group = read_string(&ptr, max);
+		mentry->mtime = (time_t)read_int(&ptr, 8, max);
+		mentry->mtimensec = (time_t)read_int(&ptr, 8, max);
+		mentry->mode = (mode_t)read_int(&ptr, 2, max);
+		mentry->xattrs = (unsigned int)read_int(&ptr, 4, max);
+
+		if (mentry->xattrs > 0) {
+			mentry->xattr_names = xmalloc(mentry->xattrs * sizeof(char *));
+			mentry->xattr_lvalues = xmalloc(mentry->xattrs * sizeof(int));
+			mentry->xattr_values = xmalloc(mentry->xattrs * sizeof(char *));
+
+			for (i = 0; i < mentry->xattrs; i++) {
+				mentry->xattr_names[i] = read_string(&ptr, max);
+				mentry->xattr_lvalues[i] = (int)read_int(&ptr, 4, max);
+				mentry->xattr_values[i] = read_binary_string(&ptr, mentry->xattr_lvalues[i], max);
+			}
+		}
+		mentry_insert(mentry, mhead);
+	}
+
+out:
+	munmap(mmapstart, sbuf.st_size);
+	close(fd);
+}
+
+struct metaentry *
+mentry_find(const char *path, struct metaentry *mhead)
+{
+	struct metaentry *m;
+
+	/* FIXME - We can do a bisect search here instead */
+	for (m = mhead; m; m = m->next) {
+		if (!strcmp(path, m->path))
+			return m;
+	}
+	return NULL;
+}
+
+/* Returns xattr index in haystack which corresponds to xattr n in needle */
+int
+mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle, int n)
+{
+	int i;
+
+	for (i = 0; i < haystack->xattrs; i++) {
+		if (strcmp(haystack->xattr_names[i], needle->xattr_names[n]))
+			continue;
+		if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n])
+			return -1;
+		if (bcmp(haystack->xattr_values[i], needle->xattr_values[n], needle->xattr_lvalues[n]))
+			return -1;
+		return i;
+	}
+	return -1;
+}
+
+/* Returns zero if all xattrs in left and right match */
+int
+mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
+{
+	int i;
+
+	if (left->xattrs != right->xattrs)
+		return 1;
+
+	/* Make sure all xattrs in left are found in right and vice versa */
+	for (i = 0; i < left->xattrs; i++) {
+		if (mentry_find_xattr(right, left, i) < 0 ||
+		    mentry_find_xattr(left, right, i) < 0) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+int
+mentry_compare(struct metaentry *left, struct metaentry *right)
+{
+	int retval = DIFF_NONE;
+
+	if (!left || !right) {
+		fprintf(stderr, "mentry_compare called with empty arguments\n");
+		return -1;
+	}
+
+	if (strcmp(left->path, right->path))
+		return -1;
+
+	if (strcmp(left->owner, right->owner))
+		retval |= DIFF_OWNER;
+
+	if (strcmp(left->group, right->group))
+		retval |= DIFF_GROUP;
+
+	if ((left->mode & 07777) != (right->mode & 07777))
+		retval |= DIFF_MODE;
+
+	if ((left->mode & S_IFMT) != (right->mode & S_IFMT))
+		retval |= DIFF_TYPE;
+
+	if (do_mtime && strcmp(left->path, METAFILE) && 
+	    (left->mtime != right->mtime || left->mtimensec != right->mtimensec))
+		retval |= DIFF_MTIME;
+
+	if (mentry_compare_xattr(left, right)) {
+		retval |= DIFF_XATTR;
+		return retval;
+	}
+
+	return retval;
+}
+
+void
+compare_print(struct metaentry *left, struct metaentry *right, int cmp)
+{
+	if (!left) {
+		printf("Path %s: removed\n", right->path);
+		return;
+	}
+
+	if (!right) {
+		printf("Path %s: added\n", left->path);
+		return;
+	}
+
+	if (cmp != 0) {
+		printf("Path %s: ", left->path);
+		if (cmp & DIFF_OWNER)
+			printf("owner ");
+		if (cmp & DIFF_GROUP)
+			printf("group ");
+		if (cmp & DIFF_MODE)
+			printf("mode ");
+		if (cmp & DIFF_TYPE)
+			printf("type ");
+		if (cmp & DIFF_MTIME)
+			printf("mtime ");
+		if (cmp & DIFF_XATTR)
+			printf("xattr ");
+		printf("\n");
+	}
+}
+
+void
+compare_fix(struct metaentry *left, struct metaentry *right, int cmp)
+{
+	struct group *group;
+	struct passwd *owner;
+	gid_t gid = -1;
+	uid_t uid = -1;
+	struct utimbuf tbuf;
+	int i;
+
+	if (!left && !right) {
+		printf("%s called with incorrect arguments\n", __FUNCTION__);
+		return;
+	}
+
+	if (!left) {
+		printf("Path %s: removed\n", right->path);
+		return;
+	}
+
+	if (!right) {
+		printf("Path %s: added\n", left->path);
+		return;
+	}
+
+	if (cmp == DIFF_NONE) {
+		msg(MSG_DEBUG, "Path %s: no difference\n", left->path);
+		return;
+	}
+
+	if (cmp & DIFF_TYPE) {
+		printf("Path %s: new type, will not change metadata\n", left->path);
+		return;
+	}
+
+	if (cmp & (DIFF_OWNER | DIFF_GROUP)) {
+		if (cmp & DIFF_OWNER) {
+			printf("Path %s: fixing owner from %s to %s\n", left->path, left->group, right->group);
+			owner = getpwnam(right->owner);
+			if (!owner) {
+				perror("getpwnam");
+				return;
+			}
+			uid = owner->pw_uid;
+		}
+
+		if (cmp & DIFF_GROUP) {
+			printf("Path %s: fixing group from %s to %s\n", left->path, left->group, right->group);
+			group = getgrnam(right->group);
+			if (!group) {
+				perror("getgrnam");
+				return;
+			}
+			gid = group->gr_gid;
+		}
+
+		if (lchown(left->path, uid, gid)) {
+			perror("lchown");
+			return;
+		}
+		printf("Success\n");
+	}
+
+	if (cmp & DIFF_MODE) {
+		printf("Path %s: fixing mode from 0%o to 0%o\n", left->path, left->mode, right->mode);
+		if (chmod(left->path, left->mode)) {
+			perror("chmod");
+			return;
+		}
+	}
+
+	if (cmp & DIFF_MTIME) {
+		printf("Path %s: fixing mtime %ld to %ld\n", left->path, left->mtime, right->mtime);
+		/* FIXME: Use utimensat here */
+		tbuf.actime = right->mtime;
+		tbuf.modtime = right->mtime;
+		if (utime(left->path, &tbuf)) {
+			perror("utime");
+			return;
+		}
+	}
+
+	if (cmp & DIFF_XATTR) {
+		for (i = 0; i < left->xattrs; i++) {
+			/* Any attrs to remove? */
+			if (mentry_find_xattr(right, left, i) >= 0)
+				continue;
+
+			msg(MSG_NORMAL, "Path %s: removing xattr %s\n",
+			    left->path, left->xattr_names[i]);
+			if (lremovexattr(left->path, left->xattr_names[i]))
+				perror("lremovexattr");
+		}
+
+		for (i = 0; i < right->xattrs; i++) {
+			/* Any xattrs to add? (on change they are removed above) */
+			if (mentry_find_xattr(left, right, i) >= 0)
+				continue;
+
+			msg(MSG_NORMAL, "Path %s: adding xattr %s\n",
+			    right->path, right->xattr_names[i]);
+			if (lsetxattr(right->path, right->xattr_names[i], 
+				       right->xattr_values[i], right->xattr_lvalues[i], XATTR_CREATE))
+				perror("lsetxattr");
+		}
+	}
+}
+
+void
+mentries_compare(struct metaentry *mheadleft,
+		 struct metaentry *mheadright,
+		 void (*printfunc)(struct metaentry *, struct metaentry *, int))
+{
+	struct metaentry *left, *right;
+	int cmp;
+
+	if (!mheadleft || !mheadright) {
+		fprintf(stderr, "mentries_compare called with empty list\n");
+		return;
+	}
+
+	for (left = mheadleft; left; left = left->next) {
+		right = mentry_find(left->path, mheadright);
+		if (!right)
+			cmp = DIFF_ADDED;
+		else
+			cmp = mentry_compare(left, right);
+		printfunc(left, right, cmp);
+	}
+
+	for (right = mheadright; right; right = right->next) {
+		left = mentry_find(right->path, mheadleft);
+		if (!left)
+			printfunc(left, right, DIFF_DELE);
+	}
+}
+
+void
+usage(const char *arg0, const char *msg)
+{
+	if (msg)
+		fprintf(stderr, "%s: %s\n\n", arg0, msg);
+	fprintf(stderr, "Usage: %s ACTION [OPTIONS] [PATH]...\n\n", arg0);
+	fprintf(stderr, "Where ACTION is one of:\n"
+			"  -d, --diff\tShow differences between stored and actual metadata\n"
+			"  -s, --save\tSave current metadata\n"
+			"  -a, --apply\tApply stored metadata\n"
+			"  -h, --help\tHelp message (this text)\n\n"
+			"Valid OPTIONS are (can be given more than once):\n"
+			"  -v, --verbose\tPrint more verbose messages\n"
+			"  -q, --quiet\tPrint less verbose messages\n");
+
+	if (msg)
+		exit(EXIT_FAILURE);
+	exit(EXIT_SUCCESS);
+}
+
+static struct option long_options[] = {
+	{"compare", 0, 0, 0},
+	{"save", 0, 0, 0},
+	{"apply", 0, 0, 0},
+	{"help", 0, 0, 0},
+	{"verbose", 0, 0, 0},
+	{"quiet", 0, 0, 0},
+	{"mtime", 0, 0, 0},
+	{0, 0, 0, 0}
+};
+
+int
+main(int argc, char **argv, char **envp)
+{
+	int i, c;
+	struct metaentry *mhead = NULL;
+	struct metaentry *mfhead = NULL;
+	int action = 0;
+
+	i = 0;
+	while (1) {
+		int option_index = 0;
+		c = getopt_long(argc, argv, "csahvqm", long_options, &option_index);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 0:
+			if (!strcmp("verbose", long_options[option_index].name)) {
+				verbosity++;
+			} else if (!strcmp("quiet", long_options[option_index].name)) {
+				verbosity--;
+			} else if (!strcmp("mtime", long_options[option_index].name)) {
+				do_mtime = 1;
+			} else {
+				action |= (1 << option_index);
+				i++;
+			}
+			break;
+		case 'c':
+			action |= ACTION_DIFF;
+			i++;
+			break;
+		case 's':
+			action |= ACTION_SAVE;
+			i++;
+			break;
+		case 'a':
+			action |= ACTION_APPLY;
+			i++;
+			break;
+		case 'h':
+			action |= ACTION_HELP;
+			i++;
+			break;
+		case 'v':
+			verbosity++;
+			break;
+		case 'q':
+			verbosity--;
+			break;
+		case 'm':
+			do_mtime = 1;
+			break;
+		default:
+			usage(argv[0], "unknown option");
+		}
+	}
+
+	if (i != 1)
+		usage(argv[0], "incorrect option(s)");
+
+	switch (action) {
+	case ACTION_DIFF:
+		mentries_fromfile(&mfhead, METAFILE);
+		if (!mfhead) {
+			fprintf(stderr, "Failed to load metadata from file\n");
+			exit(EXIT_FAILURE);
+		}
+
+		if (optind < argc)
+			while (optind < argc)
+				mentries_recurse(argv[optind++], &mhead);
+		else
+			mentries_recurse(".", &mhead);
+		if (!mhead) {
+			fprintf(stderr, "Failed to load metadata from fs\n");
+			exit(EXIT_FAILURE);
+		}
+		mentries_compare(mhead, mfhead, compare_print);
+		break;
+	case ACTION_SAVE:
+		if (optind < argc)
+			while (optind < argc)
+				mentries_recurse(argv[optind++], &mhead);
+		else
+			mentries_recurse(".", &mhead);
+		if (!mhead) {
+			fprintf(stderr, "Failed to load metadata from fs\n");
+			exit(EXIT_FAILURE);
+		}
+		mentries_tofile(mhead, METAFILE);
+		break;
+	case ACTION_APPLY:
+		mentries_fromfile(&mfhead, METAFILE);
+		if (!mfhead) {
+			fprintf(stderr, "Failed to load metadata from file\n");
+			exit(EXIT_FAILURE);
+		}
+
+		if (optind < argc)
+			while (optind < argc)
+				mentries_recurse(argv[optind++], &mhead);
+		else
+			mentries_recurse(".", &mhead);
+		if (!mhead) {
+			fprintf(stderr, "Failed to load metadata from fs\n");
+			exit(EXIT_FAILURE);
+		}
+		mentries_compare(mhead, mfhead, compare_fix);
+		break;
+	case ACTION_HELP:
+		usage(argv[0], NULL);
+	}
+
+	exit(EXIT_SUCCESS);
+}
diff --git a/metastore.h b/metastore.h
new file mode 100644
index 0000000..1ccdb54
--- /dev/null
+++ b/metastore.h
@@ -0,0 +1,40 @@
+#define SIGNATURE    "MeTaSt00r3"
+#define SIGNATURELEN 10
+#define VERSION      "\0\0\0\0\0\0\0\0"
+#define VERSIONLEN   8
+#define METAFILE     "./.metadata"
+
+#define MSG_NORMAL    0
+#define MSG_DEBUG     1
+#define MSG_QUIET    -1
+#define MSG_CRITICAL -2
+
+#define ACTION_DIFF  0x01
+#define ACTION_SAVE  0x02
+#define ACTION_APPLY 0x04
+#define ACTION_HELP  0x08
+
+#define DIFF_NONE  0x00
+#define DIFF_OWNER 0x01
+#define DIFF_GROUP 0x02
+#define DIFF_MODE  0x04
+#define DIFF_TYPE  0x08
+#define DIFF_MTIME 0x10
+#define DIFF_XATTR 0x20
+#define DIFF_ADDED 0x40
+#define DIFF_DELE  0x80
+
+struct metaentry {
+	struct metaentry *next;
+	char *path;
+	char *owner;
+	char *group;
+        mode_t mode;
+	time_t mtime;
+        long mtimensec;
+	unsigned int xattrs;
+	char **xattr_names;
+	ssize_t *xattr_lvalues;
+	char **xattr_values;
+};
+
diff --git a/trash b/trash
deleted file mode 100644
index e69de29..0000000
diff --git a/utils.c b/utils.c
new file mode 100644
index 0000000..f7eb194
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,115 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "utils.h"
+#include "metastore.h"
+
+void *
+xmalloc(size_t size)
+{
+        void *result = malloc(size);
+        if (!result) {
+                fprintf(stderr, "Failed to malloc %zi bytes\n", size);
+                exit(EXIT_FAILURE);
+        }
+        return result;
+}
+
+char *
+xstrdup(const char *s)
+{
+	char *result = strdup(s);
+	if (!result) {
+		fprintf(stderr, "Failed to strdup %zi bytes\n", strlen(s));
+		exit(EXIT_FAILURE);
+	}
+	return result;
+}
+
+void
+binary_print(const char *s, ssize_t len)
+{
+	ssize_t i;
+
+	for (i = 0; i < len; i++) {
+		if (isprint(s[i]))
+			printf("%c", s[i]);
+		else
+			printf("0x%02X", (int)s[i]);
+	}
+}
+
+void
+xfwrite(const void *ptr, size_t size, FILE *stream)
+{
+	if (fwrite(ptr, size, 1, stream) != 1) {
+		perror("fwrite");
+		exit(EXIT_FAILURE);
+	}
+}
+
+void
+write_int(uint64_t value, size_t len, FILE *to)
+{
+	char buf[len];
+	int i;
+
+	for (i = 0; i < len; i++)
+		buf[i] = ((value >> (8 * i)) & 0xff);
+	xfwrite(buf, len, to);
+}
+
+void
+write_binary_string(const char *string, size_t len, FILE *to)
+{
+	xfwrite(string, len, to);
+}
+
+void
+write_string(const char *string, FILE *to)
+{
+	xfwrite(string, strlen(string) + 1, to);
+}
+
+uint64_t
+read_int(char **from, size_t len, const char *max)
+{
+	uint64_t result = 0;
+	int i;
+
+	if (*from + len > max) {
+		fprintf(stderr, "Attempt to read beyond end of file, corrupt file?\n");
+		exit(EXIT_FAILURE);
+	}
+
+	for (i = 0; i < len; i++)
+		result += (((*from)[i] & 0xff) << (8 * i));
+	*from += len;
+	return result;
+}
+
+char *
+read_binary_string(char **from, size_t len, const char *max)
+{
+	char *result;
+
+	if (*from + len > max) {
+		fprintf(stderr, "Attempt to read beyond end of file, corrupt file?\n");
+		exit(EXIT_FAILURE);
+	}
+
+	result = xmalloc(len);
+	strncpy(result, *from, len);
+	*from += len;
+	return result;
+}
+
+char *
+read_string(char **from, const char *max)
+{
+	return read_binary_string(from, strlen(*from) + 1, max);
+}
+
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..2f39611
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,13 @@
+void *xmalloc(size_t size);
+char *xstrdup(const char *s);
+void binary_print(const char *s, ssize_t len);
+void xfwrite(const void *ptr, size_t size, FILE *stream);
+
+void write_int(uint64_t value, size_t len, FILE *to);
+void write_binary_string(const char *string, size_t len, FILE *to);
+void write_string(const char *string, FILE *to);
+
+uint64_t read_int(char **from, size_t len, const char *max);
+char *read_binary_string(char **from, size_t len, const char *max);
+char *read_string(char **from, const char *max);
+
-- 
cgit v1.2.1