summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPrzemyslaw Pawelczyk <przemoc@gmail.com>2015-09-13 22:12:14 +0200
committerPrzemyslaw Pawelczyk <przemoc@gmail.com>2015-09-13 22:12:14 +0200
commit16ab153f3a54194e3217fcf1a235904e4b61623b (patch)
treec27f9326815ebf4454e753028dcb3d388b3c0990 /src
parent295428a550bb55a509ce15c5e27a0ab173799ebb (diff)
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.
Diffstat (limited to 'src')
-rw-r--r--src/metaentry.c677
-rw-r--r--src/metaentry.h97
-rw-r--r--src/metastore.c539
-rw-r--r--src/metastore.h43
-rw-r--r--src/settings.h35
-rw-r--r--src/utils.c303
-rw-r--r--src/utils.h90
7 files changed, 1784 insertions, 0 deletions
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 */