summaryrefslogtreecommitdiff
path: root/tbdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tbdiff')
-rw-r--r--tbdiff/Makefile.am60
-rw-r--r--tbdiff/tbdiff-1.pc.in13
-rw-r--r--tbdiff/tbdiff-apply.c740
-rw-r--r--tbdiff/tbdiff-common.h103
-rw-r--r--tbdiff/tbdiff-create.c797
-rw-r--r--tbdiff/tbdiff-io.c166
-rw-r--r--tbdiff/tbdiff-io.h63
-rw-r--r--tbdiff/tbdiff-private.h27
-rw-r--r--tbdiff/tbdiff-stat.c265
-rw-r--r--tbdiff/tbdiff-stat.h62
-rw-r--r--tbdiff/tbdiff-xattrs.c227
-rw-r--r--tbdiff/tbdiff-xattrs.h64
-rw-r--r--tbdiff/tbdiff.h31
13 files changed, 2618 insertions, 0 deletions
diff --git a/tbdiff/Makefile.am b/tbdiff/Makefile.am
new file mode 100644
index 0000000..1b2644f
--- /dev/null
+++ b/tbdiff/Makefile.am
@@ -0,0 +1,60 @@
+# vi:set ts=8 sw=8 noet ai nocindent:
+# -
+# Copyright (c) 2011-2012 Codethink Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License Version 2 as
+# published by the Free Software Foundation.
+#
+# 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.
+
+lib_LTLIBRARIES = \
+ libtbdiff-1.la
+
+libtbdiff_headers = \
+ tbdiff-common.h \
+ tbdiff-io.h \
+ tbdiff-stat.h \
+ tbdiff-xattrs.h \
+ tbdiff-private.h \
+ tbdiff.h
+
+libtbdiff_sources = \
+ tbdiff-apply.c \
+ tbdiff-create.c \
+ tbdiff-io.c \
+ tbdiff-stat.c \
+ tbdiff-xattrs.c
+
+libtbdiffincludedir = $(includedir)/tbdiff-$(TBDIFF_VERSION_API)/tbdiff
+
+libtbdiffinclude_HEADERS = \
+ $(libtbdiff_headers)
+
+libtbdiff_1_la_SOURCES = \
+ $(libtbdiff_sources) \
+ $(libtbdiff_headers)
+
+libtbdiff_1_la_CFLAGS = \
+ -DDATADIR=\"$(datadir)\" \
+ -DTBDIFF_COMPILATION \
+ -DTBDIFF_VERSION_API=\"$(TBDIFF_VERSION_API)\" \
+ -I$(top_srcdir) \
+ $(PLATFORM_CFLAGS) \
+ $(PLATFORM_CPPFLAGS)
+
+libtbdiff_1_la_LDFLAGS = \
+ -no-undefined \
+ -export-dynamic \
+ -version-info $(TBDIFF_VERINFO) \
+ $(PLATFORM_LDFLAGS)
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = tbdiff-1.pc
diff --git a/tbdiff/tbdiff-1.pc.in b/tbdiff/tbdiff-1.pc.in
new file mode 100644
index 0000000..45d5460
--- /dev/null
+++ b/tbdiff/tbdiff-1.pc.in
@@ -0,0 +1,13 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+tbdiff_api_version=@TBDIFF_VERSION_API@
+
+Name: @PACKAGE_TARNAME@
+Description: Supporting library for tbdiff-create and tbdiff-deploy
+Requires:
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -ltbdiff-${tbdiff_api_version}
+Cflags: -I${includedir}/tbdiff-${tbdiff_api_version}
diff --git a/tbdiff/tbdiff-apply.c b/tbdiff/tbdiff-apply.c
new file mode 100644
index 0000000..e281d73
--- /dev/null
+++ b/tbdiff/tbdiff-apply.c
@@ -0,0 +1,740 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <time.h>
+
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <attr/xattr.h>
+
+#include <tbdiff/tbdiff-common.h>
+#include <tbdiff/tbdiff-io.h>
+#include <tbdiff/tbdiff-private.h>
+#include <tbdiff/tbdiff-xattrs.h>
+
+char*
+tbd_apply_fread_string(FILE *stream)
+{
+ uint16_t dlen;
+ if(tbd_read_uint16_t(&dlen, stream) != 1)
+ return NULL;
+ char dname[dlen + 1];
+ if(fread(dname, 1, dlen, stream) != dlen)
+ return NULL;
+ dname[dlen] = '\0';
+
+ return strdup(dname);
+}
+
+/* reads a block of data into memory
+ * using the address in *data which is assumed to be able to contain *size
+ * if it needs more than *size bytes to store the data, *data is reallocated
+ * providing initial values of *data = NULL and *size = 0 will force it to
+ * allocate the required memory itself
+ * do not supply a statically or dynamically allocated buffer unless:
+ * - you can guarantee it is not smaller than the data
+ * - or realloc doesn't free old memory (though this will be a memory leak)
+ * - or your allocator does nothing when asked to free non-allocated memory
+ */
+int tbd_apply_fread_block(FILE *stream, void **data, size_t *size)
+{
+ {
+ size_t _size;
+ if (fread(&_size, 1, sizeof(_size), stream) != sizeof(_size) ) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ }
+ if (_size > *size) {
+ void *allocres = realloc(*data, _size);
+ if (allocres == NULL) {
+ return TBD_ERROR(TBD_ERROR_OUT_OF_MEMORY);
+ }
+ *data = allocres;
+ *size = _size;
+ }
+ }
+
+ if (fread(*data, 1, *size, stream) != *size) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ }
+ return TBD_ERROR_SUCCESS;
+}
+
+static int
+tbd_apply_identify(FILE *stream)
+{
+ uint8_t cmd;
+ if(fread(&cmd, 1, 1, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ if(cmd != TBD_CMD_IDENTIFY)
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+ uint16_t nlen;
+ if(tbd_read_uint16_t(&nlen, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ if(strlen(TB_DIFF_PROTOCOL_ID) != nlen)
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+ char nstr[nlen];
+ if(fread(nstr, 1, nlen, stream) != nlen)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ if(strncmp(nstr, TB_DIFF_PROTOCOL_ID, nlen) != 0)
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+ return 0;
+}
+
+static int
+tbd_apply_cmd_dir_create(FILE *stream)
+{
+ uint16_t dlen;
+ if(tbd_read_uint16_t(&dlen, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ char dname[dlen + 1];
+ if(fread(dname, 1, dlen, stream) != dlen)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ dname[dlen] = '\0';
+ fprintf(stderr, "cmd_dir_create %s\n", dname);
+ if(strchr(dname, '/') != NULL)
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+
+ time_t mtime;
+ if(tbd_read_time_t(&mtime, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ uid_t uid;
+ if(tbd_read_uid_t(&uid, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ gid_t gid;
+ if(tbd_read_gid_t(&gid, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ mode_t mode;
+ if(tbd_read_mode_t(&mode, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ if(mkdir(dname, (mode_t)mode) != 0)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CREATE_DIR);
+
+ // Apply metadata.
+ struct utimbuf timebuff = { time(NULL), mtime };
+ utime(dname, &timebuff); // Don't care if it succeeds right now.
+
+ chown(dname, (uid_t)uid, (gid_t)gid);
+ chmod (dname, mode);
+
+ return 0;
+}
+
+static int
+tbd_apply_cmd_dir_enter(FILE *stream,
+ uintptr_t *depth)
+{
+ uint16_t dlen;
+ if(tbd_read_uint16_t(&dlen, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ char dname[dlen + 1];
+ if(fread(dname, 1, dlen, stream) != dlen)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ dname[dlen] = '\0';
+ fprintf(stderr, "cmd_dir_enter %s\n", dname);
+ if((strchr(dname, '/') != NULL) || (strcmp(dname, "..") == 0))
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CHANGE_DIR);
+ if(depth != NULL)
+ (*depth)++;
+
+ if(chdir(dname) != 0)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CHANGE_DIR);
+ return 0;
+}
+
+static int
+tbd_apply_cmd_dir_leave(FILE *stream,
+ uintptr_t *depth)
+{
+ int err = TBD_ERROR_SUCCESS;
+ struct utimbuf time;
+
+ if (tbd_read_time_t(&(time.modtime), stream) != 1) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ }
+ time.actime = time.modtime;/* not sure what the best atime to use is */
+
+ fprintf(stderr, "cmd_dir_leave\n");
+
+ /* test for leaving shallowest depth */
+ if ((depth != NULL) && (*depth < 1)) {
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+ }
+
+ if (utime(".", &time) == -1) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CHANGE_DIR);
+ }
+
+ if (chdir("..") != 0) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CHANGE_DIR);
+ }
+
+ if (depth != NULL) {
+ (*depth)--;
+ }
+
+ return err;
+}
+
+static int
+tbd_apply_cmd_file_create(FILE *stream)
+{
+ uint16_t flen;
+ if(tbd_read_uint16_t(&flen, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ char fname[flen + 1];
+ if(fread(fname, 1, flen, stream) != flen)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ fname[flen] = '\0';
+ if((strchr(fname, '/') != NULL) || (strcmp(fname, "..") == 0))
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+
+ time_t mtime;
+ uint32_t mode;
+ uid_t uid;
+ gid_t gid;
+ uint32_t fsize;
+
+ if(tbd_read_time_t(&mtime, stream) != 1 ||
+ tbd_read_uint32_t(&mode, stream) != 1 ||
+ tbd_read_uid_t(&uid, stream) != 1 ||
+ tbd_read_gid_t(&gid, stream) != 1 ||
+ tbd_read_uint32_t(&fsize, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ fprintf(stderr, "cmd_file_create %s:%"PRId32"\n", fname, fsize);
+
+ FILE *fp = fopen(fname, "rb");
+ if(fp != NULL) {
+ fclose(fp);
+ return TBD_ERROR(TBD_ERROR_FILE_ALREADY_EXISTS);
+ }
+
+ fp = fopen(fname, "wb");
+ if(fp == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_WRITING);
+
+ uintptr_t block = 256;
+ uint8_t fbuff[block];
+ for(; fsize != 0; fsize -= block) {
+ if(fsize < block)
+ block = fsize;
+ if(fread(fbuff, 1, block, stream) != block)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ if(fwrite(fbuff, 1, block, fp) != block)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+ fclose(fp);
+
+ // Apply metadata.
+ struct utimbuf timebuff = { time(NULL), mtime };
+
+ // Don't care if it succeeds right now.
+ utime(fname, &timebuff);
+ /* Chown ALWAYS have to be done before chmod */
+ chown(fname, (uid_t)uid, (gid_t)gid);
+ chmod(fname, mode);
+
+ return 0;
+}
+
+static int
+tbd_apply_cmd_file_delta(FILE *stream)
+{
+ uint16_t mdata_mask;
+ time_t mtime;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ uint16_t flen;
+ if(tbd_read_uint16_t(&flen, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ char fname[flen + 1];
+ if(fread(fname, 1, flen, stream) != flen)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ fname[flen] = '\0';
+
+ fprintf(stderr, "cmd_file_delta %s\n", fname);
+
+ if((strchr(fname, '/') != NULL) ||
+ (strcmp(fname, "..") == 0))
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+
+ /* Reading metadata */
+ if(tbd_read_uint16_t(&mdata_mask, stream) != 1 ||
+ tbd_read_time_t(&mtime, stream) != 1 ||
+ tbd_read_uid_t(&uid, stream) != 1 ||
+ tbd_read_gid_t(&gid, stream) != 1 ||
+ tbd_read_uint32_t(&mode, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ FILE *op = fopen(fname, "rb");
+ if(op == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_READING);
+ if(remove(fname) != 0) {
+ fclose(op);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_REMOVE_FILE);
+ }
+ FILE *np = fopen(fname, "wb");
+ if(np == NULL) {
+ fclose(op);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_WRITING);
+ }
+
+ uint32_t dstart, dend;
+ if(tbd_read_uint32_t(&dstart, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ if(tbd_read_uint32_t(&dend, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ uintptr_t block;
+ uint8_t fbuff[256];
+ for(block = 256; dstart != 0; dstart -= block) {
+ if(dstart < block)
+ block = dstart;
+ if(fread(fbuff, 1, block, op) != block)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ if(fwrite(fbuff, 1, block, np) != block)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+
+ uint32_t fsize;
+ if(tbd_read_uint32_t(&fsize, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ for(block = 256; fsize != 0; fsize -= block) {
+ if(fsize < block)
+ block = fsize;
+ if(fread(fbuff, 1, block, stream) != block)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ if(fwrite(fbuff, 1, block, np) != block)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+
+ if(fseek(op, dend, SEEK_SET) != 0) {
+ fclose(np);
+ fclose(op);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_SEEK_THROUGH_STREAM);
+ }
+
+ for(block = 256; block != 0;) {
+ block = fread(fbuff, 1, block, op);
+ if(fwrite(fbuff, 1, block, np) != block)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+
+ fclose(np);
+ fclose(op);
+
+ // Apply metadata.
+ /* file was removed so old permissions were lost
+ * all permissions need to be reapplied, all were sent in this protocol
+ * if only changed sent will have to save mdata from file before it is
+ * removed, then change that data based on the mask
+ * it will still all have to be reapplied
+ */
+ {
+ struct utimbuf timebuff;
+ timebuff.modtime = mtime;
+ if (time(&(timebuff.actime)) == (time_t)-1) {
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ if (utime(fname, &timebuff) == -1) {
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ if (chown(fname, (uid_t)uid, (gid_t)gid) == -1) {
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ if (chmod(fname, mode) == -1) {
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ }
+
+ return 0;
+}
+
+static int tbd_apply_cmd_entity_delete_for_name(const char*);
+static int tbd_apply_cmd_dir_delete(const char *name)
+{
+ int err = TBD_ERROR_SUCCESS;
+ DIR *dp;
+ struct dirent *entry;
+ if ((dp = opendir(name)) == NULL) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_REMOVE_FILE);
+ }
+
+ if (chdir(name) != 0) {
+ closedir(dp);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CHANGE_DIR);
+ }
+
+ while ((entry = readdir(dp)) != NULL) {
+ if ((strcmp(entry->d_name, ".") == 0) ||
+ (strcmp(entry->d_name, "..") == 0)) {
+ continue;
+ }
+ if ((err = tbd_apply_cmd_entity_delete_for_name(entry->d_name))
+ != TBD_ERROR_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ if (chdir("..") != 0) {
+ err = TBD_ERROR(TBD_ERROR_UNABLE_TO_CHANGE_DIR);
+ goto cleanup;
+ }
+ if (rmdir(name) != 0) {
+ err = TBD_ERROR(TBD_ERROR_UNABLE_TO_REMOVE_FILE);
+ }
+cleanup:
+ closedir(dp);
+ return err;
+}
+
+static int
+tbd_apply_cmd_entity_delete_for_name(const char *name)
+{
+ struct stat info;
+ if (lstat(name, &info) != 0) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_STAT_FILE);
+ }
+
+ if (S_ISDIR(info.st_mode)) {
+ return tbd_apply_cmd_dir_delete(name);
+ }
+
+ if (unlink(name) != 0) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_REMOVE_FILE);
+ }
+
+ return TBD_ERROR_SUCCESS;
+}
+
+static int
+tbd_apply_cmd_entity_delete(FILE *stream)
+{
+ uint16_t elen;
+ if(tbd_read_uint16_t(&elen, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ char ename[elen + 1];
+ if(fread(ename, 1, elen, stream) != elen)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ ename[elen] = '\0';
+
+ fprintf(stderr, "cmd_entity_delete %s\n", ename);
+
+ if((strchr(ename, '/') != NULL) || (strcmp(ename, "..") == 0))
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+ return tbd_apply_cmd_entity_delete_for_name(ename);
+}
+
+static int
+tbd_apply_cmd_symlink_create(FILE *stream)
+{
+ uint16_t len;
+ time_t mtime;
+ uid_t uid;
+ gid_t gid;
+
+ if(tbd_read_time_t(&mtime, stream) != 1 ||
+ tbd_read_uid_t(&uid, stream) != 1 ||
+ tbd_read_gid_t(&gid, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ /* Reading link file name */
+ if(tbd_read_uint16_t(&len, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ char linkname[len + 1];
+ linkname[len] = '\0';
+ if(fread(linkname, sizeof(char), len, stream) != len)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ /* Reading target path */
+ if(tbd_read_uint16_t(&len, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ char linkpath[len+1];
+ linkpath[len] = '\0';
+
+ if(fread(linkpath, sizeof(char), len, stream) != len)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ fprintf(stderr, "cmd_symlink_create %s -> %s\n", linkname, linkpath);
+
+ if(symlink(linkpath, linkname))
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CREATE_SYMLINK);
+
+ struct timeval tv[2];
+ gettimeofday(&tv[0], NULL);
+ tv[1].tv_sec = (long) mtime;
+ tv[1].tv_usec = 0;
+
+ lutimes(linkname, tv); // Don't care if it succeeds right now.
+ lchown(linkname, (uid_t)uid, (uid_t)gid);
+
+ return TBD_ERROR_SUCCESS;
+}
+
+static int
+tbd_apply_cmd_special_create(FILE *stream)
+{
+ char *name = tbd_apply_fread_string(stream);
+ time_t mtime;
+ mode_t mode;
+ uid_t uid;
+ gid_t gid;
+ uint32_t dev;
+
+ if(name == NULL ||
+ tbd_read_time_t(&mtime, stream) != 1 ||
+ tbd_read_mode_t(&mode, stream) != 1 ||
+ tbd_read_uid_t(&uid, stream) != 1 ||
+ tbd_read_gid_t(&gid, stream) != 1 ||
+ tbd_read_uint32_t(&dev, stream) != 1) {
+ free(name);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ }
+
+ fprintf(stderr, "cmd_special_create %s\n", name);
+
+ if(mknod(name, mode, (dev_t)dev) != 0) {
+ free(name);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CREATE_SPECIAL_FILE);
+ }
+
+ struct utimbuf timebuff = { time(NULL), mtime };
+ utime(name, &timebuff); // Don't care if it succeeds right now.
+
+ chown(name, (uid_t)uid, (gid_t)gid);
+ chmod(name, mode);
+
+ free(name);
+ return TBD_ERROR_SUCCESS;
+}
+
+static int
+tbd_apply_cmd_dir_delta(FILE *stream)
+{
+ uint16_t metadata_mask;
+ time_t mtime;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+
+ if(tbd_read_uint16_t(&metadata_mask, stream) != 1 ||
+ tbd_read_time_t(&mtime, stream) != 1 ||
+ tbd_read_uid_t(&uid, stream) != 1 ||
+ tbd_read_gid_t(&gid, stream) != 1 ||
+ tbd_read_uint32_t(&mode, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ char *dname = tbd_apply_fread_string(stream);
+ if(dname == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ fprintf(stderr, "cmd_dir_delta %s\n", dname);
+
+ if(metadata_mask & TBD_METADATA_MTIME) {
+ struct utimbuf timebuff = { time(NULL), mtime };
+ utime(dname, &timebuff); // Don't care if it succeeds right now.
+ }
+ if(metadata_mask & TBD_METADATA_UID || metadata_mask & TBD_METADATA_GID)
+ chown(dname, (uid_t)uid, (gid_t)gid);
+ if(metadata_mask | TBD_METADATA_MODE)
+ chmod(dname, mode);
+
+ free(dname);
+ return TBD_ERROR_SUCCESS;
+}
+
+static int
+tbd_apply_cmd_file_mdata_update(FILE *stream)
+{
+ uint16_t metadata_mask;
+ time_t mtime;
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+
+ if(tbd_read_uint16_t(&metadata_mask, stream) != 1 ||
+ tbd_read_time_t(&mtime, stream) != 1 ||
+ tbd_read_uid_t(&uid, stream) != 1 ||
+ tbd_read_gid_t(&gid, stream) != 1 ||
+ tbd_read_uint32_t(&mode, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ char *dname = tbd_apply_fread_string(stream);
+ if(dname == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+
+ fprintf(stderr, "cmd_metadata_update %s\n", dname);
+
+ if(metadata_mask & TBD_METADATA_MTIME) {
+ struct utimbuf timebuff = { time(NULL), mtime };
+ utime(dname, &timebuff); // Don't care if it succeeds right now.
+ }
+ if(metadata_mask & TBD_METADATA_UID || metadata_mask & TBD_METADATA_GID)
+ chown(dname, (uid_t)uid, (gid_t)gid);
+ if(metadata_mask | TBD_METADATA_MODE)
+ chmod(dname, mode);
+
+ free(dname);
+ return TBD_ERROR_SUCCESS;
+}
+
+static int tbd_apply_cmd_xattrs_update(FILE *stream)
+{
+ int err = TBD_ERROR_SUCCESS;
+ char *fname;
+ uint32_t count;
+ void *data = NULL;
+ size_t dsize = 0;
+ /* read the name of the file to operate on */
+ if ((fname = tbd_apply_fread_string(stream)) == NULL) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ }
+
+ /* remove all attributes in preparation for adding new ones */
+ if ((err = tbd_xattrs_removeall(fname)) != TBD_ERROR_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* read how many attributes to process */
+ if (tbd_read_uint32_t(&count, stream) != 1) {
+ err = TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ goto cleanup;
+ }
+
+ /* operate on each attribute */
+ while (count > 0) {
+ char *aname = tbd_apply_fread_string(stream);
+ if (aname == NULL) {
+ err=TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ goto cleanup;
+ }
+
+ /* read a block of data, reallocating if needed */
+ if ((err = tbd_apply_fread_block(stream, &data, &dsize))
+ != TBD_ERROR_SUCCESS) {
+ free(aname);
+ goto cleanup;
+ }
+
+ if (lsetxattr(fname, aname, data, dsize, 0) == -1) {
+ free(aname);
+ goto cleanup;
+ }
+
+ count--;
+ free(aname);
+ }
+cleanup:
+ free(data);
+ free(fname);
+ return err;
+}
+
+int
+tbd_apply(FILE *stream)
+{
+ if(stream == NULL)
+ return TBD_ERROR(TBD_ERROR_NULL_POINTER);
+
+ int err;
+ if((err = tbd_apply_identify(stream)) != 0)
+ return err;
+
+ uintptr_t depth = 0;
+ bool flush = false;
+ while(!flush) {
+ uint8_t cmd;
+ if(fread(&cmd, 1, 1, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ switch(cmd) {
+ case TBD_CMD_DIR_CREATE:
+ if((err = tbd_apply_cmd_dir_create(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_DIR_ENTER:
+ if((err = tbd_apply_cmd_dir_enter(stream, &depth)) != 0)
+ return err;
+ break;
+ case TBD_CMD_DIR_LEAVE:
+ if((err = tbd_apply_cmd_dir_leave(stream, &depth)) != 0)
+ return err;
+ break;
+ case TBD_CMD_FILE_CREATE:
+ if((err = tbd_apply_cmd_file_create(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_FILE_DELTA:
+ if((err = tbd_apply_cmd_file_delta(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_SYMLINK_CREATE:
+ if((err = tbd_apply_cmd_symlink_create(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_SPECIAL_CREATE:
+ if((err = tbd_apply_cmd_special_create(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_DIR_DELTA:
+ if((err = tbd_apply_cmd_dir_delta(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_FILE_METADATA_UPDATE:
+ if((err = tbd_apply_cmd_file_mdata_update(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_XATTRS_UPDATE:
+ if ((err = tbd_apply_cmd_xattrs_update(stream)) !=
+ TBD_ERROR_SUCCESS) {
+ return err;
+ }
+ break;
+ case TBD_CMD_ENTITY_MOVE:
+ case TBD_CMD_ENTITY_COPY:
+ return TBD_ERROR(TBD_ERROR_FEATURE_NOT_IMPLEMENTED); // TODO - Implement.
+ case TBD_CMD_ENTITY_DELETE:
+ if((err = tbd_apply_cmd_entity_delete(stream)) != 0)
+ return err;
+ break;
+ case TBD_CMD_UPDATE:
+ flush = true;
+ break;
+ default:
+ fprintf(stderr, "Error: Invalid command 0x%02"PRIx8".\n", cmd);
+ return TBD_ERROR(TBD_ERROR_INVALID_PARAMETER);
+ }
+ }
+
+ return 0;
+}
diff --git a/tbdiff/tbdiff-common.h b/tbdiff/tbdiff-common.h
new file mode 100644
index 0000000..d4ac2c8
--- /dev/null
+++ b/tbdiff/tbdiff-common.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#if !defined (TBDIFF_INSIDE_TBDIFF_H) && !defined (TBDIFF_COMPILATION)
+#error "Only <tbdiff/tbdiff.h> may be included directly. This file might disappear or change contents."
+#endif
+
+#ifndef __TBDIFF_COMMON_H__
+#define __TBDIFF_COMMON_H__
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <tbdiff/tbdiff-stat.h>
+
+typedef enum {
+ TBD_CMD_IDENTIFY = 0x00,
+ TBD_CMD_UPDATE = 0x01,
+ TBD_CMD_DIR_CREATE = 0x10,
+ TBD_CMD_DIR_ENTER = 0x11,
+ TBD_CMD_DIR_LEAVE = 0x12,
+ TBD_CMD_DIR_DELTA = 0x13,
+ TBD_CMD_FILE_CREATE = 0x20,
+ TBD_CMD_FILE_DELTA = 0x21,
+ TBD_CMD_FILE_METADATA_UPDATE = 0x22,
+ TBD_CMD_ENTITY_MOVE = 0x30,
+ TBD_CMD_ENTITY_COPY = 0x31,
+ TBD_CMD_ENTITY_DELETE = 0x32,
+ TBD_CMD_SYMLINK_CREATE = 0x40,
+ TBD_CMD_SPECIAL_CREATE = 0x50,
+ TBD_CMD_XATTRS_UPDATE = 0x60,
+} tbd_cmd_e;
+
+typedef enum {
+ TBD_METADATA_NONE = 0x0,
+ TBD_METADATA_MTIME = 0x1,
+ TBD_METADATA_MODE = 0x2,
+ TBD_METADATA_UID = 0x4,
+ TBD_METADATA_GID = 0x8,
+ TBD_METADATA_RDEV = 0x10,
+} tbd_metadata_type_e;
+
+typedef enum {
+ TBD_ERROR_SUCCESS = 0,
+ TBD_ERROR_FAILURE = -1,
+ TBD_ERROR_OUT_OF_MEMORY = -2,
+ TBD_ERROR_NULL_POINTER = -3,
+ TBD_ERROR_INVALID_PARAMETER = -4,
+ TBD_ERROR_UNABLE_TO_READ_STREAM = -5,
+ TBD_ERROR_UNABLE_TO_WRITE_STREAM = -6,
+ TBD_ERROR_UNABLE_TO_CREATE_DIR = -7,
+ TBD_ERROR_UNABLE_TO_CHANGE_DIR = -8,
+ TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_READING = -9,
+ TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_WRITING = -10,
+ TBD_ERROR_FILE_ALREADY_EXISTS = -11,
+ TBD_ERROR_UNABLE_TO_REMOVE_FILE = -12,
+ TBD_ERROR_UNABLE_TO_SEEK_THROUGH_STREAM = -13,
+ TBD_ERROR_FEATURE_NOT_IMPLEMENTED = -14,
+ TBD_ERROR_FILE_DOES_NOT_EXIST = -15,
+ TBD_ERROR_UNABLE_TO_DETECT_STREAM_POSITION = -16,
+ TBD_ERROR_UNABLE_TO_STAT_FILE = -17,
+ TBD_ERROR_UNABLE_TO_READ_SYMLINK = -18,
+ TBD_ERROR_UNABLE_TO_CREATE_SYMLINK = -19,
+ TBD_ERROR_UNABLE_TO_READ_SPECIAL_FILE = -20,
+ TBD_ERROR_UNABLE_TO_CREATE_SPECIAL_FILE = -21,
+ TBD_ERROR_UNABLE_TO_CREATE_SOCKET_FILE = -22,
+ TBD_ERROR_XATTRS_NOT_SUPPORTED = -23,
+ TBD_ERROR_XATTRS_MISSING_ATTR = -24,
+} tbd_error_e;
+
+#ifdef NDEBUG
+#define TBD_ERROR(e) (e)
+#else
+#define TBD_ERROR(e) tbd_error(e, #e, __func__, __LINE__, __FILE__)
+static inline tbd_error_e
+tbd_error(tbd_error_e e, char const *s, char const *func, int line,
+ char const* file)
+{
+ if (e != TBD_ERROR_SUCCESS)
+ fprintf(stderr, "TBDiff error '%s' in function '%s' at line %d "
+ "of file '%s'.\n", s, func, line, file);
+ return e;
+}
+#endif
+
+extern int tbd_apply (FILE *stream);
+extern int tbd_create(FILE *stream, tbd_stat_t *a, tbd_stat_t *b);
+
+#endif /* !__TBDIFF_COMMON_H__ */
diff --git a/tbdiff/tbdiff-create.c b/tbdiff/tbdiff-create.c
new file mode 100644
index 0000000..ec5a8ce
--- /dev/null
+++ b/tbdiff/tbdiff-create.c
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <tbdiff/tbdiff-common.h>
+#include <tbdiff/tbdiff-io.h>
+#include <tbdiff/tbdiff-private.h>
+#include <tbdiff/tbdiff-xattrs.h>
+
+#define PATH_BUFFER_LENGTH 4096
+
+static int
+tbd_create_fwrite_cmd(FILE *stream,
+ uint8_t cmd)
+{
+ if(fwrite(&cmd, 1, 1, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_fwrite_string(FILE *stream,
+ const char *string)
+{
+ uint16_t slen = strlen(string);
+ if((tbd_write_uint16_t(slen, stream) != 1)
+ || (fwrite(string, 1, slen, stream) != slen))
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_fwrite_block(FILE *stream, void const *data, size_t size)
+{
+ if (fwrite(&size, 1, sizeof(size), stream) != sizeof(size)) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+
+ if (fwrite(data, 1, size, stream) != size) {
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+ return TBD_ERROR_SUCCESS;
+}
+
+static int
+tbd_create_fwrite_mdata_mask(FILE *stream,
+ uint16_t mask)
+{
+ if(tbd_write_uint16_t(mask, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_fwrite_mtime(FILE *stream,
+ time_t mtime)
+{
+ if(tbd_write_time_t(mtime, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_fwrite_mode(FILE *stream,
+ mode_t mode)
+{
+ if(tbd_write_mode_t(mode, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_fwrite_gid(FILE *stream,
+ gid_t gid)
+{
+ if(tbd_write_gid_t(gid, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_fwrite_uid(FILE *stream,
+ uid_t uid)
+{
+ if(tbd_write_uid_t(uid, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_fwrite_dev(FILE *stream,
+ uint32_t dev)
+{
+ if(tbd_write_uint32_t(dev, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ return 0;
+}
+
+static int
+tbd_create_cmd_ident(FILE *stream)
+{
+ int err;
+
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_IDENTIFY)) != 0)
+ return err;
+ if((err = tbd_create_fwrite_string(stream, TB_DIFF_PROTOCOL_ID)) != 0)
+ return err;
+ return 0;
+}
+
+static int
+tbd_create_cmd_update(FILE *stream)
+{
+ return tbd_create_fwrite_cmd(stream, TBD_CMD_UPDATE);
+}
+
+/* callback function to pass to tbx_xattrs_pairs
+ * this will write the attribute name, then the data representing that block
+ */
+static int
+_write_pair(char const *name, void const *data, size_t size, void *ud)
+{
+ FILE *stream = ud;
+ int err;
+
+ if ((err = tbd_create_fwrite_string(stream, name)) !=
+ TBD_ERROR_SUCCESS) {
+ return err;
+ }
+
+ if ((err = tbd_create_fwrite_block(stream, data, size)) !=
+ TBD_ERROR_SUCCESS) {
+ return err;
+ }
+
+ return TBD_ERROR_SUCCESS;
+}
+
+static int
+tbd_create_cmd_fwrite_xattrs(FILE *stream, tbd_stat_t *f)
+{
+ int err = TBD_ERROR_SUCCESS;
+ tbd_xattrs_names_t names;
+ char *path = tbd_stat_path(f);
+ if (path == NULL) {
+ return TBD_ERROR(TBD_ERROR_OUT_OF_MEMORY);
+ }
+
+ switch (err = tbd_xattrs_names(path, &names)) {
+ /* separated as ignore XATTR unspported may be added */
+ case TBD_ERROR_XATTRS_NOT_SUPPORTED:
+ default:
+ goto cleanup_path;
+ case TBD_ERROR_SUCCESS:
+ break;
+ }
+
+ { /* write the header */
+ uint32_t count;
+ /* if failed to count or there are no xattrs */
+ if ((err = tbd_xattrs_names_count(&names, &count)) !=
+ TBD_ERROR_SUCCESS || count == 0) {
+ goto cleanup_names;
+ }
+
+ if ((err = tbd_create_fwrite_cmd(stream,
+ TBD_CMD_XATTRS_UPDATE)
+ ) != TBD_ERROR_SUCCESS) {
+ goto cleanup_names;
+ }
+
+ if ((err = tbd_create_fwrite_string(stream, f->name))!=
+ TBD_ERROR_SUCCESS) {
+ goto cleanup_names;
+ }
+
+ if (tbd_write_uint32_t(count, stream) != 1) {
+ err = TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ goto cleanup_names;
+ }
+ }
+
+ /* write the name:data pairs */
+ err = tbd_xattrs_pairs(&names, path, _write_pair, stream);
+
+cleanup_names:
+ tbd_xattrs_names_free(&names);
+cleanup_path:
+ free(path);
+ return err;
+}
+
+static int
+tbd_create_cmd_file_create(FILE *stream,
+ tbd_stat_t *f)
+{
+ int err;
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_FILE_CREATE)) != 0 ||
+ (err = tbd_create_fwrite_string(stream, f->name)) != 0 ||
+ (err = tbd_create_fwrite_mtime (stream, f->mtime)) != 0 ||
+ (err = tbd_create_fwrite_mode (stream, f->mode)) != 0 ||
+ (err = tbd_create_fwrite_uid (stream, f->uid)) != 0 ||
+ (err = tbd_create_fwrite_gid (stream, f->gid)) != 0)
+ return err;
+
+ uint32_t size = f->size;
+ if(tbd_write_uint32_t(size, stream) != 1)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+
+ FILE *fp = tbd_stat_fopen(f, "rb");
+ if(fp == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_READING);
+
+ uint8_t buff[256];
+ uintptr_t b = 256;
+ for(b = 256; b == 256; ) {
+ b = fread(buff, 1, b, fp);
+ if(fwrite(buff, 1, b, stream) != b) {
+ fclose(fp);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+ }
+ fclose(fp);
+
+ return tbd_create_cmd_fwrite_xattrs(stream, f);
+}
+
+static uint16_t
+tbd_metadata_mask(tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ uint16_t metadata_mask = TBD_METADATA_NONE;
+
+ /* If nothing changes we issue no command */
+ if(a->mtime != b->mtime)
+ metadata_mask |= TBD_METADATA_MTIME;
+ if(a->uid != b->uid)
+ metadata_mask |= TBD_METADATA_UID;
+ if(a->gid != b->gid)
+ metadata_mask |= TBD_METADATA_GID;
+ if(a->mode != b->mode)
+ metadata_mask |= TBD_METADATA_MODE;
+
+ return metadata_mask;
+}
+
+static int
+tbd_create_cmd_file_metadata_update(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ int err;
+ uint16_t metadata_mask = tbd_metadata_mask(a, b);
+
+ if(metadata_mask == TBD_METADATA_NONE)
+ return 0;
+ /* TODO: Optimize protocol by only sending useful metadata */
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_FILE_METADATA_UPDATE)) != 0 ||
+ (err = tbd_create_fwrite_mdata_mask (stream, metadata_mask)) != 0 ||
+ (err = tbd_create_fwrite_mtime (stream, b->mtime)) != 0 ||
+ (err = tbd_create_fwrite_uid (stream, b->uid)) != 0 ||
+ (err = tbd_create_fwrite_gid (stream, b->gid)) != 0 ||
+ (err = tbd_create_fwrite_mode (stream, b->mode)) != 0)
+ return err;
+
+ return tbd_create_fwrite_string(stream, b->name);
+}
+
+static int
+tbd_create_cmd_file_delta(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ FILE *fpa = tbd_stat_fopen(a, "rb");
+ if(fpa == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_READING);
+ FILE *fpb = tbd_stat_fopen(b, "rb");
+ if(fpb == NULL) {
+ fclose(fpa);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_OPEN_FILE_FOR_READING);
+ }
+
+ // Calculate start.
+ uintptr_t blks[2] = { 256, 256 };
+ uint8_t buff[2][256];
+
+ uintptr_t o;
+ for(o = 0; (blks[1] == blks[0]) && (blks[0] != 0); o += blks[1]) {
+ blks[0] = fread(buff[0], 1, blks[0], fpa);
+ blks[1] = fread(buff[1], 1, blks[0], fpb);
+ if((blks[0] == 0) || (blks[1] == 0))
+ break;
+
+ uintptr_t i;
+ for(i = 0; i < blks[1]; i++) {
+ if(buff[0][i] != buff[1][i]) {
+ o += i;
+ break;
+ }
+ }
+ if(i < blks[1])
+ break;
+ }
+ uint32_t start = o;
+
+ if((fseek(fpa, 0, SEEK_END) != 0) || (fseek(fpb, 0, SEEK_END) != 0)) {
+ fclose(fpa);
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_SEEK_THROUGH_STREAM);
+ }
+
+ // Find length.
+ long flena = ftell(fpa);
+ long flenb = ftell(fpb);
+
+ if((flena < 0) || (flenb < 0)) {
+ fclose(fpa);
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_DETECT_STREAM_POSITION);
+ }
+
+ // Find end.
+ blks[0] = 256;
+ blks[1] = 256;
+ for(o = 0; true; o += blks[1]) {
+ blks[0] = ((flena - o) < 256 ? (flena - o) : 256 );
+ blks[1] = ((flenb - o) < blks[0] ? (flenb - o) : blks[0]);
+ if((blks[0] == 0) || (blks[1] == 0))
+ break;
+
+ if((fseek(fpa, flena - (o + blks[0]), SEEK_SET) != 0)
+ || (fseek(fpb, flenb - (o + blks[1]), SEEK_SET) != 0)) {
+ fclose(fpa);
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_SEEK_THROUGH_STREAM);
+ }
+
+ if((fread(buff[0], 1, blks[0], fpa) != blks[0])
+ || (fread(buff[1], 1, blks[1], fpb) != blks[1])) {
+ fclose(fpa);
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ }
+
+ uintptr_t i, ja, jb;
+ for(i = 0, ja = (blks[0] - 1), jb = (blks[1] - 1); i < blks[1]; i++, ja--, jb--) {
+ if(buff[0][ja] != buff[1][jb]) {
+ o += i;
+ break;
+ }
+ }
+ if(i < blks[1])
+ break;
+ }
+ fclose(fpa);
+
+ // Ensure that the start and end don't overlap for the new file.
+ if((flenb - o) < start)
+ o = (flenb - start);
+
+ uint32_t end = (flena - o);
+ if(end < start)
+ end = start;
+
+ uint32_t size = flenb - ((flena - end) + start); //(flenb - (o + start));
+
+ /* Data is identical, only alter metadata */
+ if((end == start) && (size == 0)) {
+ tbd_create_cmd_file_metadata_update(stream, a, b);
+ fclose(fpb);
+ return tbd_create_cmd_fwrite_xattrs(stream, b);
+ }
+
+ uint16_t metadata_mask = tbd_metadata_mask(a, b);
+
+ /* TODO: Optimize protocol by only sending useful metadata */
+ int err;
+ if(((err = tbd_create_fwrite_cmd(stream, TBD_CMD_FILE_DELTA)) != 0) ||
+ ((err = tbd_create_fwrite_string(stream, b->name)) != 0) ||
+ ((err = tbd_create_fwrite_mdata_mask(stream, metadata_mask)) != 0) ||
+ ((err = tbd_create_fwrite_mtime (stream, b->mtime)) != 0) ||
+ ((err = tbd_create_fwrite_uid (stream, b->uid)) != 0) ||
+ ((err = tbd_create_fwrite_gid (stream, b->gid)) != 0) ||
+ ((err = tbd_create_fwrite_mode (stream, b->mode)) != 0)) {
+ fclose(fpb);
+ return err;
+ }
+ if((tbd_write_uint32_t(start, stream) != 1) ||
+ (tbd_write_uint32_t(end, stream) != 1) ||
+ (tbd_write_uint32_t(size, stream) != 1)) {
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+ if(fseek(fpb, start, SEEK_SET) != 0) {
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_SEEK_THROUGH_STREAM);
+ }
+
+ for(o = 0; o < size; o += 256) {
+ uintptr_t csize = ((size - o) > 256 ? 256 : (size - o));
+ if(fread(buff[0], 1, csize, fpb) != csize) {
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_STREAM);
+ }
+ if(fwrite(buff[0], 1, csize, stream) != csize) {
+ fclose(fpb);
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_WRITE_STREAM);
+ }
+ }
+
+ fclose(fpb);
+ return tbd_create_cmd_fwrite_xattrs(stream, b);
+}
+
+static int
+tbd_create_cmd_dir_create(FILE *stream,
+ tbd_stat_t *d)
+{
+ int err;
+
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_DIR_CREATE)) != 0 ||
+ (err = tbd_create_fwrite_string(stream, d->name)) != 0 ||
+ (err = tbd_create_fwrite_mtime(stream, d->mtime)) != 0 ||
+ (err = tbd_create_fwrite_uid(stream, d->uid)) != 0 ||
+ (err = tbd_create_fwrite_gid(stream, d->gid)) != 0)
+ return err;
+
+ return tbd_create_fwrite_mode(stream, d->mode);
+}
+
+static int
+tbd_create_cmd_dir_enter(FILE *stream,
+ const char *name)
+{
+ int err;
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_DIR_ENTER)) != 0)
+ return err;
+ return tbd_create_fwrite_string(stream, name);
+}
+
+static int
+tbd_create_cmd_dir_leave(FILE *stream,
+ tbd_stat_t *dir)
+{
+ int err;
+ if ((err = tbd_create_fwrite_cmd(stream, TBD_CMD_DIR_LEAVE)) !=
+ TBD_ERROR_SUCCESS) {
+ return err;
+ }
+
+ return tbd_create_fwrite_mtime(stream, dir->mtime);
+}
+
+static int
+tbd_create_cmd_entity_delete(FILE *stream,
+ const char *name)
+{
+ int err;
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_ENTITY_DELETE)) != 0)
+ return err;
+ return tbd_create_fwrite_string(stream, name);
+}
+
+static int
+tbd_create_cmd_dir_delta(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ int err;
+ uint16_t metadata_mask = tbd_metadata_mask(a, b);
+
+ if(metadata_mask == TBD_METADATA_NONE)
+ return 0;
+
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_DIR_DELTA)) != 0 ||
+ (err = tbd_create_fwrite_mdata_mask (stream, metadata_mask)) != 0 ||
+ (err = tbd_create_fwrite_mtime (stream, b->mtime)) != 0 ||
+ (err = tbd_create_fwrite_uid (stream, b->uid)) != 0 ||
+ (err = tbd_create_fwrite_gid (stream, b->gid)) != 0 ||
+ (err = tbd_create_fwrite_mode (stream, b->mode)) != 0)
+ return err;
+
+ return tbd_create_fwrite_string(stream, b->name);
+}
+
+static int
+tbd_create_cmd_symlink_create(FILE *stream,
+ tbd_stat_t *symlink)
+{
+ int err;
+ char path[PATH_BUFFER_LENGTH];
+
+ char *slpath = tbd_stat_path(symlink);
+ ssize_t len = readlink(slpath, path, sizeof(path)-1);
+ free(slpath);
+ if(len < 0)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_SYMLINK);
+ path[len] = '\0';
+
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_SYMLINK_CREATE)) != 0 ||
+ (err = tbd_create_fwrite_mtime (stream, symlink->mtime)) != 0 ||
+ (err = tbd_create_fwrite_uid (stream, symlink->uid)) != 0 ||
+ (err = tbd_create_fwrite_gid (stream, symlink->gid)) != 0 ||
+ (err = tbd_create_fwrite_string(stream, symlink->name)) != 0)
+ return err;
+
+ return tbd_create_fwrite_string(stream, path);
+}
+
+static int
+tbd_create_cmd_symlink_delta(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ int err;
+ char path_a[PATH_BUFFER_LENGTH];
+ char path_b[PATH_BUFFER_LENGTH];
+
+ char *spath_a = tbd_stat_path(a);
+ char *spath_b = tbd_stat_path(b);
+
+ ssize_t len_a = readlink(spath_a, path_a, sizeof(path_a)-1);
+ ssize_t len_b = readlink(spath_b, path_b, sizeof(path_b)-1);
+
+ free(spath_a);
+ free(spath_b);
+
+ if(len_a < 0 || len_b < 0)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_READ_SYMLINK);
+
+ path_a[len_a] = path_b[len_b] = '\0';
+
+ int pathcmp = strcmp(path_a, path_b);
+ printf ("readlink %s %s - %d\n", path_a, path_b, pathcmp);
+
+ /* If both symlinks are equal, we quit */
+ if ((b->mtime == a->mtime) && (b->gid == a->gid) && (pathcmp == 0))
+ return 0;
+
+ /* TODO: If only mtime changes, use a mtime update cmd */
+ err = tbd_create_cmd_entity_delete(stream, a->name);
+ if(err != 0)
+ return err;
+
+ return tbd_create_cmd_symlink_create(stream, b);
+}
+
+static int
+tbd_create_cmd_special_create(FILE *stream,
+ tbd_stat_t *nod)
+{
+ int err;
+
+ if((err = tbd_create_fwrite_cmd(stream, TBD_CMD_SPECIAL_CREATE)) != 0 ||
+ (err = tbd_create_fwrite_string(stream, nod->name)) != 0 ||
+ (err = tbd_create_fwrite_mtime (stream, nod->mtime)) != 0 ||
+ (err = tbd_create_fwrite_mode (stream, nod->mode)) != 0 ||
+ (err = tbd_create_fwrite_uid (stream, nod->uid)) != 0 ||
+ (err = tbd_create_fwrite_gid (stream, nod->gid)) != 0)
+ return err;
+ return tbd_create_fwrite_dev(stream, nod->rdev);
+}
+
+static int
+tbd_create_cmd_special_delta(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ uint16_t metadata_mask = tbd_metadata_mask(a, b);
+ if(a->rdev != b->rdev)
+ metadata_mask |= TBD_METADATA_RDEV;
+
+ /* If nothing changes we issue no command */
+ if(metadata_mask == TBD_METADATA_NONE)
+ return 0;
+
+ int err;
+ if((err = tbd_create_cmd_entity_delete(stream, a->name)) != 0)
+ return err;
+
+ return tbd_create_cmd_special_create(stream, b);
+}
+
+static int
+tbd_create_cmd_socket_create(FILE *stream,
+ tbd_stat_t *nod)
+{
+ (void)stream;
+ (void)nod;
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CREATE_SOCKET_FILE);
+}
+
+static int
+tbd_create_cmd_socket_delta(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ (void)stream;
+ (void)a;
+ (void)b;
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_CREATE_SOCKET_FILE);
+}
+
+static int
+tbd_create_dir(FILE *stream,
+ tbd_stat_t *d)
+{
+ int err;
+ if(((err =tbd_create_cmd_dir_create(stream, d)) != 0) ||
+ ((err = tbd_create_cmd_dir_enter(stream, d->name)) != 0))
+ return err;
+
+ uintptr_t i;
+ for(i = 0; i < d->size; i++) {
+ tbd_stat_t *f = tbd_stat_entry(d, i);
+
+ if(f == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_STAT_FILE);
+
+ switch(f->type) {
+ case TBD_STAT_TYPE_FILE:
+ err = tbd_create_cmd_file_create(stream, f);
+ break;
+ case TBD_STAT_TYPE_DIR:
+ err = tbd_create_dir(stream, f);
+ break;
+ case TBD_STAT_TYPE_SYMLINK:
+ err = tbd_create_cmd_symlink_create(stream, f);
+ break;
+ case TBD_STAT_TYPE_BLKDEV:
+ case TBD_STAT_TYPE_CHRDEV:
+ case TBD_STAT_TYPE_FIFO:
+ case TBD_STAT_TYPE_SOCKET:
+ err = tbd_create_cmd_special_create(stream, f);
+ break;
+ default:
+ tbd_stat_free(f);
+ return TBD_ERROR(TBD_ERROR_FEATURE_NOT_IMPLEMENTED);
+ break;
+ }
+ tbd_stat_free(f);
+ if(err != 0)
+ return err;
+ }
+
+ return tbd_create_cmd_dir_leave(stream, d);
+}
+
+static int
+tbd_create_impl(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b,
+ bool top)
+{
+ if((a == NULL) && (b == NULL))
+ return TBD_ERROR(TBD_ERROR_NULL_POINTER);
+
+ int err;
+ if(((b == NULL) || ((a != NULL) && (a->type != b->type)))) {
+ fprintf(stderr, "file delete %s\n", a->name);
+ if((err = tbd_create_cmd_entity_delete(stream, a->name)) != 0)
+ return err;
+ }
+
+ if((a == NULL) || ((b != NULL) && (a->type != b->type))) {
+ switch(b->type) {
+ case TBD_STAT_TYPE_FILE:
+ fprintf(stderr, "file new %s\n", b->name);
+ return tbd_create_cmd_file_create(stream, b);
+ case TBD_STAT_TYPE_DIR:
+ fprintf(stderr, "dir new %s\n", b->name);
+ return tbd_create_dir(stream, b);
+ case TBD_STAT_TYPE_SYMLINK:
+ fprintf(stderr, "symlink new %s\n", b->name);
+ return tbd_create_cmd_symlink_create(stream, b);
+ case TBD_STAT_TYPE_CHRDEV:
+ case TBD_STAT_TYPE_BLKDEV:
+ case TBD_STAT_TYPE_FIFO:
+ fprintf(stderr, "special new %s\n", b->name);
+ return tbd_create_cmd_special_create(stream, b);
+ case TBD_STAT_TYPE_SOCKET:
+ fprintf(stderr, "socket new %s\n", b->name);
+ return tbd_create_cmd_socket_create(stream, b);
+ default:
+ return TBD_ERROR(TBD_ERROR_FEATURE_NOT_IMPLEMENTED);
+ }
+ }
+
+ switch(b->type) {
+ case TBD_STAT_TYPE_FILE:
+ fprintf(stderr, "file delta %s\n", a->name);
+ return tbd_create_cmd_file_delta(stream, a, b);
+ case TBD_STAT_TYPE_SYMLINK:
+ fprintf(stderr, "symlink delta %s\n", a->name);
+ return tbd_create_cmd_symlink_delta(stream, a, b);
+ case TBD_STAT_TYPE_CHRDEV:
+ case TBD_STAT_TYPE_BLKDEV:
+ case TBD_STAT_TYPE_FIFO:
+ fprintf(stderr, "special delta %s\n", a->name);
+ return tbd_create_cmd_special_delta(stream, a, b);
+ case TBD_STAT_TYPE_SOCKET:
+ fprintf(stderr, "socket delta %s\n", a->name);
+ return tbd_create_cmd_socket_delta(stream, a, b);
+ case TBD_STAT_TYPE_DIR:
+ if(!top) {
+ fprintf(stderr, "dir delta %s\n", a->name);
+ if ((err = tbd_create_cmd_dir_delta(stream, a, b)) !=
+ TBD_ERROR_SUCCESS) {
+ return err;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if(!top && ((err = tbd_create_cmd_dir_enter(stream, b->name)) != 0))
+ return err;
+
+ // Handle changes/additions.
+ uintptr_t i;
+ for(i = 0; i < b->size; i++) {
+ tbd_stat_t *_b = tbd_stat_entry(b, i);
+ if(_b == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_STAT_FILE);
+ tbd_stat_t *_a = tbd_stat_entry_find(a, _b->name);
+ err = tbd_create_impl(stream, _a, _b, false);
+ tbd_stat_free(_a);
+ tbd_stat_free(_b);
+ if(err != 0)
+ return err;
+ }
+
+ // Handle deletions.
+ for(i = 0; i < a->size; i++) {
+ err = 0;
+
+ tbd_stat_t *_a = tbd_stat_entry(a, i);
+ if(_a == NULL)
+ return TBD_ERROR(TBD_ERROR_UNABLE_TO_STAT_FILE);
+ tbd_stat_t *_b = tbd_stat_entry_find(b, _a->name);
+
+ if (_b == NULL)
+ err = tbd_create_cmd_entity_delete(stream, _a->name);
+
+ tbd_stat_free(_b);
+ tbd_stat_free(_a);
+
+ if(err != 0)
+ return err;
+ }
+
+ if(!top && ((err = tbd_create_cmd_dir_leave(stream, b)) !=
+ TBD_ERROR_SUCCESS)) {
+ return err;
+ }
+ return TBD_ERROR_SUCCESS;
+}
+
+int
+tbd_create(FILE *stream,
+ tbd_stat_t *a,
+ tbd_stat_t *b)
+{
+ int err;
+ if((stream == NULL) || (a == NULL) || (b == NULL))
+ return TBD_ERROR(TBD_ERROR_NULL_POINTER);
+
+ if((err = tbd_create_cmd_ident(stream)) != 0 ||
+ (err = tbd_create_impl(stream, a, b, true)) != 0)
+ return err;
+
+ return tbd_create_cmd_update(stream);
+}
diff --git a/tbdiff/tbdiff-io.c b/tbdiff/tbdiff-io.c
new file mode 100644
index 0000000..698273b
--- /dev/null
+++ b/tbdiff/tbdiff-io.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <endian.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <tbdiff/tbdiff-stat.h>
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+//inverts the indices of an array of bytes.
+static void byteswap (char* value, int size) {
+ char tmp;
+ int i;
+ for (i = 0; i < size/2; i++) {
+ tmp = value[i];
+ value[i] = value[size-i-1];
+ value[size-i-1] = tmp;
+ }
+}
+#endif
+
+size_t tbd_write_uint16_t (uint16_t value, FILE* stream) {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&value, sizeof(value));
+#endif
+ return fwrite(&value, sizeof(value), 1, stream);
+}
+
+size_t tbd_write_uint32_t (uint32_t value, FILE* stream) {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&value, sizeof(value));
+#endif
+ return fwrite(&value, sizeof(value), 1, stream);
+}
+
+size_t tbd_write_uint64_t (uint64_t value, FILE* stream) {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&value, sizeof(value));
+#endif
+ return fwrite(&value, sizeof(value), 1, stream);
+}
+
+size_t tbd_write_time_t (time_t value, FILE* stream) {
+ uint64_t realv = value;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&realv, sizeof(realv));
+#endif
+ return fwrite(&realv, sizeof(realv), 1, stream);
+}
+
+size_t tbd_write_mode_t (mode_t value, FILE* stream) {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&value, sizeof(value));
+#endif
+ return fwrite(&value, sizeof(value), 1, stream);
+}
+
+size_t tbd_write_uid_t (uid_t value, FILE* stream) {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&value, sizeof(value));
+#endif
+ return fwrite(&value, sizeof(value), 1, stream);
+}
+
+size_t tbd_write_gid_t (gid_t value, FILE* stream) {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&value, sizeof(value));
+#endif
+ return fwrite(&value, sizeof(value), 1, stream);
+}
+
+size_t tbd_write_size_t (size_t value, FILE* stream) {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&value, sizeof(value));
+#endif
+ return fwrite(&value, sizeof(value), 1, stream);
+}
+
+size_t tbd_read_uint16_t (uint16_t *value, FILE* stream) {
+ assert(value != NULL);
+ size_t rval = fread(value, sizeof(*value), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)value, sizeof(*value));
+#endif
+ return rval;
+}
+
+size_t tbd_read_uint32_t (uint32_t *value, FILE* stream) {
+ assert(value != NULL);
+ size_t rval = fread(value, sizeof(*value), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)value, sizeof(*value));
+#endif
+ return rval;
+}
+
+size_t tbd_read_uint64_t (uint64_t *value, FILE* stream) {
+ assert(value != NULL);
+ size_t rval = fread(value, sizeof(*value), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)value, sizeof(*value));
+#endif
+ return rval;
+}
+
+size_t tbd_read_time_t (time_t *value, FILE* stream) {
+ assert(value != NULL);
+ uint64_t realv;
+ size_t rval = fread(&realv, sizeof(realv), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)&realv, sizeof(realv));
+#endif
+ *value = realv;
+ return rval;
+ }
+
+size_t tbd_read_mode_t (mode_t *value, FILE* stream) {
+ assert(value != NULL);
+ size_t rval = fread(value, sizeof(*value), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)value, sizeof(*value));
+#endif
+ return rval;
+ }
+
+size_t tbd_read_uid_t (uid_t *value, FILE* stream) {
+ assert(value != NULL);
+ size_t rval = fread(value, sizeof(*value), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)value, sizeof(*value));
+#endif
+ return rval;
+ }
+
+size_t tbd_read_gid_t (gid_t *value, FILE* stream) {
+ assert(value != NULL);
+ size_t rval = fread(value, sizeof(*value), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)value, sizeof(*value));
+#endif
+ return rval;
+ }
+
+size_t tbd_read_size_t (size_t *value, FILE* stream) {
+ assert(value != NULL);
+ size_t rval = fread(value, sizeof(*value), 1, stream);
+#if __BYTE_ORDER == __BIG_ENDIAN
+ byteswap((char*)value, sizeof(*value));
+#endif
+ return rval;
+ }
diff --git a/tbdiff/tbdiff-io.h b/tbdiff/tbdiff-io.h
new file mode 100644
index 0000000..73e7711
--- /dev/null
+++ b/tbdiff/tbdiff-io.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#if !defined (TBDIFF_INSIDE_TBDIFF_H) && !defined (TBDIFF_COMPILATION)
+#error "Only <tbdiff/tbdiff.h> may be included directly. This file might disappear or change contents."
+#endif
+
+#ifndef __TBDIFF_IO_H__
+#define __TBDIFF_IO_H__
+
+#include <endian.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include <tbdiff/tbdiff-stat.h>
+
+size_t tbd_write_uint16_t (uint16_t value, FILE* stream);
+
+size_t tbd_write_uint32_t (uint32_t value, FILE* stream);
+
+size_t tbd_write_uint64_t (uint64_t value, FILE* stream);
+
+size_t tbd_write_time_t (time_t value, FILE* stream);
+
+size_t tbd_write_mode_t (mode_t value, FILE* stream);
+
+size_t tbd_write_uid_t (uid_t value, FILE* stream);
+
+size_t tbd_write_gid_t (gid_t value, FILE* stream);
+
+size_t tbd_write_size_t (size_t value, FILE* stream);
+
+size_t tbd_read_uint16_t (uint16_t *value, FILE* stream);
+
+size_t tbd_read_uint32_t (uint32_t *value, FILE* stream);
+
+size_t tbd_read_uint64_t (uint64_t *value, FILE* stream);
+
+size_t tbd_read_time_t (time_t *value, FILE* stream);
+
+size_t tbd_read_mode_t (mode_t *value, FILE* stream);
+
+size_t tbd_read_uid_t (uid_t *value, FILE* stream);
+
+size_t tbd_read_gid_t (gid_t *value, FILE* stream);
+
+size_t tbd_read_size_t (size_t *value, FILE* stream);
+
+#endif /* !__TBDIFF_IO_H__ */
diff --git a/tbdiff/tbdiff-private.h b/tbdiff/tbdiff-private.h
new file mode 100644
index 0000000..8287670
--- /dev/null
+++ b/tbdiff/tbdiff-private.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#if !defined (TBDIFF_INSIDE_TBDIFF_H) && !defined (TBDIFF_COMPILATION)
+#error "Only <tbdiff/tbdiff.h> may be included directly. This file might disappear or change contents."
+#endif
+
+#ifndef __TBDIFF_PRIVATE_H__
+#define __TBDIFF_PRIVATE_H__
+
+#define TB_DIFF_PROTOCOL_ID "Codethink:TBDIFFv0"
+
+#endif /* !__TBDIFF_PRIVATE_H__ */
diff --git a/tbdiff/tbdiff-stat.c b/tbdiff/tbdiff-stat.c
new file mode 100644
index 0000000..fd8964e
--- /dev/null
+++ b/tbdiff/tbdiff-stat.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <dirent.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <tbdiff/tbdiff-stat.h>
+
+static tbd_stat_t*
+tbd_stat_from_path(const char *name,
+ const char *path)
+{
+ struct stat info;
+
+ if(lstat(path, &info) != 0)
+ return NULL;
+
+ size_t nlen = strlen(name);
+ tbd_stat_t *ret = (tbd_stat_t*)malloc(sizeof(tbd_stat_t) + (nlen + 1));
+ if(ret == NULL)
+ return NULL;
+
+ ret->parent = NULL;
+ ret->size = 0;
+ ret->name = (char*)((uintptr_t)ret + sizeof(tbd_stat_t));
+ memcpy(ret->name, name, (nlen + 1));
+
+ if(S_ISREG(info.st_mode)) {
+ ret->type = TBD_STAT_TYPE_FILE;
+ ret->size = info.st_size;
+ } else if(S_ISDIR(info.st_mode)) {
+ ret->type = TBD_STAT_TYPE_DIR;
+ DIR *dp = opendir(path);
+
+ if(dp == NULL) {
+ free(ret);
+ return NULL;
+ }
+
+ /* FIXME: Remove the need for directory size? */
+ struct dirent *ds;
+ for(ds = readdir(dp); ds != NULL; ds = readdir(dp)) {
+ if((strcmp(ds->d_name, ".") == 0)
+ || (strcmp(ds->d_name, "..") == 0))
+ continue;
+
+ ret->size++;
+ }
+ closedir(dp);
+ } else if(S_ISLNK(info.st_mode))
+ ret->type = TBD_STAT_TYPE_SYMLINK;
+ else if(S_ISCHR(info.st_mode))
+ ret->type = TBD_STAT_TYPE_CHRDEV;
+ else if(S_ISBLK(info.st_mode))
+ ret->type = TBD_STAT_TYPE_BLKDEV;
+ else if(S_ISFIFO(info.st_mode))
+ ret->type = TBD_STAT_TYPE_FIFO;
+ else if(S_ISSOCK(info.st_mode))
+ ret->type = TBD_STAT_TYPE_SOCKET;
+ else {
+ free(ret);
+ return NULL;
+ }
+
+ ret->rdev = (uint32_t)info.st_rdev;
+ ret->uid = (uid_t)info.st_uid;
+ ret->gid = (gid_t)info.st_gid;
+ ret->mode = (mode_t)info.st_mode;
+ ret->mtime = (time_t)info.st_mtime;
+ return ret;
+}
+
+tbd_stat_t*
+tbd_stat(const char *path)
+{
+ tbd_stat_t *ret = tbd_stat_from_path(path, path);
+ return ret;
+}
+
+void
+tbd_stat_free(tbd_stat_t *file)
+{
+ free(file);
+}
+
+void
+tbd_stat_print(tbd_stat_t *file)
+{
+ (void)file;
+}
+
+tbd_stat_t*
+tbd_stat_entry(tbd_stat_t *file, uint32_t entry)
+{
+ if((file == NULL)
+ || (file->type != TBD_STAT_TYPE_DIR)
+ || (entry >= file->size))
+ return NULL;
+
+ char *path = tbd_stat_path(file);
+ DIR *dp = opendir(path);
+ free (path);
+
+ if(dp == NULL)
+ return NULL;
+
+ uintptr_t i;
+ struct dirent *ds;
+ for(i = 0; i <= entry; i++) {
+ ds = readdir(dp);
+ if(ds == NULL) {
+ closedir(dp);
+ return NULL;
+ }
+
+ if((strcmp(ds->d_name, ".") == 0) ||
+ (strcmp(ds->d_name, "..") == 0))
+ i--;
+ }
+ char *name = strndup(ds->d_name, ds->d_reclen-offsetof(struct dirent, d_name));
+ closedir (dp);
+
+ char *spath = tbd_stat_subpath(file, name);
+ if(spath == NULL)
+ return NULL;
+
+ tbd_stat_t *ret = tbd_stat_from_path(name, (const char*)spath);
+
+ free(name);
+ free(spath);
+
+ if (ret == NULL)
+ return NULL;
+
+ ret->parent = file;
+ return ret;
+}
+
+tbd_stat_t*
+tbd_stat_entry_find(tbd_stat_t *file,
+ const char *name)
+{
+ if((file == NULL)
+ || (file->type != TBD_STAT_TYPE_DIR))
+ return NULL;
+
+ char *path = tbd_stat_path (file);
+ DIR *dp = opendir(path);
+ free (path);
+
+ if(dp == NULL)
+ return NULL;
+
+ struct dirent *ds;
+ for(ds = readdir(dp); ds != NULL; ds = readdir(dp)) {
+ if(strcmp(ds->d_name, name) == 0) {
+ char *spath = tbd_stat_subpath(file, ds->d_name);
+
+ if(spath == NULL) {
+ closedir (dp);
+ return NULL;
+ }
+
+ tbd_stat_t *ret = tbd_stat_from_path(ds->d_name, (const char*)spath);
+ free(spath);
+ ret->parent = file;
+
+ closedir (dp);
+ return ret;
+ }
+ }
+
+ closedir (dp);
+ return NULL;
+}
+
+char*
+tbd_stat_subpath(tbd_stat_t *file,
+ const char *entry)
+{
+ if(file == NULL)
+ return NULL;
+
+ size_t elen = ((entry == NULL) ? 0 : (strlen(entry) + 1));
+ size_t plen;
+
+ tbd_stat_t *root;
+ for(root = file, plen = 0;
+ root != NULL;
+ plen += (strlen(root->name) + 1), root = (tbd_stat_t*)root->parent);
+
+ plen += elen;
+
+ char *path = (char*)malloc(plen);
+ if(path == NULL)
+ return NULL;
+ char *ptr = &path[plen];
+
+ if(entry != NULL) {
+ ptr = (char*)((uintptr_t)ptr - elen);
+ memcpy(ptr, entry, elen);
+ }
+
+ for(root = file; root != NULL; root = (tbd_stat_t*)root->parent) {
+ size_t rlen = strlen(root->name) + 1;
+ ptr = (char*)((uintptr_t)ptr - rlen);
+ memcpy(ptr, root->name, rlen);
+ if((file != root) || (entry != NULL))
+ ptr[rlen - 1] = '/';
+ }
+
+ return path;
+}
+
+char*
+tbd_stat_path(tbd_stat_t *file)
+{
+ return tbd_stat_subpath(file, NULL);
+}
+
+int
+tbd_stat_open(tbd_stat_t *file, int flags)
+{
+ char *path = tbd_stat_path(file);
+ if(path == NULL)
+ return -1;
+ int fd = open(path, flags);
+ free(path);
+ return fd;
+}
+
+FILE*
+tbd_stat_fopen(tbd_stat_t *file,
+ const char *mode)
+{
+ char *path = tbd_stat_path(file);
+ if(path == NULL)
+ return NULL;
+ FILE *fp = fopen(path, mode);
+ free(path);
+ return fp;
+}
diff --git a/tbdiff/tbdiff-stat.h b/tbdiff/tbdiff-stat.h
new file mode 100644
index 0000000..d23cc80
--- /dev/null
+++ b/tbdiff/tbdiff-stat.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#if !defined (TBDIFF_INSIDE_TBDIFF_H) && !defined (TBDIFF_COMPILATION)
+#error "Only <tbdiff/tbdiff.h> may be included directly. This file might disappear or change contents."
+#endif
+
+#ifndef __TBDIFF_STAT_H__
+#define __TBDIFF_STAT_H__
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+
+typedef enum {
+ TBD_STAT_TYPE_FILE = 'f',
+ TBD_STAT_TYPE_DIR = 'd',
+ TBD_STAT_TYPE_SYMLINK = 'l',
+ TBD_STAT_TYPE_CHRDEV = 'c',
+ TBD_STAT_TYPE_BLKDEV = 'b',
+ TBD_STAT_TYPE_FIFO = 'p',
+ TBD_STAT_TYPE_SOCKET = 's'
+} tbd_stat_type_e;
+
+typedef struct {
+ void* parent;
+ char* name;
+ tbd_stat_type_e type;
+ time_t mtime;
+ uint32_t size; // Count for directory.
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ uint32_t rdev;
+} tbd_stat_t;
+
+extern tbd_stat_t* tbd_stat(const char *path);
+extern void tbd_stat_free(tbd_stat_t *file);
+extern void tbd_stat_print(tbd_stat_t *file);
+extern tbd_stat_t* tbd_stat_entry(tbd_stat_t *file, uint32_t entry);
+extern tbd_stat_t* tbd_stat_entry_find(tbd_stat_t *file, const char *name);
+extern char* tbd_stat_subpath(tbd_stat_t *file, const char *entry);
+extern char* tbd_stat_path(tbd_stat_t *file);
+extern int tbd_stat_open(tbd_stat_t *file, int flags);
+extern FILE* tbd_stat_fopen(tbd_stat_t *file, const char *mode);
+
+#endif /* !__TBDIFF_STAT_H__ */
diff --git a/tbdiff/tbdiff-xattrs.c b/tbdiff/tbdiff-xattrs.c
new file mode 100644
index 0000000..95d263f
--- /dev/null
+++ b/tbdiff/tbdiff-xattrs.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <attr/xattr.h>
+#include <errno.h>
+
+#include <tbdiff/tbdiff-common.h>
+#include <tbdiff/tbdiff-xattrs.h>
+
+int tbd_xattrs_names(char const *path, tbd_xattrs_names_t *names)
+{
+ char *attrnames = NULL;
+ /* get size of names list */
+ ssize_t size = llistxattr(path, NULL, 0);
+ if (size < 0) {
+ if (errno == ENOSYS || errno == ENOTSUP) {
+ return TBD_ERROR(TBD_ERROR_XATTRS_NOT_SUPPORTED);
+ } else {
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ }
+
+ if (size == 0) {
+ names->begin = NULL;
+ names->end = NULL;
+ return TBD_ERROR_SUCCESS;
+ }
+
+ while (1) {
+ { /* allocate memory for list */
+ /* allocate 1 more for NUL terminator */
+ char *allocres = realloc(attrnames, size + 1);
+ if (allocres == NULL) {
+ free(attrnames);
+ return TBD_ERROR(TBD_ERROR_OUT_OF_MEMORY);
+ }
+ attrnames = allocres;
+ }
+
+ { /* try to read names list */
+ ssize_t listres = llistxattr(path, attrnames, size);
+ if (listres >= 0) {
+ /* succeeded, save size */
+ size = listres;
+ break;
+ }
+ if (errno != ERANGE) {
+ /* other error, can't fix */
+ free(attrnames);
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ /* not big enough, enlarge and try again */
+ size *= 2;
+ errno = 0;
+ }
+ }
+ /* ensure NUL terminated */
+ attrnames[size] = '\0';
+ names->begin = attrnames;
+ names->end = attrnames + size;
+ return TBD_ERROR_SUCCESS;
+}
+
+void tbd_xattrs_names_free(tbd_xattrs_names_t *names)
+{
+ free((void *)names->begin);
+}
+
+int tbd_xattrs_names_each(tbd_xattrs_names_t const *names,
+ int (*f)(char const *name, void *ud), void *ud)
+{
+ char const *name;
+ int err = TBD_ERROR_SUCCESS;
+ for (name = names->begin; name != names->end;
+ name = strchr(name, '\0') + 1) {
+ if ((err = f(name, ud)) != TBD_ERROR_SUCCESS) {
+ return err;
+ }
+ }
+ return err;
+}
+
+static int names_sum(char const *name, void *ud) {
+ if (name == NULL || ud == NULL)
+ return TBD_ERROR(TBD_ERROR_NULL_POINTER);
+ (*((uint32_t*)ud))++;
+ return TBD_ERROR_SUCCESS;
+}
+int tbd_xattrs_names_count(tbd_xattrs_names_t const *names, uint32_t *count) {
+ uint32_t _count = 0;
+ int err;
+ if ((err = tbd_xattrs_names_each(names, &names_sum, &_count)) ==
+ TBD_ERROR_SUCCESS) {
+ *count = _count;
+ }
+ return err;
+}
+
+static int name_remove(char const *name, void *ud) {
+ char const *path = ud;
+ if (lremovexattr(path, name) < 0) {
+ switch (errno) {
+ case ENOATTR:
+ return TBD_ERROR(TBD_ERROR_XATTRS_MISSING_ATTR);
+ case ENOTSUP:
+ return TBD_ERROR(TBD_ERROR_XATTRS_NOT_SUPPORTED);
+ default:
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ }
+ return TBD_ERROR_SUCCESS;
+}
+int tbd_xattrs_removeall(char const *path)
+{
+ int err = TBD_ERROR_SUCCESS;
+ tbd_xattrs_names_t list;
+
+ /* get the list of attributes */
+ if ((err = tbd_xattrs_names(path, &list)) != TBD_ERROR_SUCCESS) {
+ return err;
+ }
+
+ err = tbd_xattrs_names_each(&list, &name_remove, (void*)path);
+
+ tbd_xattrs_names_free(&list);
+ return err;
+}
+
+int tbd_xattrs_get(char const *path, char const* name, void **buf,
+ size_t *bufsize, size_t *valsize)
+{
+ ssize_t toalloc = *bufsize;
+ if (toalloc == 0 || *buf == NULL) {
+ toalloc = lgetxattr(path, name, NULL, 0);
+ if (toalloc < 0) {
+ if (errno == ENOSYS || errno == ENOTSUP) {
+ return TBD_ERROR(TBD_ERROR_XATTRS_NOT_SUPPORTED);
+ } else {
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ }
+ {
+ void *allocres = malloc(toalloc);
+ if (allocres == NULL) {
+ return TBD_ERROR(TBD_ERROR_OUT_OF_MEMORY);
+ }
+ *buf = allocres;
+ *bufsize = toalloc;
+ }
+
+ }
+ while (1) {
+ { /* try to get value */
+ ssize_t getres = lgetxattr(path, name, *buf, *bufsize);
+ if (getres >= 0) {
+ /* succeeded, save size */
+ *valsize = getres;
+ break;
+ }
+ if (errno != ERANGE) {
+ /* other error, can't fix */
+ return TBD_ERROR(TBD_ERROR_FAILURE);
+ }
+ /* not big enough, enlarge and try again */
+ toalloc *= 2;
+ errno = 0;
+ }
+
+ { /* allocate memory for list */
+ void *allocres = realloc(*buf, toalloc);
+ if (allocres == NULL) {
+ return TBD_ERROR(TBD_ERROR_OUT_OF_MEMORY);
+ }
+ *buf = allocres;
+ *bufsize = toalloc;
+ }
+ }
+ return TBD_ERROR_SUCCESS;
+}
+
+typedef struct {
+ char const *path;
+ tbd_xattrs_pairs_callback_t f;
+ void *pairs_ud;
+ void *data;
+ size_t data_size;
+} tbd_xattrs_pairs_params_t;
+static int call_with_data(char const *name, void *ud)
+{
+ tbd_xattrs_pairs_params_t *params;
+ params = ud;
+ size_t value_size;
+ int err;
+ if ((err = tbd_xattrs_get(params->path, name, &(params->data),
+ &(params->data_size), &value_size)) !=
+ TBD_ERROR_SUCCESS) {
+ return err;
+ }
+ return params->f(name, params->data, value_size, params->pairs_ud);
+}
+int tbd_xattrs_pairs(tbd_xattrs_names_t const *names, char const *path,
+ tbd_xattrs_pairs_callback_t f, void *ud)
+{
+ tbd_xattrs_pairs_params_t params = {
+ path, f, ud, NULL, 0,
+ };
+ int err = tbd_xattrs_names_each(names, &call_with_data, &params);
+ free(params.data);
+ return err;
+}
diff --git a/tbdiff/tbdiff-xattrs.h b/tbdiff/tbdiff-xattrs.h
new file mode 100644
index 0000000..ba4a79e
--- /dev/null
+++ b/tbdiff/tbdiff-xattrs.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#if !defined (TBDIFF_INSIDE_TBDIFF_H) && !defined (TBDIFF_COMPILATION)
+#error "Only <tbdiff/tbdiff.h> may be included directly. This file might disappear or change contents."
+#endif
+
+#ifndef _TBDIFF_XATTRS_H
+#define _TBDIFF_XATTRS_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+/* structure for names data */
+typedef struct tbd_xattrs_names {
+ char const *begin;
+ char const *end;
+} tbd_xattrs_names_t;
+
+/* gets a list of the names of the file referenced by path */
+extern int tbd_xattrs_names(char const *path, tbd_xattrs_names_t *names_out);
+
+/* frees up the INTERNAL resources of the list, doesn't free the list itself */
+extern void tbd_xattrs_names_free(tbd_xattrs_names_t *names);
+
+/* calls f with every name in the list */
+extern int tbd_xattrs_names_each(tbd_xattrs_names_t const *names,
+ int (*f)(char const *name, void *ud),
+ void *ud);
+
+/* gets how many different attributes there are in the list */
+extern int tbd_xattrs_names_count(tbd_xattrs_names_t const *names, uint32_t *count);
+
+/* puts the value of the attribute called name into *buf with size *bufsize
+ * if *buf is NULL or *bufsize is 0 then memory will be allocated for it
+ * if *buf was too small it will be reallocated
+ * if it is successful, *buf will contain *valsize bytes of data
+ */
+extern int tbd_xattrs_get(char const *path, char const* name, void **buf,
+ size_t *bufsize, size_t *valsize);
+
+/* removes all attributes of the file referenced by path */
+extern int tbd_xattrs_removeall(char const *path);
+
+/* calls f for every attribute:value pair in the list */
+typedef int (*tbd_xattrs_pairs_callback_t)(char const *name, void const *data,
+ size_t size, void *ud);
+extern int tbd_xattrs_pairs(tbd_xattrs_names_t const *names, char const *path,
+ tbd_xattrs_pairs_callback_t f, void *ud);
+#endif /* !__TBDIFF_XATTRS_H__ */
diff --git a/tbdiff/tbdiff.h b/tbdiff/tbdiff.h
new file mode 100644
index 0000000..b7027e9
--- /dev/null
+++ b/tbdiff/tbdiff.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011-2012 Codethink Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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 __TBDIFF_H__
+#define __TBDIFF_H__
+
+#define TBDIFF_INSIDE_TBDIFF_H
+
+#include <tbdiff/tbdiff-common.h>
+#include <tbdiff/tbdiff-io.h>
+#include <tbdiff/tbdiff-private.h>
+#include <tbdiff/tbdiff-stat.h>
+#include <tbdiff/tbdiff-xattrs.h>
+
+#undef TBDIFF_INSIDE_TBDIFF_H
+
+#endif /* !__TBDIFF_H__ */