From: Sergey Nazaryev <sergey@coolautomation.com>
Date: Thu, 27 Jan 2022 19:41:23 +0200
Subject: [PATCH 1/1] handlers: rdiff handler for applying librsync's rdiff
 patches to UBI volumes

The ubivol rdiff handler adds support for applying binary
delta patches generated by librsync's rdiff tool to UBI volumes,
see http://librsync.sourcefrog.net

Signed-off-by: Sergey Nazaryev <sergey@coolautomation.com>
Signed-off-by: David Escalona <david.escalona@digi.com>
---
 Makefile.flags                  |   4 +
 configs/all_handlers_defconfig  |   1 +
 handlers/Config.in              |  13 +
 handlers/Makefile               |   1 +
 handlers/ubivol_rdiff_handler.c | 606 ++++++++++++++++++++++++++++++++
 5 files changed, 625 insertions(+)
 create mode 100644 handlers/ubivol_rdiff_handler.c

diff --git a/Makefile.flags b/Makefile.flags
index 2a021c8..b542358 100644
--- a/Makefile.flags
+++ b/Makefile.flags
@@ -209,6 +209,10 @@ ifeq ($(CONFIG_RDIFFHANDLER),y)
 LDLIBS += rsync
 endif
 
+ifeq ($(CONFIG_UBIVOL_RDIFFHANDLER),y)
+LDLIBS += rsync
+endif
+
 ifeq ($(CONFIG_REMOTE_HANDLER),y)
 LDLIBS += zmq
 endif
diff --git a/configs/all_handlers_defconfig b/configs/all_handlers_defconfig
index 8f6f8e0..9077220 100644
--- a/configs/all_handlers_defconfig
+++ b/configs/all_handlers_defconfig
@@ -31,5 +31,6 @@ CONFIG_SHELLSCRIPTHANDLER=y
 CONFIG_SWUFORWARDER_HANDLER=y
 CONFIG_SSBLSWITCH=y
 CONFIG_UBIVOL=y
+CONFIG_UBIVOL_RDIFFHANDLER=y
 CONFIG_UCFWHANDLER=y
 CONFIG_UNIQUEUUID=y
diff --git a/handlers/Config.in b/handlers/Config.in
index 107ffeb..62d54bd 100644
--- a/handlers/Config.in
+++ b/handlers/Config.in
@@ -166,6 +166,19 @@ config RDIFFHANDLER
 comment "rdiff support needs librsync"
 	depends on !HAVE_LIBRSYNC
 
+config UBIVOL_RDIFFHANDLER
+	bool "ubirdiff"
+	depends on HAVE_LIBRSYNC
+	depends on HAVE_LIBUBI
+	depends on MTD
+	default n
+	help
+	  Add support for applying librsync's rdiff patches to UBI volumes,
+	  see http://librsync.sourcefrog.net/
+
+comment "ubirdiff support needs libubi and librsync"
+	depends on !HAVE_LIBRSYNC || !HAVE_LIBUBI
+
 config READBACKHANDLER
 	bool "readback"
 	depends on HASH_VERIFY
diff --git a/handlers/Makefile b/handlers/Makefile
index b5203f9..bd47d3b 100644
--- a/handlers/Makefile
+++ b/handlers/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_CFIHAMMING1)+= flash_hamming1_handler.o
 obj-$(CONFIG_LUASCRIPTHANDLER) += lua_scripthandler.o
 obj-$(CONFIG_RAW)	+= raw_handler.o
 obj-$(CONFIG_RDIFFHANDLER) += rdiff_handler.o
+obj-$(CONFIG_UBIVOL_RDIFFHANDLER) += ubivol_rdiff_handler.o
 obj-$(CONFIG_READBACKHANDLER) += readback_handler.o
 obj-$(CONFIG_REMOTE_HANDLER) += remote_handler.o
 obj-$(CONFIG_SHELLSCRIPTHANDLER) += shell_scripthandler.o
diff --git a/handlers/ubivol_rdiff_handler.c b/handlers/ubivol_rdiff_handler.c
new file mode 100644
index 0000000..3b6073d
--- /dev/null
+++ b/handlers/ubivol_rdiff_handler.c
@@ -0,0 +1,606 @@
+/*
+ * Author: Christian Storm
+ * Copyright (C) 2018, Siemens AG
+ *
+ * SPDX-License-Identifier:     GPL-2.0-only
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <librsync.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <mtd/mtd-user.h>
+#include "swupdate.h"
+#include "handler.h"
+#include "flash.h"
+#include "util.h"
+
+/* Use rdiff's default inbuf and outbuf size of 64K */
+#define RDIFF_BUFFER_SIZE 64 * 1024
+
+#define TEST_OR_FAIL(expr, failret) \
+	if (expr) { \
+	} else { \
+		ERROR("Assertion violated: %s.", #expr); \
+		return failret; \
+	}
+
+void ubivol_rdiff_image_handler(void);
+
+struct rdiff_t
+{
+	rs_job_t *job;
+	rs_buffers_t buffers;
+
+	int dest_file_fd;
+	FILE *base_file;
+
+	char *inbuf;
+	char *outbuf;
+
+	uint8_t type;
+};
+
+static void rdiff_log(rs_loglevel level, char const *msg)
+{
+	int loglevelmap[] =
+	{
+		[RS_LOG_EMERG]   = ERRORLEVEL,
+		[RS_LOG_ALERT]   = ERRORLEVEL,
+		[RS_LOG_CRIT]    = ERRORLEVEL,
+		[RS_LOG_ERR]     = ERRORLEVEL,
+		[RS_LOG_WARNING] = WARNLEVEL,
+		[RS_LOG_NOTICE]  = INFOLEVEL,
+		[RS_LOG_INFO]    = INFOLEVEL,
+		[RS_LOG_DEBUG]   = TRACELEVEL
+	};
+	*strchrnul(msg, '\n') = '\0';
+	swupdate_notify(RUN, "%s", loglevelmap[level], msg);
+}
+
+static rs_result base_file_read_cb(void *fp, rs_long_t pos, size_t *len, void **buf)
+{
+	FILE *f = (FILE *)fp;
+
+	if (fseek(f, pos, SEEK_SET) != 0) {
+		ERROR("Error seeking rdiff base file: %s", strerror(errno));
+		return RS_IO_ERROR;
+	}
+
+	int ret = fread(*buf, 1, *len, f);
+	if (ret == -1) {
+		ERROR("Error reading rdiff base file: %s", strerror(errno));
+		return RS_IO_ERROR;
+	}
+	if (ret == 0) {
+		ERROR("Unexpected EOF on rdiff base file.");
+		return RS_INPUT_ENDED;
+	}
+	*len = ret;
+
+	return RS_DONE;
+}
+
+static rs_result fill_inbuffer(struct rdiff_t *rdiff_state, const void *buf, unsigned int *len)
+{
+	rs_buffers_t *buffers = &rdiff_state->buffers;
+
+	if (buffers->eof_in == true) {
+		TRACE("EOF on rdiff chunk input, not reading more data.");
+		return RS_DONE;
+	}
+
+	if (*len == 0) {
+		TRACE("No rdiff chunk input to consume.");
+		return RS_DONE;
+	}
+
+	if (buffers->avail_in == 0) {
+		/* No more buffered input data pending, get some... */
+		TEST_OR_FAIL(*len <= RDIFF_BUFFER_SIZE, RS_IO_ERROR);
+		buffers->next_in = rdiff_state->inbuf;
+		buffers->avail_in = *len;
+		TRACE("Writing %d bytes to rdiff input buffer.", *len);
+		(void)memcpy(rdiff_state->inbuf, buf, *len);
+		*len = 0;
+	} else {
+		/* There's more input, try to append it to input buffer. */
+		char *target = buffers->next_in + buffers->avail_in;
+		unsigned int buflen = rdiff_state->inbuf + RDIFF_BUFFER_SIZE - target;
+		buflen = buflen > *len ? *len : buflen;
+		TEST_OR_FAIL(target + buflen <= rdiff_state->inbuf + RDIFF_BUFFER_SIZE, RS_IO_ERROR);
+
+		if (buflen == 0) {
+			TRACE("Not consuming rdiff chunk input, buffer already filled.");
+			return RS_BLOCKED;
+		}
+		TRACE("Appending %d bytes to rdiff input buffer.", buflen);
+		buffers->avail_in += buflen;
+		(void)memcpy(target, buf, buflen);
+		*len -= buflen;
+	}
+	return RS_DONE;
+}
+
+static rs_result drain_outbuffer(struct rdiff_t *rdiff_state)
+{
+	rs_buffers_t *buffers = &rdiff_state->buffers;
+
+	int len = buffers->next_out - rdiff_state->outbuf;
+	TEST_OR_FAIL(len <= RDIFF_BUFFER_SIZE, RS_IO_ERROR);
+	TEST_OR_FAIL(buffers->next_out >= rdiff_state->outbuf, RS_IO_ERROR);
+	TEST_OR_FAIL(buffers->next_out <= rdiff_state->outbuf + RDIFF_BUFFER_SIZE, RS_IO_ERROR);
+
+	if (len > 0) {
+		TRACE("Draining %d bytes from rdiff output buffer", len);
+		buffers->next_out = rdiff_state->outbuf;
+		buffers->avail_out = RDIFF_BUFFER_SIZE;
+		if (copy_write(&rdiff_state->dest_file_fd, buffers->next_out, len) != 0) {
+			ERROR("Cannot drain rdiff output buffer.");
+			return RS_IO_ERROR;
+		}
+	} else {
+		TRACE("No output rdiff buffer data to drain.");
+	}
+	return RS_DONE;
+}
+
+static inline void rdiff_stats(const char* msg, struct rdiff_t *rdiff_state, rs_result result) {
+	rs_buffers_t *buffers = &rdiff_state->buffers;
+	char *strresult = (char*)"ERROR";
+	switch (result) {
+		case RS_DONE:    strresult = (char*)"DONE";    break;
+		case RS_BLOCKED: strresult = (char*)"BLOCKED"; break;
+		case RS_RUNNING: strresult = (char*)"RUNNING"; break;
+		default: break;
+	}
+	TRACE("%s avail_in=%ld avail_out=%ld result=%s",
+		  msg, buffers->avail_in, buffers->avail_out, strresult);
+}
+
+/**
+ * check_ubi_alwaysremove - check the property always-remove for this image
+ * @img: image information
+ *
+ * Return: 1 if the property always-remove is true, otherwise 0.
+ */
+static bool check_ubi_alwaysremove(struct img_type *img)
+{
+	return strtobool(dict_get_value(&img->properties, "always-remove"));
+}
+
+static struct ubi_part *search_volume(const char *str, struct ubilist *list)
+{
+	struct ubi_part *vol;
+
+	LIST_FOREACH(vol, list, next) {
+		if (strcmp(vol->vol_info.name, str) == 0)
+			return vol;
+	}
+	return NULL;
+}
+
+/* search a UBI volume by name across all mtd partitions */
+static struct ubi_part *search_volume_global(const char *str)
+{
+	struct flash_description *flash = get_flash_info();
+	struct mtd_info *mtd_info = &flash->mtd;
+	struct mtd_ubi_info *mtd_ubi_info;
+	struct ubi_part *ubivol;
+	int i;
+
+	for (i = mtd_info->lowest_mtd_num; i <= mtd_info->highest_mtd_num; i++) {
+		mtd_ubi_info = &flash->mtd_info[i];
+		ubivol = search_volume(str, &mtd_ubi_info->ubi_partitions);
+		if (ubivol)
+			return ubivol;
+	}
+	return NULL;
+}
+
+static int resize_volume(struct img_type *cfg, long long size)
+{
+	struct flash_description *nandubi = get_flash_info();
+	struct ubi_part *ubivol;
+	struct ubi_mkvol_request req;
+	struct mtd_ubi_info *mtd_info;
+	int mtdnum, req_vol_type;
+	char node[64];
+	int err;
+	struct flash_description *flash = get_flash_info();
+
+	/* determine the requested volume type */
+	if (!strcmp(cfg->type_data, "static"))
+		req_vol_type = UBI_STATIC_VOLUME;
+	else
+		req_vol_type = UBI_DYNAMIC_VOLUME;
+
+	/*
+	 * Partition are adjusted only in one MTD device
+	 * Other MTD are not touched
+	 */
+	mtdnum = get_mtd_from_device(cfg->device);
+	if (mtdnum < 0) {
+		/* Allow device to be specified by name OR number */
+		mtdnum = get_mtd_from_name(cfg->device);
+	}
+	if (mtdnum < 0 || !mtd_dev_present(flash->libmtd, mtdnum)) {
+		ERROR("%s does not exist: partitioning not possible",
+			cfg->device);
+		return -ENODEV;
+	}
+
+	mtd_info = &nandubi->mtd_info[mtdnum];
+
+	/*
+	 * Search for volume with the same name
+	 */
+	ubivol = mtd_info->ubi_partitions.lh_first;
+	for(ubivol = mtd_info->ubi_partitions.lh_first;
+		ubivol != NULL;
+		ubivol = ubivol->next.le_next) {
+		if (strcmp(ubivol->vol_info.name, cfg->volname) == 0) {
+			break;
+		}
+	}
+
+	if (ubivol) {
+		unsigned int requested_lebs, allocated_lebs;
+
+		/* This should never happen, the fields are filled by scan_ubi */
+		if (!mtd_info->dev_info.leb_size) {
+			return -EFAULT;
+		}
+
+		/* Check if size is changed */
+		requested_lebs = size / mtd_info->dev_info.leb_size +
+			((size % mtd_info->dev_info.leb_size) ? 1 : 0);
+		allocated_lebs = ubivol->vol_info.rsvd_bytes / mtd_info->dev_info.leb_size;
+
+		if (requested_lebs == allocated_lebs &&
+		    req_vol_type == ubivol->vol_info.type &&
+		    !check_ubi_alwaysremove(cfg)) {
+			TRACE("skipping volume %s (same size and type)",
+			      ubivol->vol_info.name);
+			return 0;
+		}
+
+		snprintf(node, sizeof(node), "/dev/ubi%d", ubivol->vol_info.dev_num);
+		err = ubi_rmvol(nandubi->libubi, node, ubivol->vol_info.vol_id);
+		if (err) {
+			ERROR("Volume %s cannot be dropped", ubivol->vol_info.name);
+			return -1;
+		}
+		TRACE("Removed UBI Volume %s", ubivol->vol_info.name);
+
+		LIST_REMOVE(ubivol, next);
+		free(ubivol);
+	}
+
+	if (size) {
+		/* We do not need a volume to get the right node */
+		snprintf(node, sizeof(node), "/dev/ubi%d", mtd_info->dev_info.dev_num);
+
+		/*
+		 * Creates all other partitions as specified in the description file
+		 * Volumes are empty, and they are filled later by the update procedure
+		 */
+		memset(&req, 0, sizeof(req));
+		req.vol_type = req_vol_type;
+		req.vol_id = UBI_VOL_NUM_AUTO;
+		req.alignment = 1;
+		req.bytes = size;
+		req.name = cfg->volname;
+		err = ubi_mkvol(nandubi->libubi, node, &req);
+		if (err < 0) {
+			ERROR("cannot create %s UBI volume %s of %lld bytes",
+				  (req_vol_type == UBI_DYNAMIC_VOLUME) ? "dynamic" : "static",
+				req.name, req.bytes);
+			return err;
+		}
+
+		ubivol = (struct ubi_part *)calloc(1, sizeof(struct ubi_part));
+		if (!ubivol) {
+			ERROR("No memory: malloc failed");
+			return -ENOMEM;
+		}
+		err = ubi_get_vol_info1(nandubi->libubi,
+			mtd_info->dev_info.dev_num, req.vol_id,
+			&ubivol->vol_info);
+		if (err) {
+			ERROR("cannot get information about "
+				"newly created UBI volume");
+			return err;
+		}
+		LIST_INSERT_HEAD(&mtd_info->ubi_partitions, ubivol, next);
+		TRACE("Created %s UBI volume %s of %lld bytes (old size %lld)",
+			  (req_vol_type == UBI_DYNAMIC_VOLUME) ? "dynamic" : "static",
+			  req.name, req.bytes, ubivol->vol_info.rsvd_bytes);
+	}
+
+	return 0;
+}
+
+/**
+ * check_auto_resize - check the property auto-resize for this image
+ * @img: image information
+ *
+ * Return: 1 if the property auto-resize is true, otherwise 0.
+ */
+static bool check_ubi_autoresize(struct img_type *img)
+{
+	return strtobool(dict_get_value(&img->properties, "auto-resize"));
+}
+
+static int wait_volume(struct img_type *img)
+{
+	int ret = -1, num = 0, dev_num, vol_id;
+	struct ubi_part *ubivol;
+	struct stat buf;
+	char node[64];
+
+	ubivol = search_volume_global(img->volname);
+	if (!ubivol) {
+		ERROR("can't found volume %s", img->volname);
+		return -1;
+	}
+
+	dev_num = ubivol->vol_info.dev_num;
+	vol_id  = ubivol->vol_info.vol_id;
+
+	snprintf(node, sizeof(node), "/dev/ubi%d_%d",
+		 dev_num,
+		 vol_id);
+
+	while (num++ < 5)
+	{
+		ret = stat(node, &buf);
+		if (!ret)
+			break;
+
+		sleep(1);
+	}
+
+	return ret;
+}
+
+static int apply_rdiff_chunk_cb(void *out, const void *buf, unsigned int len)
+{
+	struct rdiff_t *rdiff_state = (struct rdiff_t *)out;
+	rs_buffers_t *buffers = &rdiff_state->buffers;
+	unsigned int inbytesleft = len;
+	rs_result result = RS_RUNNING;
+	rs_result drain_run_result = RS_RUNNING;
+
+	if (buffers->next_out == NULL) {
+		TEST_OR_FAIL(buffers->avail_out == 0, -1);
+		buffers->next_out = rdiff_state->outbuf;
+		buffers->avail_out = RDIFF_BUFFER_SIZE;
+	}
+
+	while (inbytesleft > 0 || buffers->avail_in > 0) {
+		rdiff_stats("[pre] ", rdiff_state, result);
+		result = fill_inbuffer(rdiff_state, buf, &inbytesleft);
+		if (result != RS_DONE && result != RS_BLOCKED) {
+			return -1;
+		}
+		result = rs_job_iter(rdiff_state->job, buffers);
+		if (result != RS_DONE && result != RS_BLOCKED) {
+			ERROR("Error processing rdiff chunk: %s", rs_strerror(result));
+			return -1;
+		}
+		drain_run_result = drain_outbuffer(rdiff_state);
+		if (drain_run_result != RS_DONE) {
+			ERROR("drain_outbuffer return error");
+			return -1;
+		}
+		rdiff_stats("[post]", rdiff_state, result);
+
+		if (result == RS_DONE) {
+			TRACE("rdiff processing done.");
+			break;
+		}
+	}
+	rdiff_stats("[ret] ", rdiff_state, result);
+	return 0;
+}
+
+static int apply_rdiff_patch(struct img_type *img,
+							 void __attribute__((__unused__)) * data)
+{
+	int ret = 0;
+	long long bytes;
+	char node[64];
+	int err;
+	char sbuf[128];
+
+	struct rdiff_t rdiff_state = {};
+	rdiff_state.type = IMAGE_HANDLER;
+
+	struct flash_description *flash = get_flash_info();
+	libubi_t libubi = flash->libubi;
+
+	char *base_file_filename = NULL;
+	char *output_size_str = NULL;
+	struct ubi_part *ubivol;
+	struct ubi_vol_info *vol;
+
+	if (img->seek) {
+		/*
+		 * img->seek mandates copyfile()'s out parameter to be a fd, it
+		 * isn't. So, the seek option is invalid for the rdiff handler.
+		 * */
+		ERROR("Option 'seek' is not supported for rdiff.");
+		return -1;
+	}
+
+	base_file_filename = dict_get_value(&img->properties, "rdiffbase");
+	if (base_file_filename == NULL) {
+		ERROR("Property 'rdiffbase' is missing in sw-description.");
+		return -1;
+	}
+
+	output_size_str = dict_get_value(&img->properties, "rdiffnewsize");
+	if (!output_size_str) {
+		ERROR("Property 'rdiffnewsize' is missing in sw-description.");
+		return -1;
+	}
+
+	bytes = ustrtoull(output_size_str, NULL, 0);
+	if (errno || bytes <= 0) {
+		ERROR("rdiffnewsize argument %s: ustrtoull failed",
+		      output_size_str);
+		return -1;
+	}
+	TRACE("The size after applying patch would be %lld bytes", bytes);
+
+	if (check_ubi_autoresize(img)) {
+		ret = resize_volume(img, bytes);
+		if (ret < 0) {
+			ERROR("Can't resize ubi volume %s", img->volname);
+			return -1;
+		}
+
+		ret = wait_volume(img);
+		if (ret < 0) {
+			ERROR("can't found ubi volume %s", img->volname);
+			return -1;
+		}
+	}
+
+	if (!libubi) {
+		ERROR("Request to write into UBI, but no UBI on system");
+		return -1;
+	}
+
+	/* find the volume to be updated */
+	ubivol = search_volume_global(img->volname);
+
+	if (!ubivol) {
+		ERROR("Image %s should be stored in volume "
+			"%s, but no volume found",
+			img->fname,
+				img->volname);
+		return -1;
+	}
+
+	vol = &ubivol->vol_info;
+
+	if (bytes > vol->rsvd_bytes) {
+		ERROR("\"%s\" (size %lld) will not fit volume \"%s\" (size %lld)",
+		       img->fname, bytes, img->volname, vol->rsvd_bytes);
+		return -1;
+	}
+
+	snprintf(node, sizeof(node), "/dev/ubi%d_%d",
+		vol->dev_num,
+		vol->vol_id);
+
+	err = ubi_probe_node(libubi, node);
+
+	if (err == 1) {
+		ERROR("\"%s\" is an UBI device node, not an UBI volume node",
+			node);
+		return -1;
+	}
+	if (err < 0) {
+		if (errno == ENODEV)
+			ERROR("%s is not an UBI volume node", node);
+		else
+			ERROR("error while probing %s", node);
+		return -1;
+	}
+
+	rdiff_state.dest_file_fd = open(node, O_RDWR);
+	if (rdiff_state.dest_file_fd < 0) {
+		ERROR("cannot open UBI volume \"%s\"", node);
+		return -1;
+	}
+
+	if ((rdiff_state.base_file = fopen(base_file_filename, "rb+")) == NULL) {
+		ERROR("%s cannot be opened for reading: %s", base_file_filename, strerror(errno));
+		ret = -1;
+		goto cleanup;
+	}
+
+	if (!(rdiff_state.inbuf = malloc(RDIFF_BUFFER_SIZE))) {
+		ERROR("Cannot allocate memory for rdiff input buffer.");
+		ret = -1;
+		goto cleanup;
+	}
+
+	if (!(rdiff_state.outbuf = malloc(RDIFF_BUFFER_SIZE))) {
+		ERROR("Cannot allocate memory for rdiff output buffer.");
+		ret = -1;
+		goto cleanup;
+	}
+
+	err = ubi_update_start(libubi, rdiff_state.dest_file_fd, bytes);
+	if (err) {
+		ERROR("cannot start volume \"%s\" update", node);
+		ret = -1;
+		goto cleanup;
+	}
+
+	snprintf(sbuf, sizeof(sbuf), "Installing image %s into volume %s(%s)",
+		img->fname, node, img->volname);
+	notify(RUN, RECOVERY_NO_ERROR, INFOLEVEL, sbuf);
+
+	int loglevelmap[] =
+	{
+		[OFF]        = RS_LOG_ERR,
+		[ERRORLEVEL] = RS_LOG_ERR,
+		[WARNLEVEL]  = RS_LOG_WARNING,
+		[INFOLEVEL]  = RS_LOG_INFO,
+		[DEBUGLEVEL] = RS_LOG_DEBUG,
+		[TRACELEVEL] = RS_LOG_DEBUG,
+	};
+	rs_trace_set_level(loglevelmap[loglevel]);
+	rs_trace_to(rdiff_log);
+
+	rdiff_state.job = rs_patch_begin(base_file_read_cb, rdiff_state.base_file);
+	ret = copyfile(img->fdin,
+			&rdiff_state,
+			img->size,
+			(unsigned long *)&img->offset,
+			img->seek,
+			0, /* no skip */
+			img->compressed,
+			&img->checksum,
+			img->sha256,
+			img->is_encrypted,
+			img->ivt_ascii,
+			apply_rdiff_chunk_cb);
+	if (ret != 0) {
+		ERROR("Error %d running rdiff job, aborting.", ret);
+		goto cleanup;
+	}
+
+cleanup:
+	free(rdiff_state.inbuf);
+	free(rdiff_state.outbuf);
+	if (rdiff_state.job != NULL) {
+		(void)rs_job_free(rdiff_state.job);
+	}
+	if (rdiff_state.base_file != NULL) {
+		if (fclose(rdiff_state.base_file) == EOF) {
+			ERROR("Error while closing rdiff base: %s", strerror(errno));
+		}
+	}
+	close(rdiff_state.dest_file_fd);
+	return ret;
+}
+
+__attribute__((constructor))
+void ubivol_rdiff_image_handler(void)
+{
+	register_handler("ubivol_rdiff_image", apply_rdiff_patch, IMAGE_HANDLER, NULL);
+}
