From 16ab153f3a54194e3217fcf1a235904e4b61623b Mon Sep 17 00:00:00 2001
From: Przemyslaw Pawelczyk <przemoc@gmail.com>
Date: Sun, 13 Sep 2015 22:12:14 +0200
Subject: Move source files to src/ directory.

As a bonus you can build out-of-tree now via make -f.
It's part of the work related to issue #22.
---
 Makefile        |   6 +
 metaentry.c     | 677 --------------------------------------------------------
 metaentry.h     |  97 --------
 metastore.c     | 539 --------------------------------------------
 metastore.h     |  43 ----
 settings.h      |  35 ---
 src/metaentry.c | 677 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/metaentry.h |  97 ++++++++
 src/metastore.c | 539 ++++++++++++++++++++++++++++++++++++++++++++
 src/metastore.h |  43 ++++
 src/settings.h  |  35 +++
 src/utils.c     | 303 +++++++++++++++++++++++++
 src/utils.h     |  90 ++++++++
 utils.c         | 303 -------------------------
 utils.h         |  90 --------
 15 files changed, 1790 insertions(+), 1784 deletions(-)
 delete mode 100644 metaentry.c
 delete mode 100644 metaentry.h
 delete mode 100644 metastore.c
 delete mode 100644 metastore.h
 delete mode 100644 settings.h
 create mode 100644 src/metaentry.c
 create mode 100644 src/metaentry.h
 create mode 100644 src/metastore.c
 create mode 100644 src/metastore.h
 create mode 100644 src/settings.h
 create mode 100644 src/utils.c
 create mode 100644 src/utils.h
 delete mode 100644 utils.c
 delete mode 100644 utils.h

diff --git a/Makefile b/Makefile
index 3746029..96acb88 100644
--- a/Makefile
+++ b/Makefile
@@ -30,11 +30,17 @@ LINK            = $(CC) $(CFLAGS) $(LDFLAGS)
 OBJECTS         = utils.o metastore.o metaentry.o
 HEADERS         = utils.h metastore.h metaentry.h
 
+PROJ_DIR       := $(dir $(lastword $(MAKEFILE_LIST)))
+SRCS_DIR       := $(PROJ_DIR)src/
+
 DESTDIR        ?=
 prefix         	= /usr
 usrbindir       = ${prefix}/bin
 mandir          = ${prefix}/share/man
 
+vpath %.c $(SRCS_DIR)
+vpath %.h $(SRCS_DIR)
+
 #
 # Targets
 #
diff --git a/metaentry.c b/metaentry.c
deleted file mode 100644
index f3ba692..0000000
--- a/metaentry.c
+++ /dev/null
@@ -1,677 +0,0 @@
-/*
- * Various functions to work with meta entries.
- *
- * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <sys/xattr.h>
-#include <limits.h>
-#include <dirent.h>
-#include <sys/mman.h>
-#include <utime.h>
-#include <fcntl.h>
-#include <stdint.h>
-#include <errno.h>
-#include <bsd/string.h>
-#include <time.h>
-
-#include "metastore.h"
-#include "metaentry.h"
-#include "utils.h"
-
-/* Free's a metaentry and all its parameters */
-static void
-mentry_free(struct metaentry *m)
-{
-	unsigned 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);
-}
-
-/* Allocates an empty metahash table */
-static struct metahash *
-mhash_alloc()
-{
-	struct metahash *mhash;
-	mhash = xmalloc(sizeof(struct metahash));
-	memset(mhash, 0, sizeof(struct metahash));
-	return mhash;
-}
-
-/* Generates a hash key (using djb2) */
-static unsigned int
-hash(const char *str)
-{
-	unsigned int hash = 5381;
-	int c;
-
-	while ((c = *str++))
-		hash = ((hash << 5) + hash) + c;
-
-	return hash % HASH_INDEXES;
-}
-
-/* Allocates an empty metaentry */
-static struct metaentry *
-mentry_alloc()
-{
-	struct metaentry *mentry;
-	mentry = xmalloc(sizeof(struct metaentry));
-	memset(mentry, 0, sizeof(struct metaentry));
-	return mentry;
-}
-
-/* Does a bisect search for the closest match in a metaentry list */
-struct metaentry *
-mentry_find(const char *path, struct metahash *mhash)
-{
-	struct metaentry *base;
-	unsigned int key;
-
-	if (!mhash) {
-		msg(MSG_ERROR, "%s called with empty hash table\n", __FUNCTION__);
-		return NULL;
-	}
-
-	key = hash(path);
-	for (base = mhash->bucket[key]; base; base = base->next) {
-		if (!strcmp(base->path, path))
-			return base;
-	}
-
-	return NULL;
-}
-
-/* Inserts a metaentry into a metaentry list */
-static void
-mentry_insert(struct metaentry *mentry, struct metahash *mhash)
-{
-	unsigned int key;
-
-	key = hash(mentry->path);
-	mentry->next = mhash->bucket[key];
-	mhash->bucket[key] = mentry;
-}
-
-#ifdef DEBUG
-/* Prints a metaentry */
-static void
-mentry_print(const struct metaentry *mentry)
-{
-	int i;
-
-	if (!mentry || !mentry->path) {
-		msg(MSG_DEBUG,
-		    "Incorrect meta entry passed to printmetaentry\n");
-		return;
-	}
-
-	msg(MSG_DEBUG, "===========================\n");
-	msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry);
-	msg(MSG_DEBUG, "===========================\n");
-
-	msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path);
-	msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner);
-	msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group);
-	msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
-	msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
-	msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode);
-	for (i = 0; i < mentry->xattrs; i++) {
-		msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
-		binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
-		msg(MSG_DEBUG, "\"\n");
-	}
-
-	msg(MSG_DEBUG, "===========================\n\n");
-}
-
-/* Prints all metaentries in a metaentry list */
-static void
-mentries_print(const struct metahash *mhash)
-{
-	const struct metaentry *mentry;
-	int index;
-
-	for (index = 0; index < HASH_INDEXES; index++)
-		for (mentry = mhash->bucket[index]; mentry; mentry = mentry->next)
-			mentry_print(mentry);
-
-	msg(MSG_DEBUG, "%i entries in total\n", mhash->count);
-}
-#endif
-
-/* Creates a metaentry for the file/dir/etc at path */
-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)) {
-		msg(MSG_ERROR, "lstat failed for %s: %s\n",
-		    path, strerror(errno));
-		return NULL;
-	}
-
-	pbuf = xgetpwuid(sbuf.st_uid);
-	if (!pbuf) {
-		msg(MSG_ERROR, "getpwuid failed for %s: uid %i not found\n",
-		    path, (int)sbuf.st_uid);
-		return NULL;
-	}
-
-	gbuf = xgetgrgid(sbuf.st_gid);
-	if (!gbuf) {
-		msg(MSG_ERROR, "getgrgid failed for %s: gid %i not found\n",
-		    path, (int)sbuf.st_gid);
-		return NULL;
-	}
-
-	mentry = mentry_alloc();
-	mentry->path = xstrdup(path);
-	mentry->pathlen = strlen(mentry->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) {
-		/* Perhaps the FS doesn't support xattrs? */
-		if (errno == ENOTSUP)
-			return mentry;
-
-		msg(MSG_ERROR, "listxattr failed for %s: %s\n",
-		    path, strerror(errno));
-		return NULL;
-	}
-
-	list = xmalloc(lsize);
-	lsize = listxattr(path, list, lsize);
-	if (lsize < 0) {
-		msg(MSG_ERROR, "listxattr failed for %s: %s\n",
-		    path, strerror(errno));
-		free(list);
-		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) {
-			msg(MSG_ERROR, "getxattr failed for %s: %s\n",
-			    path, strerror(errno));
-			free(list);
-			mentry_free(mentry);
-			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) {
-			msg(MSG_ERROR, "getxattr failed for %s: %s\n",
-			    path, strerror(errno));
-			free(list);
-			mentry_free(mentry);
-			return NULL;
-		}
-		i++;
-	}
-
-	free(list);
-	return mentry;
-}
-
-/* Cleans up a path and makes it relative to current working dir unless it is absolute */
-static char *
-normalize_path(const char *orig)
-{
-	char *real = realpath(orig, NULL);
-	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;
-}
-
-/* Internal function for the recursive path walk */
-static void
-mentries_recurse(const char *path, struct metahash *mhash, msettings *st)
-{
-	struct stat sbuf;
-	struct metaentry *mentry;
-	char tpath[PATH_MAX];
-	DIR *dir;
-	struct dirent *dent;
-
-	if (!path)
-		return;
-
-	if (lstat(path, &sbuf)) {
-		msg(MSG_ERROR, "lstat failed for %s: %s\n",
-		    path, strerror(errno));
-		return;
-	}
-
-	mentry = mentry_create(path);
-	if (!mentry)
-		return;
-
-	mentry_insert(mentry, mhash);
-
-	if (S_ISDIR(sbuf.st_mode)) {
-		dir = opendir(path);
-		if (!dir) {
-			msg(MSG_ERROR, "opendir failed for %s: %s\n",
-			    path, strerror(errno));
-			return;
-		}
-
-		while ((dent = readdir(dir))) {
-			if (!strcmp(dent->d_name, ".") ||
-			    !strcmp(dent->d_name, "..") ||
-			    (!st->do_git && !strcmp(dent->d_name, ".git")))
-				continue;
-			snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
-			tpath[PATH_MAX - 1] = '\0';
-			mentries_recurse(tpath, mhash, st);
-		}
-
-		closedir(dir);
-	}
-}
-
-/* Recurses opath and adds metadata entries to the metaentry list */
-void
-mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st)
-{
-	char *path = normalize_path(opath);
-
-	if (!(*mhash))
-		*mhash = mhash_alloc();
-	mentries_recurse(path, *mhash, st);
-	free(path);
-}
-
-/* Stores metaentries to a file */
-void
-mentries_tofile(const struct metahash *mhash, const char *path)
-{
-	FILE *to;
-	const struct metaentry *mentry;
-	int key;
-	unsigned i;
-
-	to = fopen(path, "w");
-	if (!to) {
-		msg(MSG_CRITICAL, "Failed to open %s: %s\n",
-		    path, strerror(errno));
-		exit(EXIT_FAILURE);
-	}
-
-	write_binary_string(SIGNATURE, SIGNATURELEN, to);
-	write_binary_string(VERSION, VERSIONLEN, to);
-
-	for (key = 0; key < HASH_INDEXES; key++) {
-		for (mentry = mhash->bucket[key]; 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);
-}
-
-/* Creates a metaentry list from a file */
-void
-mentries_fromfile(struct metahash **mhash, const char *path)
-{
-	struct metaentry *mentry;
-	char *mmapstart;
-	char *ptr;
-	char *max;
-	int fd;
-	struct stat sbuf;
-	unsigned i;
-
-	if (!(*mhash))
-		*mhash = mhash_alloc();
-
-	fd = open(path, O_RDONLY);
-	if (fd < 0) {
-		msg(MSG_CRITICAL, "Failed to open %s: %s\n",
-		    path, strerror(errno));
-		exit(EXIT_FAILURE);
-	}
-
-	if (fstat(fd, &sbuf)) {
-		msg(MSG_CRITICAL, "Failed to stat %s: %s\n",
-		    path, strerror(errno));
-		exit(EXIT_FAILURE);
-	}
-
-	if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
-		msg(MSG_CRITICAL, "File %s has an invalid size\n", path);
-		exit(EXIT_FAILURE);
-	}
-
-	mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ,
-			 MAP_SHARED, fd, 0);
-	if (mmapstart == MAP_FAILED) {
-		msg(MSG_CRITICAL, "Unable to mmap %s: %s\n",
-		    path, strerror(errno));
-		exit(EXIT_FAILURE);
-	}
-	ptr = mmapstart;
-	max = mmapstart + sbuf.st_size;
-
-	if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
-		msg(MSG_CRITICAL, "Invalid signature for file %s\n", path);
-		goto out;
-	}
-	ptr += SIGNATURELEN;
-
-	if (strncmp(ptr, VERSION, VERSIONLEN)) {
-		msg(MSG_CRITICAL, "Invalid version of file %s\n", path);
-		goto out;
-	}
-	ptr += VERSIONLEN;
-
-	while (ptr < mmapstart + sbuf.st_size) {
-		if (*ptr == '\0') {
-			msg(MSG_CRITICAL, "Invalid characters in file %s\n",
-			    path);
-			goto out;
-		}
-
-		mentry = mentry_alloc();
-		mentry->path = read_string(&ptr, max);
-		mentry->pathlen = strlen(mentry->path);
-		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) {
-			mentry_insert(mentry, *mhash);
-			continue;
-		}
-
-		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, *mhash);
-	}
-
-out:
-	munmap(mmapstart, sbuf.st_size);
-	close(fd);
-}
-
-/* Searches haystack for an xattr matching xattr number n in needle */
-int
-mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle,
-                  unsigned n)
-{
-	unsigned 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 */
-static int
-mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
-{
-	unsigned 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;
-}
-
-/* Compares two metaentries and returns an int with a bitmask of differences */
-int
-mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st)
-{
-	int retval = DIFF_NONE;
-
-	if (!left || !right) {
-		msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
-		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 (st->do_mtime && strcmp(left->path, st->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;
-}
-
-/* Compares lists of real and stored metadata and calls pfunc for each */
-void
-mentries_compare(struct metahash *mhashreal,
-		 struct metahash *mhashstored,
-		 void (*pfunc)
-		 (struct metaentry *real, struct metaentry *stored, int cmp),
-		 msettings *st)
-{
-	struct metaentry *real, *stored;
-	int key;
-
-	if (!mhashreal || !mhashstored) {
-		msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
-		return;
-	}
-
-	for (key = 0; key < HASH_INDEXES; key++) {
-		for (real = mhashreal->bucket[key]; real; real = real->next) {
-			stored = mentry_find(real->path, mhashstored);
-
-			if (!stored)
-				pfunc(real, NULL, DIFF_ADDED);
-			else
-				pfunc(real, stored, mentry_compare(real, stored, st));
-		}
-
-		for (stored = mhashstored->bucket[key]; stored; stored = stored->next) {
-			real = mentry_find(stored->path, mhashreal);
-
-			if (!real)
-				pfunc(NULL, stored, DIFF_DELE);
-		}
-	}
-}
-
-/* Dumps given metadata */
-void
-mentries_dump(struct metahash *mhash)
-{
-	const struct metaentry *mentry;
-	char mode[11 + 1] = "";
-	char date[12 + 2 + 2 + 2*1 + 1 + 2 + 2 + 2 + 2*1 + 1] = "";
-	char zone[5 + 1] = "";
-	struct tm cal;
-
-	for (int key = 0; key < HASH_INDEXES; key++) {
-		for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
-			strmode(mentry->mode, mode);
-			localtime_r(&mentry->mtime, &cal);
-			strftime(date, sizeof(date), "%F %T", &cal);
-			strftime(zone, sizeof(zone), "%z", &cal);
-			printf("%s\t%s\t%s\t%s.%09ld %s\t%s%s\n",
-			       mode,
-			       mentry->owner, mentry->group,
-			       date, mentry->mtimensec, zone,
-			       mentry->path, S_ISDIR(mentry->mode) ? "/" : "");
-			for (int i = 0; i < mentry->xattrs; i++) {
-				printf("\t\t\t\t%s%s\t%s=",
-				       mentry->path, S_ISDIR(mentry->mode) ? "/" : "",
-				       mentry->xattr_names[i]);
-				ssize_t p = 0;
-				for (; p < mentry->xattr_lvalues[i]; p++) {
-					const char ch = mentry->xattr_values[i][p];
-					if ((unsigned)(ch - 32) > 126 - 32) {
-						p = -1;
-						break;
-					}
-				}
-				if (p >= 0)
-					printf("\"%.*s\"\n",
-					       (int)mentry->xattr_lvalues[i],
-					       mentry->xattr_values[i]);
-				else {
-					printf("0x");
-					for (p = 0; p < mentry->xattr_lvalues[i]; p++)
-						printf("%02hhx", (char)mentry->xattr_values[i][p]);
-					printf("\n");
-				}
-			}
-		}
-	}
-}
diff --git a/metaentry.h b/metaentry.h
deleted file mode 100644
index 666c5af..0000000
--- a/metaentry.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Various functions to work with meta entries.
- *
- * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-#ifndef METAENTRY_H
-#define METAENTRY_H
-
-#include <stdbool.h>
-
-#include "settings.h"
-
-/* Data structure to hold all metadata for a file/dir */
-struct metaentry {
-	struct metaentry *next; /* For the metahash chains */
-	struct metaentry *list; /* For creating additional lists of entries */
-
-	char *path;
-	unsigned int pathlen;
-
-	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;
-};
-
-#define HASH_INDEXES 1024
-
-/* Data structure to hold a number of metadata entries */
-struct metahash {
-        struct metaentry *bucket[HASH_INDEXES];
-        unsigned int count;
-};
-
-/* Create a metaentry for the file/dir/etc at path */
-struct metaentry *mentry_create(const char *path);
-
-/* Recurses opath and adds metadata entries to the metaentry list */
-void mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st);
-
-/* Stores a metaentry list to a file */
-void mentries_tofile(const struct metahash *mhash, const char *path);
-
-/* Creates a metaentry list from a file */
-void mentries_fromfile(struct metahash **mhash, const char *path);
-
-/* Searches haystack for an xattr matching xattr number n in needle */
-int mentry_find_xattr(struct metaentry *haystack,
-                      struct metaentry *needle,
-                      unsigned n);
-
-#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
-
-/* Compares two metaentries and returns an int with a bitmask of differences */
-int mentry_compare(struct metaentry *left,
-		   struct metaentry *right,
-		   msettings *st);
-
-/* Compares lists of real and stored metadata and calls pfunc for each */
-void mentries_compare(struct metahash *mhashreal,
-                      struct metahash *mhashstored,
-                      void (*pfunc)(struct metaentry *real,
-                                    struct metaentry *stored,
-                                    int cmp),
-                      msettings *st);
-
-void mentries_dump(struct metahash *mhash);
-
-#endif /* METAENTRY_H */
diff --git a/metastore.c b/metastore.c
deleted file mode 100644
index 2956cab..0000000
--- a/metastore.c
+++ /dev/null
@@ -1,539 +0,0 @@
-/*
- * Main functions of the program.
- *
- * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-#define _BSD_SOURCE
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <getopt.h>
-#include <utime.h>
-#include <sys/xattr.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <errno.h>
-
-#include "metastore.h"
-#include "settings.h"
-#include "utils.h"
-#include "metaentry.h"
-
-/* metastore settings */
-static struct metasettings settings = {
-	.metafile = METAFILE,
-	.do_mtime = false,
-	.do_emptydirs = false,
-	.do_removeemptydirs = false,
-	.do_git = false,
-};
-
-/* Used to create lists of dirs / other files which are missing in the fs */
-static struct metaentry *missingdirs = NULL;
-static struct metaentry *missingothers = NULL;
-
-/* Used to create lists of dirs / other files which are missing in metadata */
-static struct metaentry *extradirs = NULL;
-
-/*
- * Inserts an entry in a linked list ordered by pathlen
- */
-static void
-insert_entry_plist(struct metaentry **list, struct metaentry *entry)
-{
-	struct metaentry **parent;
-
-	for (parent = list; *parent; parent = &((*parent)->list)) {
-		if ((*parent)->pathlen > entry->pathlen)
-			break;
-	}
-
-	entry->list = *parent;
-	*parent = entry;
-}
-
-/*
- * Inserts an entry in a linked list ordered by pathlen descendingly
- */
-static void
-insert_entry_pdlist(struct metaentry **list, struct metaentry *entry)
-{
-	struct metaentry **parent;
-
-	for (parent = list; *parent; parent = &((*parent)->list)) {
-		if ((*parent)->pathlen < entry->pathlen)
-			break;
-	}
-
-	entry->list = *parent;
-	*parent = entry;
-}
-
-/*
- * Prints differences between real and stored actual metadata
- * - for use in mentries_compare
- */
-static void
-compare_print(struct metaentry *real, struct metaentry *stored, int cmp)
-{
-	if (!real && !stored) {
-		msg(MSG_ERROR, "%s called with incorrect arguments\n", __FUNCTION__);
-		return;
-	}
-
-	if (cmp == DIFF_NONE) {
-		msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
-		return;
-	}
-
-	msg(MSG_QUIET, "%s:\t", real ? real->path : stored->path);
-
-	if (cmp & DIFF_ADDED)
-		msg(MSG_QUIET, "added ", real->path);
-	if (cmp & DIFF_DELE)
-		msg(MSG_QUIET, "removed ", stored->path);
-	if (cmp & DIFF_OWNER)
-		msg(MSG_QUIET, "owner ");
-	if (cmp & DIFF_GROUP)
-		msg(MSG_QUIET, "group ");
-	if (cmp & DIFF_MODE)
-		msg(MSG_QUIET, "mode ");
-	if (cmp & DIFF_TYPE)
-		msg(MSG_QUIET, "type ");
-	if (cmp & DIFF_MTIME)
-		msg(MSG_QUIET, "mtime ");
-	if (cmp & DIFF_XATTR)
-		msg(MSG_QUIET, "xattr ");
-	msg(MSG_QUIET, "\n");
-}
-
-/*
- * Tries to change the real metadata to match the stored one
- * - for use in mentries_compare
- */
-static void
-compare_fix(struct metaentry *real, struct metaentry *stored, int cmp)
-{
-	struct group *group;
-	struct passwd *owner;
-	gid_t gid = -1;
-	uid_t uid = -1;
-	struct utimbuf tbuf;
-	unsigned i;
-
-	if (!real && !stored) {
-		msg(MSG_ERROR, "%s called with incorrect arguments\n",
-		    __FUNCTION__);
-		return;
-	}
-
-	if (!real) {
-		if (S_ISDIR(stored->mode))
-			insert_entry_plist(&missingdirs, stored);
-		else
-			insert_entry_plist(&missingothers, stored);
-
-		msg(MSG_NORMAL, "%s:\tremoved\n", stored->path);
-		return;
-	}
-
-	if (!stored) {
-		if (S_ISDIR(real->mode))
-			insert_entry_pdlist(&extradirs, real);
-		msg(MSG_NORMAL, "%s:\tadded\n", real->path);
-		return;
-	}
-
-	if (cmp == DIFF_NONE) {
-		msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
-		return;
-	}
-
-	if (cmp & DIFF_TYPE) {
-		msg(MSG_NORMAL, "%s:\tnew type, will not change metadata\n",
-		    real->path);
-		return;
-	}
-
-	msg(MSG_QUIET, "%s:\tchanging metadata\n", real->path);
-
-	while (cmp & (DIFF_OWNER | DIFF_GROUP)) {
-		if (cmp & DIFF_OWNER) {
-			msg(MSG_NORMAL, "%s:\tchanging owner from %s to %s\n",
-			    real->path, real->group, stored->group);
-			owner = xgetpwnam(stored->owner);
-			if (!owner) {
-				msg(MSG_DEBUG, "\tgetpwnam failed: %s\n",
-				    strerror(errno));
-				break;
-			}
-			uid = owner->pw_uid;
-		}
-
-		if (cmp & DIFF_GROUP) {
-			msg(MSG_NORMAL, "%s:\tchanging group from %s to %s\n",
-			    real->path, real->group, stored->group);
-			group = xgetgrnam(stored->group);
-			if (!group) {
-				msg(MSG_DEBUG, "\tgetgrnam failed: %s\n",
-				    strerror(errno));
-				break;
-			}
-			gid = group->gr_gid;
-		}
-
-		if (lchown(real->path, uid, gid)) {
-			msg(MSG_DEBUG, "\tlchown failed: %s\n",
-			    strerror(errno));
-			break;
-		}
-		break;
-	}
-
-	if (cmp & DIFF_MODE) {
-		msg(MSG_NORMAL, "%s:\tchanging mode from 0%o to 0%o\n",
-		    real->path, real->mode & 07777, stored->mode & 07777);
-		if (chmod(real->path, stored->mode & 07777))
-			msg(MSG_DEBUG, "\tchmod failed: %s\n", strerror(errno));
-	}
-
-	/* FIXME: Use utimensat here, or even better - lutimensat */
-	if ((cmp & DIFF_MTIME) && S_ISLNK(real->mode)) {
-		msg(MSG_NORMAL, "%s:\tsymlink, not changing mtime\n", real->path);
-	} else if (cmp & DIFF_MTIME) {
-		msg(MSG_NORMAL, "%s:\tchanging mtime from %ld to %ld\n",
-		    real->path, real->mtime, stored->mtime);
-		tbuf.actime = stored->mtime;
-		tbuf.modtime = stored->mtime;
-		if (utime(real->path, &tbuf)) {
-			msg(MSG_DEBUG, "\tutime failed: %s\n", strerror(errno));
-			return;
-		}
-	}
-
-	if (cmp & DIFF_XATTR) {
-		for (i = 0; i < real->xattrs; i++) {
-			/* Any attrs to remove? */
-			if (mentry_find_xattr(stored, real, i) >= 0)
-				continue;
-
-			msg(MSG_NORMAL, "%s:\tremoving xattr %s\n",
-			    real->path, real->xattr_names[i]);
-			if (lremovexattr(real->path, real->xattr_names[i]))
-				msg(MSG_DEBUG, "\tlremovexattr failed: %s\n",
-				    strerror(errno));
-		}
-
-		for (i = 0; i < stored->xattrs; i++) {
-			/* Any xattrs to add? (on change they are removed above) */
-			if (mentry_find_xattr(real, stored, i) >= 0)
-				continue;
-
-			msg(MSG_NORMAL, "%s:\tadding xattr %s\n",
-			    stored->path, stored->xattr_names[i]);
-			if (lsetxattr(stored->path, stored->xattr_names[i],
-				      stored->xattr_values[i],
-				      stored->xattr_lvalues[i], XATTR_CREATE))
-				msg(MSG_DEBUG, "\tlsetxattr failed: %s\n",
-				    strerror(errno));
-		}
-	}
-}
-
-/*
- * Tries to fix any empty dirs which are missing from the filesystem by
- * recreating them.
- */
-static void
-fixup_emptydirs(void)
-{
-	struct metaentry *entry;
-	struct metaentry *cur;
-	struct metaentry **parent;
-	char *bpath;
-	char *delim;
-	size_t blen;
-	struct metaentry *new;
-
-	if (!missingdirs)
-		return;
-	msg(MSG_DEBUG, "\nAttempting to recreate missing dirs\n");
-
-	/* If directory x/y is missing, but file x/y/z is also missing,
-	 * we should prune directory x/y from the list of directories to
-	 * recreate since the deletition of x/y is likely to be genuine
-	 * (as opposed to empty dir pruning like git/cvs does).
-	 *
-	 * Also, if file x/y/z is missing, any child directories of
-	 * x/y should be pruned as they are probably also intentionally
-	 * removed.
-	 */
-
-	msg(MSG_DEBUG, "List of candidate dirs:\n");
-	for (cur = missingdirs; cur; cur = cur->list)
-		msg(MSG_DEBUG, " %s\n", cur->path);
-
-	for (entry = missingothers; entry; entry = entry->list) {
-		msg(MSG_DEBUG, "Pruning using file %s\n", entry->path);
-		bpath = xstrdup(entry->path);
-		delim = strrchr(bpath, '/');
-		if (!delim) {
-			msg(MSG_NORMAL, "No delimiter found in %s\n", bpath);
-			free(bpath);
-			continue;
-		}
-		*delim = '\0';
-
-		parent = &missingdirs;
-		for (cur = *parent; cur; cur = cur->list) {
-			if (strcmp(cur->path, bpath)) {
-				parent = &cur->list;
-				continue;
-			}
-
-			msg(MSG_DEBUG, "Prune phase 1 - %s\n", cur->path);
-			*parent = cur->list;
-		}
-
-		/* Now also prune subdirs of the base dir */
-		*delim++ = '/';
-		*delim = '\0';
-		blen = strlen(bpath);
-
-		parent = &missingdirs;
-		for (cur = *parent; cur; cur = cur->list) {
-			if (strncmp(cur->path, bpath, blen)) {
-				parent = &cur->list;
-				continue;
-			}
-
-			msg(MSG_DEBUG, "Prune phase 2 - %s\n", cur->path);
-			*parent = cur->list;
-		}
-
-		free(bpath);
-	}
-	msg(MSG_DEBUG, "\n");
-
-	for (cur = missingdirs; cur; cur = cur->list) {
-		msg(MSG_QUIET, "%s:\trecreating...", cur->path);
-		if (mkdir(cur->path, cur->mode)) {
-			msg(MSG_QUIET, "failed (%s)\n", strerror(errno));
-			continue;
-		}
-		msg(MSG_QUIET, "ok\n");
-
-		new = mentry_create(cur->path);
-		if (!new) {
-			msg(MSG_QUIET, "Failed to get metadata for %s\n");
-			continue;
-		}
-
-		compare_fix(new, cur, mentry_compare(new, cur, &settings));
-	}
-}
-
-/*
- * Deletes any empty dirs present in the filesystem that are missing
- * from the metadata.
- * An "empty" dir is one which either:
- *  - is empty; or
- *  - only contains empty dirs
- */
-static void
-fixup_newemptydirs(void)
-{
-	struct metaentry **cur;
-	int removed_dirs = 1;
-
-	if (!extradirs)
-		return;
-
-	/* This is a simpleminded algorithm that attempts to rmdir() all
-	 * directories discovered missing from the metadata. Naturally, this will
-	 * succeed only on the truly empty directories, but depending on the order,
-	 * it may mean that parent directory removal are attempted to be removed
-	 * *before* the children. To circumvent this, keep looping around all the
-	 * directories until none have been successfully removed.  This is a
-	 * O(N**2) algorithm, so don't try to remove too many nested directories
-	 * at once (e.g. thousands).
-	 *
-	 * Note that this will succeed only if each parent directory is writable.
-	 */
-	while (removed_dirs) {
-		removed_dirs = 0;
-		msg(MSG_DEBUG, "\nAttempting to delete empty dirs\n");
-		for (cur = &extradirs; *cur;) {
-			msg(MSG_QUIET, "%s:\tremoving...", (*cur)->path);
-			if (rmdir((*cur)->path)) {
-				msg(MSG_QUIET, "failed (%s)\n", strerror(errno));
-				cur = &(*cur)->list;
-				continue;
-			}
-			/* No freeing, because OS will do the job at the end. */
-			*cur = (*cur)->list;
-			removed_dirs++;
-			msg(MSG_QUIET, "ok\n");
-		}
-	}
-}
-
-/* Prints usage message and exits */
-static void
-usage(const char *arg0, const char *message)
-{
-	if (message)
-		msg(MSG_CRITICAL, "%s: %s\n\n", arg0, message);
-	msg(MSG_CRITICAL,
-"Usage: %s ACTION [OPTION...] [PATH...]\n",
-	    arg0);
-	msg(MSG_CRITICAL,
-"\n"
-"Where ACTION is one of:\n"
-"  -c, --compare            Show differences between stored and real metadata\n"
-"  -s, --save               Save current metadata\n"
-"  -a, --apply              Apply stored metadata\n"
-"  -d, --dump               Dump stored (if no PATH is given) or real metadata\n"
-"                           (if PATH is present, e.g. ./) in human-readable form\n"
-"  -h, --help               Help message (this text)\n"
-"\n"
-"Valid OPTIONS are:\n"
-"  -v, --verbose            Print more verbose messages\n"
-"  -q, --quiet              Print less verbose messages\n"
-"  -m, --mtime              Also take mtime into account for diff or apply\n"
-"  -e, --empty-dirs         Recreate missing empty directories\n"
-"  -E, --remove-empty-dirs  Remove extra empty directories\n"
-"  -g, --git                Do not omit .git directories\n"
-"  -f, --file=FILE          Set metadata file (" METAFILE " by default)\n"
-	    );
-
-	exit(message ? EXIT_FAILURE : EXIT_SUCCESS);
-}
-
-/* Options */
-static struct option long_options[] = {
-	{ "compare",           no_argument,       NULL, 'c' },
-	{ "save",              no_argument,       NULL, 's' },
-	{ "apply",             no_argument,       NULL, 'a' },
-	{ "dump",              no_argument,       NULL, 'd' },
-	{ "help",              no_argument,       NULL, 'h' },
-	{ "verbose",           no_argument,       NULL, 'v' },
-	{ "quiet",             no_argument,       NULL, 'q' },
-	{ "mtime",             no_argument,       NULL, 'm' },
-	{ "empty-dirs",        no_argument,       NULL, 'e' },
-	{ "remove-empty-dirs", no_argument,       NULL, 'E' },
-	{ "git",               no_argument,       NULL, 'g' },
-	{ "file",              required_argument, NULL, 'f' },
-	{ NULL, 0, NULL, 0 }
-};
-
-/* Main function */
-int
-main(int argc, char **argv)
-{
-	int i, c;
-	struct metahash *real = NULL;
-	struct metahash *stored = NULL;
-	int action = 0;
-
-	/* Parse options */
-	i = 0;
-	while (1) {
-		int option_index = 0;
-		c = getopt_long(argc, argv, "csadhvqmeEgf:",
-		                long_options, &option_index);
-		if (c == -1)
-			break;
-		switch (c) {
-		case 'c': /* compare */           action |= ACTION_DIFF;  i++;   break;
-		case 's': /* save */              action |= ACTION_SAVE;  i++;   break;
-		case 'a': /* apply */             action |= ACTION_APPLY; i++;   break;
-		case 'd': /* dump */              action |= ACTION_DUMP;  i++;   break;
-		case 'h': /* help */              action |= ACTION_HELP;  i++;   break;
-		case 'v': /* verbose */           adjust_verbosity(1);           break;
-		case 'q': /* quiet */             adjust_verbosity(-1);          break;
-		case 'm': /* mtime */             settings.do_mtime = true;      break;
-		case 'e': /* empty-dirs */        settings.do_emptydirs = true;  break;
-		case 'E': /* remove-empty-dirs */ settings.do_removeemptydirs = true;
-			                              break;
-		case 'g': /* git */               settings.do_git = true;        break;
-		case 'f': /* file */              settings.metafile = optarg;    break;
-		default:
-			usage(argv[0], "unknown option");
-		}
-	}
-
-	/* Make sure only one action is specified */
-	if (i != 1)
-		usage(argv[0], "incorrect option(s)");
-
-	/* Make sure --empty-dirs is only used with apply */
-	if (settings.do_emptydirs && action != ACTION_APPLY)
-		usage(argv[0], "--empty-dirs is only valid with --apply");
-
-	/* Make sure --remove-empty-dirs is only used with apply */
-	if (settings.do_removeemptydirs && action != ACTION_APPLY)
-		usage(argv[0], "--remove-empty-dirs is only valid with --apply");
-
-	if (action == ACTION_HELP)
-		usage(argv[0], NULL);
-
-	/* Perform action */
-	if (action & ACTIONS_READING && !(action == ACTION_DUMP && optind < argc)) {
-		mentries_fromfile(&stored, settings.metafile);
-		if (!stored) {
-			msg(MSG_CRITICAL, "Failed to load metadata from %s\n",
-			    settings.metafile);
-			exit(EXIT_FAILURE);
-		}
-	}
-
-	if (optind < argc) {
-		while (optind < argc)
-			mentries_recurse_path(argv[optind++], &real, &settings);
-	} else if (action != ACTION_DUMP) {
-		mentries_recurse_path(".", &real, &settings);
-	}
-
-	if (!real && (action != ACTION_DUMP || optind < argc)) {
-		msg(MSG_CRITICAL,
-		    "Failed to load metadata from file system\n");
-		exit(EXIT_FAILURE);
-	}
-
-	switch (action) {
-	case ACTION_DIFF:
-		mentries_compare(real, stored, compare_print, &settings);
-		break;
-	case ACTION_SAVE:
-		mentries_tofile(real, settings.metafile);
-		break;
-	case ACTION_APPLY:
-		mentries_compare(real, stored, compare_fix, &settings);
-		if (settings.do_emptydirs)
-			fixup_emptydirs();
-		if (settings.do_removeemptydirs)
-			fixup_newemptydirs();
-		break;
-	case ACTION_DUMP:
-		mentries_dump(real ? real : stored);
-		break;
-	}
-
-	exit(EXIT_SUCCESS);
-}
-
diff --git a/metastore.h b/metastore.h
deleted file mode 100644
index 384808d..0000000
--- a/metastore.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Main functions of the program.
- *
- * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-#ifndef METASTORE_H
-#define METASTORE_H
-
-/* Each file starts with SIGNATURE and VERSION */
-#define SIGNATURE    "MeTaSt00r3"
-#define SIGNATURELEN 10
-#define VERSION      "\0\0\0\0\0\0\0\0"
-#define VERSIONLEN   8
-
-/* Default filename */
-#define METAFILE     "./.metadata"
-
-/* Utility defines for the action to take */
-#define ACTION_APPLY 0x01
-#define ACTION_DIFF  0x02
-#define ACTION_DUMP  0x04
-#define ACTION_SAVE  0x10
-#define ACTION_HELP  0x80
-
-/* Action masks */
-#define ACTIONS_READING 0x07
-#define ACTIONS_WRITING 0x70
-
-#endif /* METASTORE_H */
diff --git a/settings.h b/settings.h
deleted file mode 100644
index a9ef996..0000000
--- a/settings.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2013 Przemyslaw Pawelczyk <przemoc@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-#ifndef SETTINGS_H
-#define SETTINGS_H
-
-#include <stdbool.h>
-
-/* Data structure to hold metastore settings */
-struct metasettings {
-	char *metafile;          /* path to the file containing the metadata */
-	bool do_mtime;           /* should mtimes be corrected? */
-	bool do_emptydirs;       /* should empty dirs be recreated? */
-	bool do_removeemptydirs; /* should new empty dirs be removed? */
-	bool do_git;             /* should .git dirs be processed? */
-};
-
-/* Convenient typedef for immutable settings */
-typedef const struct metasettings msettings;
-
-#endif /* SETTINGS_H */
diff --git a/src/metaentry.c b/src/metaentry.c
new file mode 100644
index 0000000..f3ba692
--- /dev/null
+++ b/src/metaentry.c
@@ -0,0 +1,677 @@
+/*
+ * Various functions to work with meta entries.
+ *
+ * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <sys/xattr.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/mman.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <errno.h>
+#include <bsd/string.h>
+#include <time.h>
+
+#include "metastore.h"
+#include "metaentry.h"
+#include "utils.h"
+
+/* Free's a metaentry and all its parameters */
+static void
+mentry_free(struct metaentry *m)
+{
+	unsigned 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);
+}
+
+/* Allocates an empty metahash table */
+static struct metahash *
+mhash_alloc()
+{
+	struct metahash *mhash;
+	mhash = xmalloc(sizeof(struct metahash));
+	memset(mhash, 0, sizeof(struct metahash));
+	return mhash;
+}
+
+/* Generates a hash key (using djb2) */
+static unsigned int
+hash(const char *str)
+{
+	unsigned int hash = 5381;
+	int c;
+
+	while ((c = *str++))
+		hash = ((hash << 5) + hash) + c;
+
+	return hash % HASH_INDEXES;
+}
+
+/* Allocates an empty metaentry */
+static struct metaentry *
+mentry_alloc()
+{
+	struct metaentry *mentry;
+	mentry = xmalloc(sizeof(struct metaentry));
+	memset(mentry, 0, sizeof(struct metaentry));
+	return mentry;
+}
+
+/* Does a bisect search for the closest match in a metaentry list */
+struct metaentry *
+mentry_find(const char *path, struct metahash *mhash)
+{
+	struct metaentry *base;
+	unsigned int key;
+
+	if (!mhash) {
+		msg(MSG_ERROR, "%s called with empty hash table\n", __FUNCTION__);
+		return NULL;
+	}
+
+	key = hash(path);
+	for (base = mhash->bucket[key]; base; base = base->next) {
+		if (!strcmp(base->path, path))
+			return base;
+	}
+
+	return NULL;
+}
+
+/* Inserts a metaentry into a metaentry list */
+static void
+mentry_insert(struct metaentry *mentry, struct metahash *mhash)
+{
+	unsigned int key;
+
+	key = hash(mentry->path);
+	mentry->next = mhash->bucket[key];
+	mhash->bucket[key] = mentry;
+}
+
+#ifdef DEBUG
+/* Prints a metaentry */
+static void
+mentry_print(const struct metaentry *mentry)
+{
+	int i;
+
+	if (!mentry || !mentry->path) {
+		msg(MSG_DEBUG,
+		    "Incorrect meta entry passed to printmetaentry\n");
+		return;
+	}
+
+	msg(MSG_DEBUG, "===========================\n");
+	msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry);
+	msg(MSG_DEBUG, "===========================\n");
+
+	msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path);
+	msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner);
+	msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group);
+	msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
+	msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
+	msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode);
+	for (i = 0; i < mentry->xattrs; i++) {
+		msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
+		binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
+		msg(MSG_DEBUG, "\"\n");
+	}
+
+	msg(MSG_DEBUG, "===========================\n\n");
+}
+
+/* Prints all metaentries in a metaentry list */
+static void
+mentries_print(const struct metahash *mhash)
+{
+	const struct metaentry *mentry;
+	int index;
+
+	for (index = 0; index < HASH_INDEXES; index++)
+		for (mentry = mhash->bucket[index]; mentry; mentry = mentry->next)
+			mentry_print(mentry);
+
+	msg(MSG_DEBUG, "%i entries in total\n", mhash->count);
+}
+#endif
+
+/* Creates a metaentry for the file/dir/etc at path */
+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)) {
+		msg(MSG_ERROR, "lstat failed for %s: %s\n",
+		    path, strerror(errno));
+		return NULL;
+	}
+
+	pbuf = xgetpwuid(sbuf.st_uid);
+	if (!pbuf) {
+		msg(MSG_ERROR, "getpwuid failed for %s: uid %i not found\n",
+		    path, (int)sbuf.st_uid);
+		return NULL;
+	}
+
+	gbuf = xgetgrgid(sbuf.st_gid);
+	if (!gbuf) {
+		msg(MSG_ERROR, "getgrgid failed for %s: gid %i not found\n",
+		    path, (int)sbuf.st_gid);
+		return NULL;
+	}
+
+	mentry = mentry_alloc();
+	mentry->path = xstrdup(path);
+	mentry->pathlen = strlen(mentry->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) {
+		/* Perhaps the FS doesn't support xattrs? */
+		if (errno == ENOTSUP)
+			return mentry;
+
+		msg(MSG_ERROR, "listxattr failed for %s: %s\n",
+		    path, strerror(errno));
+		return NULL;
+	}
+
+	list = xmalloc(lsize);
+	lsize = listxattr(path, list, lsize);
+	if (lsize < 0) {
+		msg(MSG_ERROR, "listxattr failed for %s: %s\n",
+		    path, strerror(errno));
+		free(list);
+		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) {
+			msg(MSG_ERROR, "getxattr failed for %s: %s\n",
+			    path, strerror(errno));
+			free(list);
+			mentry_free(mentry);
+			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) {
+			msg(MSG_ERROR, "getxattr failed for %s: %s\n",
+			    path, strerror(errno));
+			free(list);
+			mentry_free(mentry);
+			return NULL;
+		}
+		i++;
+	}
+
+	free(list);
+	return mentry;
+}
+
+/* Cleans up a path and makes it relative to current working dir unless it is absolute */
+static char *
+normalize_path(const char *orig)
+{
+	char *real = realpath(orig, NULL);
+	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;
+}
+
+/* Internal function for the recursive path walk */
+static void
+mentries_recurse(const char *path, struct metahash *mhash, msettings *st)
+{
+	struct stat sbuf;
+	struct metaentry *mentry;
+	char tpath[PATH_MAX];
+	DIR *dir;
+	struct dirent *dent;
+
+	if (!path)
+		return;
+
+	if (lstat(path, &sbuf)) {
+		msg(MSG_ERROR, "lstat failed for %s: %s\n",
+		    path, strerror(errno));
+		return;
+	}
+
+	mentry = mentry_create(path);
+	if (!mentry)
+		return;
+
+	mentry_insert(mentry, mhash);
+
+	if (S_ISDIR(sbuf.st_mode)) {
+		dir = opendir(path);
+		if (!dir) {
+			msg(MSG_ERROR, "opendir failed for %s: %s\n",
+			    path, strerror(errno));
+			return;
+		}
+
+		while ((dent = readdir(dir))) {
+			if (!strcmp(dent->d_name, ".") ||
+			    !strcmp(dent->d_name, "..") ||
+			    (!st->do_git && !strcmp(dent->d_name, ".git")))
+				continue;
+			snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
+			tpath[PATH_MAX - 1] = '\0';
+			mentries_recurse(tpath, mhash, st);
+		}
+
+		closedir(dir);
+	}
+}
+
+/* Recurses opath and adds metadata entries to the metaentry list */
+void
+mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st)
+{
+	char *path = normalize_path(opath);
+
+	if (!(*mhash))
+		*mhash = mhash_alloc();
+	mentries_recurse(path, *mhash, st);
+	free(path);
+}
+
+/* Stores metaentries to a file */
+void
+mentries_tofile(const struct metahash *mhash, const char *path)
+{
+	FILE *to;
+	const struct metaentry *mentry;
+	int key;
+	unsigned i;
+
+	to = fopen(path, "w");
+	if (!to) {
+		msg(MSG_CRITICAL, "Failed to open %s: %s\n",
+		    path, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	write_binary_string(SIGNATURE, SIGNATURELEN, to);
+	write_binary_string(VERSION, VERSIONLEN, to);
+
+	for (key = 0; key < HASH_INDEXES; key++) {
+		for (mentry = mhash->bucket[key]; 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);
+}
+
+/* Creates a metaentry list from a file */
+void
+mentries_fromfile(struct metahash **mhash, const char *path)
+{
+	struct metaentry *mentry;
+	char *mmapstart;
+	char *ptr;
+	char *max;
+	int fd;
+	struct stat sbuf;
+	unsigned i;
+
+	if (!(*mhash))
+		*mhash = mhash_alloc();
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		msg(MSG_CRITICAL, "Failed to open %s: %s\n",
+		    path, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	if (fstat(fd, &sbuf)) {
+		msg(MSG_CRITICAL, "Failed to stat %s: %s\n",
+		    path, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
+		msg(MSG_CRITICAL, "File %s has an invalid size\n", path);
+		exit(EXIT_FAILURE);
+	}
+
+	mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ,
+			 MAP_SHARED, fd, 0);
+	if (mmapstart == MAP_FAILED) {
+		msg(MSG_CRITICAL, "Unable to mmap %s: %s\n",
+		    path, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	ptr = mmapstart;
+	max = mmapstart + sbuf.st_size;
+
+	if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
+		msg(MSG_CRITICAL, "Invalid signature for file %s\n", path);
+		goto out;
+	}
+	ptr += SIGNATURELEN;
+
+	if (strncmp(ptr, VERSION, VERSIONLEN)) {
+		msg(MSG_CRITICAL, "Invalid version of file %s\n", path);
+		goto out;
+	}
+	ptr += VERSIONLEN;
+
+	while (ptr < mmapstart + sbuf.st_size) {
+		if (*ptr == '\0') {
+			msg(MSG_CRITICAL, "Invalid characters in file %s\n",
+			    path);
+			goto out;
+		}
+
+		mentry = mentry_alloc();
+		mentry->path = read_string(&ptr, max);
+		mentry->pathlen = strlen(mentry->path);
+		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) {
+			mentry_insert(mentry, *mhash);
+			continue;
+		}
+
+		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, *mhash);
+	}
+
+out:
+	munmap(mmapstart, sbuf.st_size);
+	close(fd);
+}
+
+/* Searches haystack for an xattr matching xattr number n in needle */
+int
+mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle,
+                  unsigned n)
+{
+	unsigned 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 */
+static int
+mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
+{
+	unsigned 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;
+}
+
+/* Compares two metaentries and returns an int with a bitmask of differences */
+int
+mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st)
+{
+	int retval = DIFF_NONE;
+
+	if (!left || !right) {
+		msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
+		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 (st->do_mtime && strcmp(left->path, st->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;
+}
+
+/* Compares lists of real and stored metadata and calls pfunc for each */
+void
+mentries_compare(struct metahash *mhashreal,
+		 struct metahash *mhashstored,
+		 void (*pfunc)
+		 (struct metaentry *real, struct metaentry *stored, int cmp),
+		 msettings *st)
+{
+	struct metaentry *real, *stored;
+	int key;
+
+	if (!mhashreal || !mhashstored) {
+		msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
+		return;
+	}
+
+	for (key = 0; key < HASH_INDEXES; key++) {
+		for (real = mhashreal->bucket[key]; real; real = real->next) {
+			stored = mentry_find(real->path, mhashstored);
+
+			if (!stored)
+				pfunc(real, NULL, DIFF_ADDED);
+			else
+				pfunc(real, stored, mentry_compare(real, stored, st));
+		}
+
+		for (stored = mhashstored->bucket[key]; stored; stored = stored->next) {
+			real = mentry_find(stored->path, mhashreal);
+
+			if (!real)
+				pfunc(NULL, stored, DIFF_DELE);
+		}
+	}
+}
+
+/* Dumps given metadata */
+void
+mentries_dump(struct metahash *mhash)
+{
+	const struct metaentry *mentry;
+	char mode[11 + 1] = "";
+	char date[12 + 2 + 2 + 2*1 + 1 + 2 + 2 + 2 + 2*1 + 1] = "";
+	char zone[5 + 1] = "";
+	struct tm cal;
+
+	for (int key = 0; key < HASH_INDEXES; key++) {
+		for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
+			strmode(mentry->mode, mode);
+			localtime_r(&mentry->mtime, &cal);
+			strftime(date, sizeof(date), "%F %T", &cal);
+			strftime(zone, sizeof(zone), "%z", &cal);
+			printf("%s\t%s\t%s\t%s.%09ld %s\t%s%s\n",
+			       mode,
+			       mentry->owner, mentry->group,
+			       date, mentry->mtimensec, zone,
+			       mentry->path, S_ISDIR(mentry->mode) ? "/" : "");
+			for (int i = 0; i < mentry->xattrs; i++) {
+				printf("\t\t\t\t%s%s\t%s=",
+				       mentry->path, S_ISDIR(mentry->mode) ? "/" : "",
+				       mentry->xattr_names[i]);
+				ssize_t p = 0;
+				for (; p < mentry->xattr_lvalues[i]; p++) {
+					const char ch = mentry->xattr_values[i][p];
+					if ((unsigned)(ch - 32) > 126 - 32) {
+						p = -1;
+						break;
+					}
+				}
+				if (p >= 0)
+					printf("\"%.*s\"\n",
+					       (int)mentry->xattr_lvalues[i],
+					       mentry->xattr_values[i]);
+				else {
+					printf("0x");
+					for (p = 0; p < mentry->xattr_lvalues[i]; p++)
+						printf("%02hhx", (char)mentry->xattr_values[i][p]);
+					printf("\n");
+				}
+			}
+		}
+	}
+}
diff --git a/src/metaentry.h b/src/metaentry.h
new file mode 100644
index 0000000..666c5af
--- /dev/null
+++ b/src/metaentry.h
@@ -0,0 +1,97 @@
+/*
+ * Various functions to work with meta entries.
+ *
+ * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef METAENTRY_H
+#define METAENTRY_H
+
+#include <stdbool.h>
+
+#include "settings.h"
+
+/* Data structure to hold all metadata for a file/dir */
+struct metaentry {
+	struct metaentry *next; /* For the metahash chains */
+	struct metaentry *list; /* For creating additional lists of entries */
+
+	char *path;
+	unsigned int pathlen;
+
+	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;
+};
+
+#define HASH_INDEXES 1024
+
+/* Data structure to hold a number of metadata entries */
+struct metahash {
+        struct metaentry *bucket[HASH_INDEXES];
+        unsigned int count;
+};
+
+/* Create a metaentry for the file/dir/etc at path */
+struct metaentry *mentry_create(const char *path);
+
+/* Recurses opath and adds metadata entries to the metaentry list */
+void mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st);
+
+/* Stores a metaentry list to a file */
+void mentries_tofile(const struct metahash *mhash, const char *path);
+
+/* Creates a metaentry list from a file */
+void mentries_fromfile(struct metahash **mhash, const char *path);
+
+/* Searches haystack for an xattr matching xattr number n in needle */
+int mentry_find_xattr(struct metaentry *haystack,
+                      struct metaentry *needle,
+                      unsigned n);
+
+#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
+
+/* Compares two metaentries and returns an int with a bitmask of differences */
+int mentry_compare(struct metaentry *left,
+		   struct metaentry *right,
+		   msettings *st);
+
+/* Compares lists of real and stored metadata and calls pfunc for each */
+void mentries_compare(struct metahash *mhashreal,
+                      struct metahash *mhashstored,
+                      void (*pfunc)(struct metaentry *real,
+                                    struct metaentry *stored,
+                                    int cmp),
+                      msettings *st);
+
+void mentries_dump(struct metahash *mhash);
+
+#endif /* METAENTRY_H */
diff --git a/src/metastore.c b/src/metastore.c
new file mode 100644
index 0000000..2956cab
--- /dev/null
+++ b/src/metastore.c
@@ -0,0 +1,539 @@
+/*
+ * Main functions of the program.
+ *
+ * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#define _BSD_SOURCE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <utime.h>
+#include <sys/xattr.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "metastore.h"
+#include "settings.h"
+#include "utils.h"
+#include "metaentry.h"
+
+/* metastore settings */
+static struct metasettings settings = {
+	.metafile = METAFILE,
+	.do_mtime = false,
+	.do_emptydirs = false,
+	.do_removeemptydirs = false,
+	.do_git = false,
+};
+
+/* Used to create lists of dirs / other files which are missing in the fs */
+static struct metaentry *missingdirs = NULL;
+static struct metaentry *missingothers = NULL;
+
+/* Used to create lists of dirs / other files which are missing in metadata */
+static struct metaentry *extradirs = NULL;
+
+/*
+ * Inserts an entry in a linked list ordered by pathlen
+ */
+static void
+insert_entry_plist(struct metaentry **list, struct metaentry *entry)
+{
+	struct metaentry **parent;
+
+	for (parent = list; *parent; parent = &((*parent)->list)) {
+		if ((*parent)->pathlen > entry->pathlen)
+			break;
+	}
+
+	entry->list = *parent;
+	*parent = entry;
+}
+
+/*
+ * Inserts an entry in a linked list ordered by pathlen descendingly
+ */
+static void
+insert_entry_pdlist(struct metaentry **list, struct metaentry *entry)
+{
+	struct metaentry **parent;
+
+	for (parent = list; *parent; parent = &((*parent)->list)) {
+		if ((*parent)->pathlen < entry->pathlen)
+			break;
+	}
+
+	entry->list = *parent;
+	*parent = entry;
+}
+
+/*
+ * Prints differences between real and stored actual metadata
+ * - for use in mentries_compare
+ */
+static void
+compare_print(struct metaentry *real, struct metaentry *stored, int cmp)
+{
+	if (!real && !stored) {
+		msg(MSG_ERROR, "%s called with incorrect arguments\n", __FUNCTION__);
+		return;
+	}
+
+	if (cmp == DIFF_NONE) {
+		msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
+		return;
+	}
+
+	msg(MSG_QUIET, "%s:\t", real ? real->path : stored->path);
+
+	if (cmp & DIFF_ADDED)
+		msg(MSG_QUIET, "added ", real->path);
+	if (cmp & DIFF_DELE)
+		msg(MSG_QUIET, "removed ", stored->path);
+	if (cmp & DIFF_OWNER)
+		msg(MSG_QUIET, "owner ");
+	if (cmp & DIFF_GROUP)
+		msg(MSG_QUIET, "group ");
+	if (cmp & DIFF_MODE)
+		msg(MSG_QUIET, "mode ");
+	if (cmp & DIFF_TYPE)
+		msg(MSG_QUIET, "type ");
+	if (cmp & DIFF_MTIME)
+		msg(MSG_QUIET, "mtime ");
+	if (cmp & DIFF_XATTR)
+		msg(MSG_QUIET, "xattr ");
+	msg(MSG_QUIET, "\n");
+}
+
+/*
+ * Tries to change the real metadata to match the stored one
+ * - for use in mentries_compare
+ */
+static void
+compare_fix(struct metaentry *real, struct metaentry *stored, int cmp)
+{
+	struct group *group;
+	struct passwd *owner;
+	gid_t gid = -1;
+	uid_t uid = -1;
+	struct utimbuf tbuf;
+	unsigned i;
+
+	if (!real && !stored) {
+		msg(MSG_ERROR, "%s called with incorrect arguments\n",
+		    __FUNCTION__);
+		return;
+	}
+
+	if (!real) {
+		if (S_ISDIR(stored->mode))
+			insert_entry_plist(&missingdirs, stored);
+		else
+			insert_entry_plist(&missingothers, stored);
+
+		msg(MSG_NORMAL, "%s:\tremoved\n", stored->path);
+		return;
+	}
+
+	if (!stored) {
+		if (S_ISDIR(real->mode))
+			insert_entry_pdlist(&extradirs, real);
+		msg(MSG_NORMAL, "%s:\tadded\n", real->path);
+		return;
+	}
+
+	if (cmp == DIFF_NONE) {
+		msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
+		return;
+	}
+
+	if (cmp & DIFF_TYPE) {
+		msg(MSG_NORMAL, "%s:\tnew type, will not change metadata\n",
+		    real->path);
+		return;
+	}
+
+	msg(MSG_QUIET, "%s:\tchanging metadata\n", real->path);
+
+	while (cmp & (DIFF_OWNER | DIFF_GROUP)) {
+		if (cmp & DIFF_OWNER) {
+			msg(MSG_NORMAL, "%s:\tchanging owner from %s to %s\n",
+			    real->path, real->group, stored->group);
+			owner = xgetpwnam(stored->owner);
+			if (!owner) {
+				msg(MSG_DEBUG, "\tgetpwnam failed: %s\n",
+				    strerror(errno));
+				break;
+			}
+			uid = owner->pw_uid;
+		}
+
+		if (cmp & DIFF_GROUP) {
+			msg(MSG_NORMAL, "%s:\tchanging group from %s to %s\n",
+			    real->path, real->group, stored->group);
+			group = xgetgrnam(stored->group);
+			if (!group) {
+				msg(MSG_DEBUG, "\tgetgrnam failed: %s\n",
+				    strerror(errno));
+				break;
+			}
+			gid = group->gr_gid;
+		}
+
+		if (lchown(real->path, uid, gid)) {
+			msg(MSG_DEBUG, "\tlchown failed: %s\n",
+			    strerror(errno));
+			break;
+		}
+		break;
+	}
+
+	if (cmp & DIFF_MODE) {
+		msg(MSG_NORMAL, "%s:\tchanging mode from 0%o to 0%o\n",
+		    real->path, real->mode & 07777, stored->mode & 07777);
+		if (chmod(real->path, stored->mode & 07777))
+			msg(MSG_DEBUG, "\tchmod failed: %s\n", strerror(errno));
+	}
+
+	/* FIXME: Use utimensat here, or even better - lutimensat */
+	if ((cmp & DIFF_MTIME) && S_ISLNK(real->mode)) {
+		msg(MSG_NORMAL, "%s:\tsymlink, not changing mtime\n", real->path);
+	} else if (cmp & DIFF_MTIME) {
+		msg(MSG_NORMAL, "%s:\tchanging mtime from %ld to %ld\n",
+		    real->path, real->mtime, stored->mtime);
+		tbuf.actime = stored->mtime;
+		tbuf.modtime = stored->mtime;
+		if (utime(real->path, &tbuf)) {
+			msg(MSG_DEBUG, "\tutime failed: %s\n", strerror(errno));
+			return;
+		}
+	}
+
+	if (cmp & DIFF_XATTR) {
+		for (i = 0; i < real->xattrs; i++) {
+			/* Any attrs to remove? */
+			if (mentry_find_xattr(stored, real, i) >= 0)
+				continue;
+
+			msg(MSG_NORMAL, "%s:\tremoving xattr %s\n",
+			    real->path, real->xattr_names[i]);
+			if (lremovexattr(real->path, real->xattr_names[i]))
+				msg(MSG_DEBUG, "\tlremovexattr failed: %s\n",
+				    strerror(errno));
+		}
+
+		for (i = 0; i < stored->xattrs; i++) {
+			/* Any xattrs to add? (on change they are removed above) */
+			if (mentry_find_xattr(real, stored, i) >= 0)
+				continue;
+
+			msg(MSG_NORMAL, "%s:\tadding xattr %s\n",
+			    stored->path, stored->xattr_names[i]);
+			if (lsetxattr(stored->path, stored->xattr_names[i],
+				      stored->xattr_values[i],
+				      stored->xattr_lvalues[i], XATTR_CREATE))
+				msg(MSG_DEBUG, "\tlsetxattr failed: %s\n",
+				    strerror(errno));
+		}
+	}
+}
+
+/*
+ * Tries to fix any empty dirs which are missing from the filesystem by
+ * recreating them.
+ */
+static void
+fixup_emptydirs(void)
+{
+	struct metaentry *entry;
+	struct metaentry *cur;
+	struct metaentry **parent;
+	char *bpath;
+	char *delim;
+	size_t blen;
+	struct metaentry *new;
+
+	if (!missingdirs)
+		return;
+	msg(MSG_DEBUG, "\nAttempting to recreate missing dirs\n");
+
+	/* If directory x/y is missing, but file x/y/z is also missing,
+	 * we should prune directory x/y from the list of directories to
+	 * recreate since the deletition of x/y is likely to be genuine
+	 * (as opposed to empty dir pruning like git/cvs does).
+	 *
+	 * Also, if file x/y/z is missing, any child directories of
+	 * x/y should be pruned as they are probably also intentionally
+	 * removed.
+	 */
+
+	msg(MSG_DEBUG, "List of candidate dirs:\n");
+	for (cur = missingdirs; cur; cur = cur->list)
+		msg(MSG_DEBUG, " %s\n", cur->path);
+
+	for (entry = missingothers; entry; entry = entry->list) {
+		msg(MSG_DEBUG, "Pruning using file %s\n", entry->path);
+		bpath = xstrdup(entry->path);
+		delim = strrchr(bpath, '/');
+		if (!delim) {
+			msg(MSG_NORMAL, "No delimiter found in %s\n", bpath);
+			free(bpath);
+			continue;
+		}
+		*delim = '\0';
+
+		parent = &missingdirs;
+		for (cur = *parent; cur; cur = cur->list) {
+			if (strcmp(cur->path, bpath)) {
+				parent = &cur->list;
+				continue;
+			}
+
+			msg(MSG_DEBUG, "Prune phase 1 - %s\n", cur->path);
+			*parent = cur->list;
+		}
+
+		/* Now also prune subdirs of the base dir */
+		*delim++ = '/';
+		*delim = '\0';
+		blen = strlen(bpath);
+
+		parent = &missingdirs;
+		for (cur = *parent; cur; cur = cur->list) {
+			if (strncmp(cur->path, bpath, blen)) {
+				parent = &cur->list;
+				continue;
+			}
+
+			msg(MSG_DEBUG, "Prune phase 2 - %s\n", cur->path);
+			*parent = cur->list;
+		}
+
+		free(bpath);
+	}
+	msg(MSG_DEBUG, "\n");
+
+	for (cur = missingdirs; cur; cur = cur->list) {
+		msg(MSG_QUIET, "%s:\trecreating...", cur->path);
+		if (mkdir(cur->path, cur->mode)) {
+			msg(MSG_QUIET, "failed (%s)\n", strerror(errno));
+			continue;
+		}
+		msg(MSG_QUIET, "ok\n");
+
+		new = mentry_create(cur->path);
+		if (!new) {
+			msg(MSG_QUIET, "Failed to get metadata for %s\n");
+			continue;
+		}
+
+		compare_fix(new, cur, mentry_compare(new, cur, &settings));
+	}
+}
+
+/*
+ * Deletes any empty dirs present in the filesystem that are missing
+ * from the metadata.
+ * An "empty" dir is one which either:
+ *  - is empty; or
+ *  - only contains empty dirs
+ */
+static void
+fixup_newemptydirs(void)
+{
+	struct metaentry **cur;
+	int removed_dirs = 1;
+
+	if (!extradirs)
+		return;
+
+	/* This is a simpleminded algorithm that attempts to rmdir() all
+	 * directories discovered missing from the metadata. Naturally, this will
+	 * succeed only on the truly empty directories, but depending on the order,
+	 * it may mean that parent directory removal are attempted to be removed
+	 * *before* the children. To circumvent this, keep looping around all the
+	 * directories until none have been successfully removed.  This is a
+	 * O(N**2) algorithm, so don't try to remove too many nested directories
+	 * at once (e.g. thousands).
+	 *
+	 * Note that this will succeed only if each parent directory is writable.
+	 */
+	while (removed_dirs) {
+		removed_dirs = 0;
+		msg(MSG_DEBUG, "\nAttempting to delete empty dirs\n");
+		for (cur = &extradirs; *cur;) {
+			msg(MSG_QUIET, "%s:\tremoving...", (*cur)->path);
+			if (rmdir((*cur)->path)) {
+				msg(MSG_QUIET, "failed (%s)\n", strerror(errno));
+				cur = &(*cur)->list;
+				continue;
+			}
+			/* No freeing, because OS will do the job at the end. */
+			*cur = (*cur)->list;
+			removed_dirs++;
+			msg(MSG_QUIET, "ok\n");
+		}
+	}
+}
+
+/* Prints usage message and exits */
+static void
+usage(const char *arg0, const char *message)
+{
+	if (message)
+		msg(MSG_CRITICAL, "%s: %s\n\n", arg0, message);
+	msg(MSG_CRITICAL,
+"Usage: %s ACTION [OPTION...] [PATH...]\n",
+	    arg0);
+	msg(MSG_CRITICAL,
+"\n"
+"Where ACTION is one of:\n"
+"  -c, --compare            Show differences between stored and real metadata\n"
+"  -s, --save               Save current metadata\n"
+"  -a, --apply              Apply stored metadata\n"
+"  -d, --dump               Dump stored (if no PATH is given) or real metadata\n"
+"                           (if PATH is present, e.g. ./) in human-readable form\n"
+"  -h, --help               Help message (this text)\n"
+"\n"
+"Valid OPTIONS are:\n"
+"  -v, --verbose            Print more verbose messages\n"
+"  -q, --quiet              Print less verbose messages\n"
+"  -m, --mtime              Also take mtime into account for diff or apply\n"
+"  -e, --empty-dirs         Recreate missing empty directories\n"
+"  -E, --remove-empty-dirs  Remove extra empty directories\n"
+"  -g, --git                Do not omit .git directories\n"
+"  -f, --file=FILE          Set metadata file (" METAFILE " by default)\n"
+	    );
+
+	exit(message ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+/* Options */
+static struct option long_options[] = {
+	{ "compare",           no_argument,       NULL, 'c' },
+	{ "save",              no_argument,       NULL, 's' },
+	{ "apply",             no_argument,       NULL, 'a' },
+	{ "dump",              no_argument,       NULL, 'd' },
+	{ "help",              no_argument,       NULL, 'h' },
+	{ "verbose",           no_argument,       NULL, 'v' },
+	{ "quiet",             no_argument,       NULL, 'q' },
+	{ "mtime",             no_argument,       NULL, 'm' },
+	{ "empty-dirs",        no_argument,       NULL, 'e' },
+	{ "remove-empty-dirs", no_argument,       NULL, 'E' },
+	{ "git",               no_argument,       NULL, 'g' },
+	{ "file",              required_argument, NULL, 'f' },
+	{ NULL, 0, NULL, 0 }
+};
+
+/* Main function */
+int
+main(int argc, char **argv)
+{
+	int i, c;
+	struct metahash *real = NULL;
+	struct metahash *stored = NULL;
+	int action = 0;
+
+	/* Parse options */
+	i = 0;
+	while (1) {
+		int option_index = 0;
+		c = getopt_long(argc, argv, "csadhvqmeEgf:",
+		                long_options, &option_index);
+		if (c == -1)
+			break;
+		switch (c) {
+		case 'c': /* compare */           action |= ACTION_DIFF;  i++;   break;
+		case 's': /* save */              action |= ACTION_SAVE;  i++;   break;
+		case 'a': /* apply */             action |= ACTION_APPLY; i++;   break;
+		case 'd': /* dump */              action |= ACTION_DUMP;  i++;   break;
+		case 'h': /* help */              action |= ACTION_HELP;  i++;   break;
+		case 'v': /* verbose */           adjust_verbosity(1);           break;
+		case 'q': /* quiet */             adjust_verbosity(-1);          break;
+		case 'm': /* mtime */             settings.do_mtime = true;      break;
+		case 'e': /* empty-dirs */        settings.do_emptydirs = true;  break;
+		case 'E': /* remove-empty-dirs */ settings.do_removeemptydirs = true;
+			                              break;
+		case 'g': /* git */               settings.do_git = true;        break;
+		case 'f': /* file */              settings.metafile = optarg;    break;
+		default:
+			usage(argv[0], "unknown option");
+		}
+	}
+
+	/* Make sure only one action is specified */
+	if (i != 1)
+		usage(argv[0], "incorrect option(s)");
+
+	/* Make sure --empty-dirs is only used with apply */
+	if (settings.do_emptydirs && action != ACTION_APPLY)
+		usage(argv[0], "--empty-dirs is only valid with --apply");
+
+	/* Make sure --remove-empty-dirs is only used with apply */
+	if (settings.do_removeemptydirs && action != ACTION_APPLY)
+		usage(argv[0], "--remove-empty-dirs is only valid with --apply");
+
+	if (action == ACTION_HELP)
+		usage(argv[0], NULL);
+
+	/* Perform action */
+	if (action & ACTIONS_READING && !(action == ACTION_DUMP && optind < argc)) {
+		mentries_fromfile(&stored, settings.metafile);
+		if (!stored) {
+			msg(MSG_CRITICAL, "Failed to load metadata from %s\n",
+			    settings.metafile);
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	if (optind < argc) {
+		while (optind < argc)
+			mentries_recurse_path(argv[optind++], &real, &settings);
+	} else if (action != ACTION_DUMP) {
+		mentries_recurse_path(".", &real, &settings);
+	}
+
+	if (!real && (action != ACTION_DUMP || optind < argc)) {
+		msg(MSG_CRITICAL,
+		    "Failed to load metadata from file system\n");
+		exit(EXIT_FAILURE);
+	}
+
+	switch (action) {
+	case ACTION_DIFF:
+		mentries_compare(real, stored, compare_print, &settings);
+		break;
+	case ACTION_SAVE:
+		mentries_tofile(real, settings.metafile);
+		break;
+	case ACTION_APPLY:
+		mentries_compare(real, stored, compare_fix, &settings);
+		if (settings.do_emptydirs)
+			fixup_emptydirs();
+		if (settings.do_removeemptydirs)
+			fixup_newemptydirs();
+		break;
+	case ACTION_DUMP:
+		mentries_dump(real ? real : stored);
+		break;
+	}
+
+	exit(EXIT_SUCCESS);
+}
+
diff --git a/src/metastore.h b/src/metastore.h
new file mode 100644
index 0000000..384808d
--- /dev/null
+++ b/src/metastore.h
@@ -0,0 +1,43 @@
+/*
+ * Main functions of the program.
+ *
+ * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef METASTORE_H
+#define METASTORE_H
+
+/* Each file starts with SIGNATURE and VERSION */
+#define SIGNATURE    "MeTaSt00r3"
+#define SIGNATURELEN 10
+#define VERSION      "\0\0\0\0\0\0\0\0"
+#define VERSIONLEN   8
+
+/* Default filename */
+#define METAFILE     "./.metadata"
+
+/* Utility defines for the action to take */
+#define ACTION_APPLY 0x01
+#define ACTION_DIFF  0x02
+#define ACTION_DUMP  0x04
+#define ACTION_SAVE  0x10
+#define ACTION_HELP  0x80
+
+/* Action masks */
+#define ACTIONS_READING 0x07
+#define ACTIONS_WRITING 0x70
+
+#endif /* METASTORE_H */
diff --git a/src/settings.h b/src/settings.h
new file mode 100644
index 0000000..a9ef996
--- /dev/null
+++ b/src/settings.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 Przemyslaw Pawelczyk <przemoc@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#include <stdbool.h>
+
+/* Data structure to hold metastore settings */
+struct metasettings {
+	char *metafile;          /* path to the file containing the metadata */
+	bool do_mtime;           /* should mtimes be corrected? */
+	bool do_emptydirs;       /* should empty dirs be recreated? */
+	bool do_removeemptydirs; /* should new empty dirs be removed? */
+	bool do_git;             /* should .git dirs be processed? */
+};
+
+/* Convenient typedef for immutable settings */
+typedef const struct metasettings msettings;
+
+#endif /* SETTINGS_H */
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..7c85b38
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,303 @@
+/*
+ * Main functions of the program.
+ *
+ * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#define _BSD_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <grp.h>
+#include <pwd.h>
+
+#include "utils.h"
+
+/* Controls the verbosity level for msg() */
+static int verbosity = 0;
+
+/* Adjusts the verbosity level for msg() */
+void
+adjust_verbosity(int adj)
+{
+	verbosity += adj;
+}
+
+/*
+ * Prints messages to console according to the current verbosity
+ * - see utils.h for level defines
+ */
+int
+msg(int level, const char *fmt, ...)
+{
+	int ret;
+	va_list ap;
+
+	if (level > verbosity)
+		return 0;
+
+	va_start(ap, fmt);
+
+	if (level < MSG_QUIET)
+		ret = vfprintf(stderr, fmt, ap);
+	else
+		ret = vfprintf(stdout, fmt, ap);
+
+	va_end(ap);
+	return ret;
+}
+
+/* Malloc which either succeeds or exits */
+void *
+xmalloc(size_t size)
+{
+        void *result = malloc(size);
+        if (!result) {
+                msg(MSG_CRITICAL, "Failed to malloc %zi bytes\n", size);
+                exit(EXIT_FAILURE);
+        }
+        return result;
+}
+
+/* Ditto for strdup */
+char *
+xstrdup(const char *s)
+{
+	char *result = strdup(s);
+	if (!result) {
+		msg(MSG_CRITICAL, "Failed to strdup %zi bytes\n", strlen(s));
+		exit(EXIT_FAILURE);
+	}
+	return result;
+}
+
+/* Human-readable printout of binary data */
+void
+binary_print(const char *s, ssize_t len)
+{
+	ssize_t i;
+
+	for (i = 0; i < len; i++) {
+		if (isprint(s[i]))
+			msg(MSG_DEBUG, "%c", s[i]);
+		else
+			msg(MSG_DEBUG, "0x%02X", (int)s[i]);
+	}
+}
+
+/* Writes data to a file or exits on failure */
+void
+xfwrite(const void *ptr, size_t size, FILE *stream)
+{
+	if (size && fwrite(ptr, size, 1, stream) != 1) {
+		msg(MSG_CRITICAL, "Failed to write to file: %s\n",
+		    strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+}
+
+/* Writes an int to a file, using len bytes, in bigendian order */
+void
+write_int(uint64_t value, size_t len, FILE *to)
+{
+	char buf[len];
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		buf[i] = ((value >> (8 * i)) & 0xff);
+	xfwrite(buf, len, to);
+}
+
+/* Writes a binary string to a file */
+void
+write_binary_string(const char *string, size_t len, FILE *to)
+{
+	xfwrite(string, len, to);
+}
+
+/* Writes a normal C string to a file */
+void
+write_string(const char *string, FILE *to)
+{
+	xfwrite(string, strlen(string) + 1, to);
+}
+
+/* Reads an int from a file, using len bytes, in bigendian order */
+uint64_t
+read_int(char **from, size_t len, const char *max)
+{
+	uint64_t result = 0;
+	size_t i;
+
+	if (*from + len > max) {
+		msg(MSG_CRITICAL,
+		    "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;
+}
+
+/* Reads a binary string from a file */
+char *
+read_binary_string(char **from, size_t len, const char *max)
+{
+	char *result;
+
+	if (*from + len > max) {
+		msg(MSG_CRITICAL,
+		    "Attempt to read beyond end of file, corrupt file?\n");
+		exit(EXIT_FAILURE);
+	}
+
+	result = xmalloc(len);
+	memcpy(result, *from, len);
+	*from += len;
+	return result;
+}
+
+/* Reads a normal C string from a file */
+char *
+read_string(char **from, const char *max)
+{
+	return read_binary_string(from, strlen(*from) + 1, max);
+}
+
+/* For group caching */
+static struct group *gtable = NULL;
+
+/* Initial setup of the gid table */
+static void
+create_group_table()
+{
+	struct group *tmp;
+	int count, index;
+
+	for (count = 0; getgrent(); count++) /* Do nothing */;
+
+	gtable = xmalloc(sizeof(struct group) * (count + 1));
+	memset(gtable, 0, sizeof(struct group) * (count + 1));
+	setgrent();
+
+	for (index = 0; (tmp = getgrent()) && index < count; index++) {
+		gtable[index].gr_gid = tmp->gr_gid;
+		gtable[index].gr_name = xstrdup(tmp->gr_name);
+	}
+
+	endgrent();
+}
+
+/* Caching version of getgrnam */
+struct group *
+xgetgrnam(const char *name)
+{
+	int i;
+
+	if (!gtable)
+		create_group_table();
+
+	for (i = 0; gtable[i].gr_name; i++) {
+		if (!strcmp(name, gtable[i].gr_name))
+			return &(gtable[i]);
+	}
+
+	return NULL;
+}
+
+/* Caching version of getgrgid */
+struct group *
+xgetgrgid(gid_t gid)
+{
+	int i;
+
+	if (!gtable)
+		create_group_table();
+
+	for (i = 0; gtable[i].gr_name; i++) {
+		if (gtable[i].gr_gid == gid)
+			return &(gtable[i]);
+	}
+
+	return NULL;
+}
+
+/* For user caching */
+static struct passwd *ptable = NULL;
+
+/* Initial setup of the passwd table */
+static void
+create_passwd_table()
+{
+	struct passwd *tmp;
+	int count, index;
+
+	for (count = 0; getpwent(); count++) /* Do nothing */;
+
+	ptable = xmalloc(sizeof(struct passwd) * (count + 1));
+	memset(ptable, 0, sizeof(struct passwd) * (count + 1));
+	setpwent();
+
+	for (index = 0; (tmp = getpwent()) && index < count; index++) {
+		ptable[index].pw_uid = tmp->pw_uid;
+		ptable[index].pw_name = xstrdup(tmp->pw_name);
+	}
+
+	endpwent();
+}
+
+/* Caching version of getpwnam */
+struct passwd *
+xgetpwnam(const char *name)
+{
+	int i;
+
+	if (!ptable)
+		create_passwd_table();
+
+	for (i = 0; ptable[i].pw_name; i++) {
+		if (!strcmp(name, ptable[i].pw_name))
+			return &(ptable[i]);
+	}
+
+	return NULL;
+}
+
+/* Caching version of getpwuid */
+struct passwd *
+xgetpwuid(uid_t uid)
+{
+	int i;
+
+	if (!ptable)
+		create_passwd_table();
+
+	for (i = 0; ptable[i].pw_name; i++) {
+		if (ptable[i].pw_uid == uid)
+			return &(ptable[i]);
+	}
+
+	return NULL;
+}
+
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..2dd61c9
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,90 @@
+/*
+ * Main functions of the program.
+ *
+ * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+/* For uint64_t */
+#include <stdint.h>
+/* For ssize_t */
+#include <unistd.h>
+/* For FILE */
+#include <stdio.h>
+/* For struct passwd */
+#include <pwd.h>
+/* For struct group */
+#include <grp.h>
+
+/* Adjusts the verbosity level for msg() */
+void adjust_verbosity(int adj);
+
+/* Verbosity levels using stdout */
+#define MSG_NORMAL    0
+#define MSG_DEBUG     1
+#define MSG_QUIET    -1
+/* Verbosity levels using stderr */
+#define MSG_ERROR    -2
+#define MSG_CRITICAL -3
+
+/* Prints messages to console according to the current verbosity */
+int msg(int level, const char *fmt, ...);
+
+/* Malloc which either succeeds or exits */
+void *xmalloc(size_t size);
+
+/* Ditto for strdup */
+char *xstrdup(const char *s);
+
+/* Human-readable printout of binary data */
+void binary_print(const char *s, ssize_t len);
+
+/* Writes data to a file or exits on failure */
+void xfwrite(const void *ptr, size_t size, FILE *stream);
+
+/* Writes an int to a file, using len bytes, in bigendian order */
+void write_int(uint64_t value, size_t len, FILE *to);
+
+/* Writes a binary string to a file */
+void write_binary_string(const char *string, size_t len, FILE *to);
+
+/* Writes a normal C string to a file */
+void write_string(const char *string, FILE *to);
+
+/* Reads an int from a file, using len bytes, in bigendian order */
+uint64_t read_int(char **from, size_t len, const char *max);
+
+/* Reads a binary string from a file */
+char *read_binary_string(char **from, size_t len, const char *max);
+
+/* Reads a normal C string from a file */
+char *read_string(char **from, const char *max);
+
+/* Caching version of getgrnam */
+struct group *xgetgrnam(const char *name);
+
+/* Caching version of getgrgid */
+struct group *xgetgrgid(gid_t gid);
+
+/* Caching version of getpwnam */
+struct passwd *xgetpwnam(const char *name);
+
+/* Caching version of getpwuid */
+struct passwd *xgetpwuid(uid_t uid);
+
+#endif /* UTILS_H */
diff --git a/utils.c b/utils.c
deleted file mode 100644
index 7c85b38..0000000
--- a/utils.c
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Main functions of the program.
- *
- * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-#define _BSD_SOURCE
-#include <stdlib.h>
-#include <stdio.h>
-#include <ctype.h>
-#include <string.h>
-#include <stdint.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <grp.h>
-#include <pwd.h>
-
-#include "utils.h"
-
-/* Controls the verbosity level for msg() */
-static int verbosity = 0;
-
-/* Adjusts the verbosity level for msg() */
-void
-adjust_verbosity(int adj)
-{
-	verbosity += adj;
-}
-
-/*
- * Prints messages to console according to the current verbosity
- * - see utils.h for level defines
- */
-int
-msg(int level, const char *fmt, ...)
-{
-	int ret;
-	va_list ap;
-
-	if (level > verbosity)
-		return 0;
-
-	va_start(ap, fmt);
-
-	if (level < MSG_QUIET)
-		ret = vfprintf(stderr, fmt, ap);
-	else
-		ret = vfprintf(stdout, fmt, ap);
-
-	va_end(ap);
-	return ret;
-}
-
-/* Malloc which either succeeds or exits */
-void *
-xmalloc(size_t size)
-{
-        void *result = malloc(size);
-        if (!result) {
-                msg(MSG_CRITICAL, "Failed to malloc %zi bytes\n", size);
-                exit(EXIT_FAILURE);
-        }
-        return result;
-}
-
-/* Ditto for strdup */
-char *
-xstrdup(const char *s)
-{
-	char *result = strdup(s);
-	if (!result) {
-		msg(MSG_CRITICAL, "Failed to strdup %zi bytes\n", strlen(s));
-		exit(EXIT_FAILURE);
-	}
-	return result;
-}
-
-/* Human-readable printout of binary data */
-void
-binary_print(const char *s, ssize_t len)
-{
-	ssize_t i;
-
-	for (i = 0; i < len; i++) {
-		if (isprint(s[i]))
-			msg(MSG_DEBUG, "%c", s[i]);
-		else
-			msg(MSG_DEBUG, "0x%02X", (int)s[i]);
-	}
-}
-
-/* Writes data to a file or exits on failure */
-void
-xfwrite(const void *ptr, size_t size, FILE *stream)
-{
-	if (size && fwrite(ptr, size, 1, stream) != 1) {
-		msg(MSG_CRITICAL, "Failed to write to file: %s\n",
-		    strerror(errno));
-		exit(EXIT_FAILURE);
-	}
-}
-
-/* Writes an int to a file, using len bytes, in bigendian order */
-void
-write_int(uint64_t value, size_t len, FILE *to)
-{
-	char buf[len];
-	size_t i;
-
-	for (i = 0; i < len; i++)
-		buf[i] = ((value >> (8 * i)) & 0xff);
-	xfwrite(buf, len, to);
-}
-
-/* Writes a binary string to a file */
-void
-write_binary_string(const char *string, size_t len, FILE *to)
-{
-	xfwrite(string, len, to);
-}
-
-/* Writes a normal C string to a file */
-void
-write_string(const char *string, FILE *to)
-{
-	xfwrite(string, strlen(string) + 1, to);
-}
-
-/* Reads an int from a file, using len bytes, in bigendian order */
-uint64_t
-read_int(char **from, size_t len, const char *max)
-{
-	uint64_t result = 0;
-	size_t i;
-
-	if (*from + len > max) {
-		msg(MSG_CRITICAL,
-		    "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;
-}
-
-/* Reads a binary string from a file */
-char *
-read_binary_string(char **from, size_t len, const char *max)
-{
-	char *result;
-
-	if (*from + len > max) {
-		msg(MSG_CRITICAL,
-		    "Attempt to read beyond end of file, corrupt file?\n");
-		exit(EXIT_FAILURE);
-	}
-
-	result = xmalloc(len);
-	memcpy(result, *from, len);
-	*from += len;
-	return result;
-}
-
-/* Reads a normal C string from a file */
-char *
-read_string(char **from, const char *max)
-{
-	return read_binary_string(from, strlen(*from) + 1, max);
-}
-
-/* For group caching */
-static struct group *gtable = NULL;
-
-/* Initial setup of the gid table */
-static void
-create_group_table()
-{
-	struct group *tmp;
-	int count, index;
-
-	for (count = 0; getgrent(); count++) /* Do nothing */;
-
-	gtable = xmalloc(sizeof(struct group) * (count + 1));
-	memset(gtable, 0, sizeof(struct group) * (count + 1));
-	setgrent();
-
-	for (index = 0; (tmp = getgrent()) && index < count; index++) {
-		gtable[index].gr_gid = tmp->gr_gid;
-		gtable[index].gr_name = xstrdup(tmp->gr_name);
-	}
-
-	endgrent();
-}
-
-/* Caching version of getgrnam */
-struct group *
-xgetgrnam(const char *name)
-{
-	int i;
-
-	if (!gtable)
-		create_group_table();
-
-	for (i = 0; gtable[i].gr_name; i++) {
-		if (!strcmp(name, gtable[i].gr_name))
-			return &(gtable[i]);
-	}
-
-	return NULL;
-}
-
-/* Caching version of getgrgid */
-struct group *
-xgetgrgid(gid_t gid)
-{
-	int i;
-
-	if (!gtable)
-		create_group_table();
-
-	for (i = 0; gtable[i].gr_name; i++) {
-		if (gtable[i].gr_gid == gid)
-			return &(gtable[i]);
-	}
-
-	return NULL;
-}
-
-/* For user caching */
-static struct passwd *ptable = NULL;
-
-/* Initial setup of the passwd table */
-static void
-create_passwd_table()
-{
-	struct passwd *tmp;
-	int count, index;
-
-	for (count = 0; getpwent(); count++) /* Do nothing */;
-
-	ptable = xmalloc(sizeof(struct passwd) * (count + 1));
-	memset(ptable, 0, sizeof(struct passwd) * (count + 1));
-	setpwent();
-
-	for (index = 0; (tmp = getpwent()) && index < count; index++) {
-		ptable[index].pw_uid = tmp->pw_uid;
-		ptable[index].pw_name = xstrdup(tmp->pw_name);
-	}
-
-	endpwent();
-}
-
-/* Caching version of getpwnam */
-struct passwd *
-xgetpwnam(const char *name)
-{
-	int i;
-
-	if (!ptable)
-		create_passwd_table();
-
-	for (i = 0; ptable[i].pw_name; i++) {
-		if (!strcmp(name, ptable[i].pw_name))
-			return &(ptable[i]);
-	}
-
-	return NULL;
-}
-
-/* Caching version of getpwuid */
-struct passwd *
-xgetpwuid(uid_t uid)
-{
-	int i;
-
-	if (!ptable)
-		create_passwd_table();
-
-	for (i = 0; ptable[i].pw_name; i++) {
-		if (ptable[i].pw_uid == uid)
-			return &(ptable[i]);
-	}
-
-	return NULL;
-}
-
diff --git a/utils.h b/utils.h
deleted file mode 100644
index 2dd61c9..0000000
--- a/utils.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Main functions of the program.
- *
- * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-#ifndef UTILS_H
-#define UTILS_H
-
-/* For uint64_t */
-#include <stdint.h>
-/* For ssize_t */
-#include <unistd.h>
-/* For FILE */
-#include <stdio.h>
-/* For struct passwd */
-#include <pwd.h>
-/* For struct group */
-#include <grp.h>
-
-/* Adjusts the verbosity level for msg() */
-void adjust_verbosity(int adj);
-
-/* Verbosity levels using stdout */
-#define MSG_NORMAL    0
-#define MSG_DEBUG     1
-#define MSG_QUIET    -1
-/* Verbosity levels using stderr */
-#define MSG_ERROR    -2
-#define MSG_CRITICAL -3
-
-/* Prints messages to console according to the current verbosity */
-int msg(int level, const char *fmt, ...);
-
-/* Malloc which either succeeds or exits */
-void *xmalloc(size_t size);
-
-/* Ditto for strdup */
-char *xstrdup(const char *s);
-
-/* Human-readable printout of binary data */
-void binary_print(const char *s, ssize_t len);
-
-/* Writes data to a file or exits on failure */
-void xfwrite(const void *ptr, size_t size, FILE *stream);
-
-/* Writes an int to a file, using len bytes, in bigendian order */
-void write_int(uint64_t value, size_t len, FILE *to);
-
-/* Writes a binary string to a file */
-void write_binary_string(const char *string, size_t len, FILE *to);
-
-/* Writes a normal C string to a file */
-void write_string(const char *string, FILE *to);
-
-/* Reads an int from a file, using len bytes, in bigendian order */
-uint64_t read_int(char **from, size_t len, const char *max);
-
-/* Reads a binary string from a file */
-char *read_binary_string(char **from, size_t len, const char *max);
-
-/* Reads a normal C string from a file */
-char *read_string(char **from, const char *max);
-
-/* Caching version of getgrnam */
-struct group *xgetgrnam(const char *name);
-
-/* Caching version of getgrgid */
-struct group *xgetgrgid(gid_t gid);
-
-/* Caching version of getpwnam */
-struct passwd *xgetpwnam(const char *name);
-
-/* Caching version of getpwuid */
-struct passwd *xgetpwuid(uid_t uid);
-
-#endif /* UTILS_H */
-- 
cgit v1.2.1