summaryrefslogtreecommitdiff
path: root/tbdiff
diff options
context:
space:
mode:
authorJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-03-22 13:55:30 +0000
committerJannis Pohlmann <jannis.pohlmann@codethink.co.uk>2012-03-22 15:32:12 +0000
commitc7a4f00fd701c7e1bd071c7a5aa8199f96833823 (patch)
tree9f3d83669527a1691fd2ad9e687671ba472f6bc4 /tbdiff
parent55551592563c26d28c62d055e3ebc657ad687a0a (diff)
downloadtbdiff-c7a4f00fd701c7e1bd071c7a5aa8199f96833823.tar.gz
Switch to a shared tbdiff library and make this an autotools project.
This commit converts tbdiff to being an autotools-based project. This means that we now support the usual autoreconf -i && ./configure && make && make install process, plus we provide 'make check' for the tbdiff test suite. The tbdiff library is now build as a shared library and is also installed into the system for others to use. The library is libtool-versioned and ships a pkg-config file (tbdiff-1.pc). The headers were adjusted so that only tbdiff/tbdiff.h may be included directly; all others are considered internal. The tbdiff-create and tbdiff-deploy tools were changed to include this header file. The tbdiff library is still GPL, not LGPL. We might want to change this in the future. Thanks to switching to autotools we now have a way to make releases by means of 'make dist' and 'make distcheck'. Unfortunately, the latter currently fails, probably due to something being missing in tbdiff/Makefile.am.
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__ */