diff options
Diffstat (limited to 'tbdiff')
-rw-r--r-- | tbdiff/Makefile.am | 60 | ||||
-rw-r--r-- | tbdiff/tbdiff-1.pc.in | 13 | ||||
-rw-r--r-- | tbdiff/tbdiff-apply.c | 740 | ||||
-rw-r--r-- | tbdiff/tbdiff-common.h | 103 | ||||
-rw-r--r-- | tbdiff/tbdiff-create.c | 797 | ||||
-rw-r--r-- | tbdiff/tbdiff-io.c | 166 | ||||
-rw-r--r-- | tbdiff/tbdiff-io.h | 63 | ||||
-rw-r--r-- | tbdiff/tbdiff-private.h | 27 | ||||
-rw-r--r-- | tbdiff/tbdiff-stat.c | 265 | ||||
-rw-r--r-- | tbdiff/tbdiff-stat.h | 62 | ||||
-rw-r--r-- | tbdiff/tbdiff-xattrs.c | 227 | ||||
-rw-r--r-- | tbdiff/tbdiff-xattrs.h | 64 | ||||
-rw-r--r-- | tbdiff/tbdiff.h | 31 |
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, ¶ms); + 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__ */ |