diff mbox

[38/38] UBIFS: introduce ubifs_unpacker tool.

Message ID 1450687321-12381-39-git-send-email-yangds.fnst@cn.fujitsu.com
State Not Applicable
Delegated to: David Oberhollenzer
Headers show

Commit Message

Dongsheng Yang Dec. 21, 2015, 8:42 a.m. UTC
From: David Gstir <david@sigma-star.at>

ubifs_unpack allows unpacking of ubifs volumes. Input can be either a image
file created from nanddump or a ubi volume.

ubifs_unpack also performs necessary sanity checks and prints a detailed report
of the FS-state.

Signed-off-by: David Gstir <david@sigma-star.at>
Signed-off-by: Richard Weinberger <richard@nod.at>
Signed-off-by: Dongsheng Yang <yangds.fnst@cn.fujitsu.com>
---
 Makefile                                |   7 +-
 ubifs-utils/include/emubi.h             |   1 +
 ubifs-utils/include/list_sort.h         |  11 +
 ubifs-utils/include/ubifs.h             |  34 ++
 ubifs-utils/lib/emubi.c                 |  20 +
 ubifs-utils/lib/list_sort.c             | 157 ++++++
 ubifs-utils/ubifs_unpack/index.c        | 648 ++++++++++++++++++++++++
 ubifs-utils/ubifs_unpack/replay.c       | 865 ++++++++++++++++++++++++++++++++
 ubifs-utils/ubifs_unpack/ubifs_unpack.c | 619 +++++++++++++++++++++++
 ubifs-utils/ubifs_unpack/ubifs_unpack.h | 107 ++++
 10 files changed, 2468 insertions(+), 1 deletion(-)
 create mode 100644 ubifs-utils/include/list_sort.h
 create mode 100644 ubifs-utils/lib/list_sort.c
 create mode 100644 ubifs-utils/ubifs_unpack/index.c
 create mode 100644 ubifs-utils/ubifs_unpack/replay.c
 create mode 100644 ubifs-utils/ubifs_unpack/ubifs_unpack.c
 create mode 100644 ubifs-utils/ubifs_unpack/ubifs_unpack.h
diff mbox

Patch

diff --git a/Makefile b/Makefile
index 1486791..bcf65fa 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,8 @@  UBI_BINS = \
 	ubidetach ubinize ubiformat ubirename mtdinfo ubirsvol ubiblock
 UBIFS_BINS = \
 	mkfs.ubifs/mkfs.ubifs \
-	ubifs_dump/ubifs_dump
+	ubifs_dump/ubifs_dump \
+	ubifs_unpack/ubifs_unpack
 JFFSX_BINS = \
 	mkfs.jffs2 sumtool jffs2reader jffs2dump
 NAND_BINS = \
@@ -135,6 +136,10 @@  LDFLAGS_ubifs_dump = $(ZLIBLDFLAGS) $(LZOLDFLAGS) $(UUIDLDFLAGS)
 LDLIBS_ubifs_dump = -lz -llzo2 -lm -luuid
 $(call mkdep,ubifs-utils/ubifs_dump/,ubifs_dump,,ubi-utils/libubi.a)
 
+obj-ubifs_unpack = $(UBIFS_LIBS) ../lib/list_sort.o ../lib/scan.o ../lib/lpt.o ../lib/master.o ../../lib/libcrc32.o replay.o index.o
+LDFLAGS_ubifs_unpack = $(ZLIBLDFLAGS) $(LZOLDFLAGS) $(UUIDLDFLAGS) -lz -llzo2
+$(call mkdep,ubifs-utils/ubifs_unpack/,ubifs_unpack,,ubi-utils/libubi.a)
+
 obj-mkfs.ubifs = $(UBIFS_LIBS)
 LDFLAGS_mkfs.ubifs = $(ZLIBLDFLAGS) $(LZOLDFLAGS) $(UUIDLDFLAGS)
 LDLIBS_mkfs.ubifs = -lz $(LZOLDLIBS) -lm -luuid
diff --git a/ubifs-utils/include/emubi.h b/ubifs-utils/include/emubi.h
index e06ba7d..a2883ce 100644
--- a/ubifs-utils/include/emubi.h
+++ b/ubifs-utils/include/emubi.h
@@ -73,6 +73,7 @@  struct emubi_ctx {
 };
 
 void emubi_print(struct emubi_ctx *ctx);
+void emubi_close(struct emubi_ctx *ctx);
 int emubi_scan(struct emubi_ctx *ctx);
 int emubi_open(struct emubi_ctx *ctx, char *file);
 int emubi_leb_read(struct emubi_ctx *ctx, unsigned int lnum, unsigned int offset,
diff --git a/ubifs-utils/include/list_sort.h b/ubifs-utils/include/list_sort.h
new file mode 100644
index 0000000..35afa54
--- /dev/null
+++ b/ubifs-utils/include/list_sort.h
@@ -0,0 +1,11 @@ 
+#ifndef _LINUX_LIST_SORT_H
+#define _LINUX_LIST_SORT_H
+
+#include "list.h"
+
+struct list_head;
+
+void list_sort(void *priv, struct list_head *head,
+              int (*cmp)(void *priv, struct list_head *a,
+                         struct list_head *b));
+#endif
diff --git a/ubifs-utils/include/ubifs.h b/ubifs-utils/include/ubifs.h
index f03601d..a12d71e 100644
--- a/ubifs-utils/include/ubifs.h
+++ b/ubifs-utils/include/ubifs.h
@@ -52,6 +52,9 @@ 
 /* Largest key size supported in this implementation */
 #define CUR_MAX_KEY_LEN UBIFS_SK_LEN
 
+/* Maximum possible inode number (only 32-bit inodes are supported now) */
+#define MAX_INUM 0xFFFFFFFF
+
 #define NONDATA_JHEADS_CNT 2
 /*
  * There is no notion of truncation key because truncation nodes do not exist
@@ -340,6 +343,8 @@  enum {
  * @old_idx_sz: size of index on flash
  * @lst: lprops statistics
  *
+ * @max_inode_sz: maximum possible inode size in bytes
+ *
  * @dead_wm: LEB dead space watermark
  * @dark_wm: LEB dark space watermark
  *
@@ -381,6 +386,11 @@  enum {
  * @lsave_offs: offset of LPT's save table
  * @lsave: LPT's save table
  * @lscan_lnum: LEB number of last LPT scan
+ *
+ * @replay_list: temporary list used during journal replay
+ * @replay_buds: list of buds to replay
+ * @cs_sqnum: sequence number of first node in the log (commit start node)
+ *
  * @emubi: emubi context for working with image files
  * @verbose: verbose mode enabled
  */
@@ -424,6 +434,8 @@  struct ubifs_info
 	unsigned long long old_idx_sz;
 	struct ubifs_lp_stats lst;
 
+	long long max_inode_sz;
+
 	int dead_wm;
 	int dark_wm;
 
@@ -474,6 +486,11 @@  struct ubifs_info
 	int max_idx_node_sz;
 
 	int max_znode_sz;
+
+	struct list_head replay_list;
+	struct list_head replay_buds;
+	unsigned long long cs_sqnum;
+
 	struct emubi_ctx *emubi;
 	int verbose;
 };
@@ -556,6 +573,23 @@  struct ubifs_branch *ubifs_idx_branch(const struct ubifs_info *c,
 				       (UBIFS_BRANCH_SZ + c->key_len) * bnum);
 }
 
+/**
+ * ubifs_next_log_lnum - switch to the next log LEB.
+ * @c: UBIFS file-system description object
+ * @lnum: current log LEB
+ *
+ * This helper function returns the log LEB number which goes next after LEB
+ * 'lnum'.
+ */
+static inline int ubifs_next_log_lnum(const struct ubifs_info *c, int lnum)
+{
+       lnum += 1;
+       if (lnum > UBIFS_LOG_LNUM + c->log_lebs - 1)
+               lnum = UBIFS_LOG_LNUM;
+
+       return lnum;
+}
+
 /* Callback used by the 'ubifs_lpt_scan_nolock()' function */
 typedef int (*ubifs_lpt_scan_callback)(struct ubifs_info *c,
 				       const struct ubifs_lprops *lprops,
diff --git a/ubifs-utils/lib/emubi.c b/ubifs-utils/lib/emubi.c
index 1c88eea..b4bf896 100644
--- a/ubifs-utils/lib/emubi.c
+++ b/ubifs-utils/lib/emubi.c
@@ -225,6 +225,26 @@  void emubi_print(struct emubi_ctx *ctx)
 	printf("\n\temubi mode: %s\n", (ctx->mode == EMUBI_MODE_MMAP) ? "mmap" : "file");
 }
 
+static void destroy_scan_lebs(struct list_head *scan_lebs)
+{
+       struct ubi_scan_leb *sleb, *ltmp;
+
+       if (!list_empty(scan_lebs)) {
+               list_for_each_entry_safe(sleb, ltmp, scan_lebs, l) {
+                       list_del(&sleb->l);
+                       free(sleb);
+               }
+       }
+
+}
+
+void emubi_close(struct emubi_ctx *ctx)
+{
+       free(ctx->eba);
+       free(ctx->ff_peb);
+       destroy_scan_lebs(&ctx->scan_lebs);
+}
+
 static int is_empty(struct emubi_ctx *ctx, char *pbuf, size_t len)
 {
 	return memcmp(pbuf, ctx->ff_peb, len) == 0;
diff --git a/ubifs-utils/lib/list_sort.c b/ubifs-utils/lib/list_sort.c
new file mode 100644
index 0000000..843d796
--- /dev/null
+++ b/ubifs-utils/lib/list_sort.c
@@ -0,0 +1,157 @@ 
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 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., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#define PROGRAM_NAME "list_sort"
+
+#include "list_sort.h"
+#include "common.h"
+#include "defs.h"
+
+#define MAX_LIST_LENGTH_BITS 20
+
+/*
+ * Returns a list organized in an intermediate format suited
+ * to chaining of merge() calls: null-terminated, no reserved or
+ * sentinel head node, "prev" links not maintained.
+ */
+static struct list_head *merge(void *priv,
+                               int (*cmp)(void *priv, struct list_head *a,
+                                       struct list_head *b),
+                               struct list_head *a, struct list_head *b)
+{
+       struct list_head head, *tail = &head;
+
+       while (a && b) {
+               /* if equal, take 'a' -- important for sort stability */
+               if ((*cmp)(priv, a, b) <= 0) {
+                       tail->next = a;
+                       a = a->next;
+               } else {
+                       tail->next = b;
+                       b = b->next;
+               }
+               tail = tail->next;
+       }
+       tail->next = a?:b;
+       return head.next;
+}
+
+/*
+ * Combine final list merge with restoration of standard doubly-linked
+ * list structure.  This approach duplicates code from merge(), but
+ * runs faster than the tidier alternatives of either a separate final
+ * prev-link restoration pass, or maintaining the prev links
+ * throughout.
+ */
+static void merge_and_restore_back_links(void *priv,
+                               int (*cmp)(void *priv, struct list_head *a,
+                                       struct list_head *b),
+                               struct list_head *head,
+                               struct list_head *a, struct list_head *b)
+{
+       struct list_head *tail = head;
+       u8 count = 0;
+
+       while (a && b) {
+               /* if equal, take 'a' -- important for sort stability */
+               if ((*cmp)(priv, a, b) <= 0) {
+                       tail->next = a;
+                       a->prev = tail;
+                       a = a->next;
+               } else {
+                       tail->next = b;
+                       b->prev = tail;
+                       b = b->next;
+               }
+               tail = tail->next;
+       }
+       tail->next = a ? : b;
+
+       do {
+               /*
+                * In worst cases this loop may run many iterations.
+                * Continue callbacks to the client even though no
+                * element comparison is needed, so the client's cmp()
+                * routine can invoke cond_resched() periodically.
+                */
+               if (unlikely(!(++count)))
+                       (*cmp)(priv, tail->next, tail->next);
+
+               tail->next->prev = tail;
+               tail = tail->next;
+       } while (tail->next);
+
+       tail->next = head;
+       head->prev = tail;
+}
+
+/**
+ * list_sort - sort a list
+ * @priv: private data, opaque to list_sort(), passed to @cmp
+ * @head: the list to sort
+ * @cmp: the elements comparison function
+ *
+ * This function implements "merge sort", which has O(nlog(n))
+ * complexity.
+ *
+ * The comparison function @cmp must return a negative value if @a
+ * should sort before @b, and a positive value if @a should sort after
+ * @b. If @a and @b are equivalent, and their original relative
+ * ordering is to be preserved, @cmp must return 0.
+ */
+void list_sort(void *priv, struct list_head *head,
+               int (*cmp)(void *priv, struct list_head *a,
+                       struct list_head *b))
+{
+       struct list_head *part[MAX_LIST_LENGTH_BITS+1]; /* sorted partial lists
+                                               -- last slot is a sentinel */
+       int lev;  /* index into part[] */
+       int max_lev = 0;
+       struct list_head *list;
+
+       if (list_empty(head))
+               return;
+
+       memset(part, 0, sizeof(part));
+
+       head->prev->next = NULL;
+       list = head->next;
+
+       while (list) {
+               struct list_head *cur = list;
+               list = list->next;
+               cur->next = NULL;
+
+               for (lev = 0; part[lev]; lev++) {
+                       cur = merge(priv, cmp, part[lev], cur);
+                       part[lev] = NULL;
+               }
+               if (lev > max_lev) {
+                       if (lev >= ARRAY_SIZE(part)-1) {
+                               printf("list too long for efficiency\n");
+                               lev--;
+                       }
+                       max_lev = lev;
+               }
+               part[lev] = cur;
+       }
+
+       for (lev = 0; lev < max_lev; lev++)
+               if (part[lev])
+                       list = merge(priv, cmp, part[lev], list);
+
+       merge_and_restore_back_links(priv, cmp, head, part[max_lev], list);
+}
diff --git a/ubifs-utils/ubifs_unpack/index.c b/ubifs-utils/ubifs_unpack/index.c
new file mode 100644
index 0000000..e60e32f
--- /dev/null
+++ b/ubifs-utils/ubifs_unpack/index.c
@@ -0,0 +1,648 @@ 
+/*
+ * Copyright (C) 2015 sigma star gmbh
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Richard Weinberger
+ *          David Gstir
+ */
+
+#define PROGRAM_NAME "index"
+
+#include "ubifs_unpack.h"
+
+
+struct ubifs_ino_list {
+	ino_t ino;
+	struct list_head *start;
+	struct list_head l;
+};
+
+static LIST_HEAD(leaf_index);
+static LIST_HEAD(ino_index);
+
+
+static void get_key(struct ubifs_branch *zbr, union ubifs_key *key)
+{
+	union ubifs_key *tmp_key;
+
+	tmp_key = (union ubifs_key *)zbr->key;
+	key->u32[0] = le32toh(tmp_key->j32[0]);
+	key->u32[1] = le32toh(tmp_key->j32[1]);
+}
+
+static void index_add_leaf(union ubifs_key *key, unsigned int lnum,
+			   unsigned int offs, unsigned int len)
+{
+	struct ubifs_leaf *ul = xmalloc(sizeof(*ul));
+	struct ubifs_leaf *last_ul = NULL;
+
+	if (!list_empty(&leaf_index))
+		last_ul = list_last_entry(&leaf_index, struct ubifs_leaf, l);
+
+	ul->lnum = lnum;
+	ul->offs = offs;
+	ul->len = len;
+	key_copy(key, &ul->key);
+
+	if (!last_ul || key->u32[0] != last_ul->key.u32[0]) {
+		struct ubifs_ino_list *uil = xmalloc(sizeof(*uil));
+		uil->ino = key_inum(key);
+		uil->start = leaf_index.prev;
+
+
+		list_add_tail(&uil->l, &ino_index);
+	}
+
+	list_add_tail(&ul->l, &leaf_index);
+}
+
+/*
+ * ubifs_destroy_index - Cleanup index
+ * @c: ubifs context
+ */
+void ubifs_destroy_index()
+{
+	struct ubifs_leaf *leaf, *ltmp;
+	struct ubifs_ino_list *uil, *itmp;
+
+	if (!list_empty(&leaf_index)) {
+		list_for_each_entry_safe(leaf, ltmp, &leaf_index, l) {
+			list_del(&leaf->l);
+			free(leaf);
+		}
+	}
+
+	if (!list_empty(&ino_index)) {
+		list_for_each_entry_safe(uil, itmp, &ino_index, l) {
+			list_del(&uil->l);
+			free(uil);
+		}
+	}
+}
+
+/*
+ * ubifs_load_index - Load full index from media
+ * @c: ubifs context
+ * @lnum: LEB where index node is located
+ * @offs: offset in LEB
+ * @len: len if index node
+ */
+void ubifs_load_index(struct ubifs_info *c, unsigned int lnum,
+		      unsigned int offs, unsigned int len)
+{
+	struct ubifs_idx_node *idx;
+	union ubifs_key tmp_key;
+	struct ubifs_ch ch;
+	unsigned int i;
+
+	idx = xmalloc(len);
+	get_idx_node(c, idx, lnum, offs, len);
+
+	for (i = 0; i < le16toh(idx->child_cnt); i++) {
+		struct ubifs_branch *br = get_branch(idx, i);
+		unsigned int br_lnum = le32toh(br->lnum);
+		unsigned int br_offs = le32toh(br->offs);
+		unsigned int br_len = le32toh(br->len);
+
+		if (le16toh(idx->level) == 0) {
+			get_key(br, &tmp_key);
+			index_add_leaf(&tmp_key, br_lnum, br_offs, br_len);
+		}
+
+		ubifs_leb_read(c, br_lnum, &ch, br_offs, sizeof(ch));
+		if (ch.node_type == UBIFS_IDX_NODE)
+			ubifs_load_index(c, br_lnum, br_offs, br_len);
+	}
+
+	free(idx);
+}
+
+static struct ubifs_ino_list *lookup_ino_list(ino_t ino)
+{
+	struct ubifs_ino_list *uil;
+
+	list_for_each_entry(uil, &ino_index, l) {
+		if (uil->ino == ino)
+			return uil;
+	}
+
+	return NULL;
+}
+
+static struct ubifs_leaf *lookup_leaf(union ubifs_key *key, int *exact)
+{
+	struct ubifs_leaf *ul, *prev_ul = NULL;
+	struct list_head *start = NULL;
+	struct ubifs_ino_list *uil;
+	int res;
+
+	*exact = 0;
+
+	uil = lookup_ino_list(key_inum(key));
+	if (!uil)
+		return NULL;
+
+	start = uil->start;
+	assert(start);
+
+	list_for_each_entry(ul, start, l) {
+		res = keys_cmp(&ul->key, key);
+		if (res == 0) {
+			*exact = 1;
+			return ul;
+		}
+
+		if (res > 0) {
+			if (is_hash_key(key))
+				return prev_ul;
+			else
+				return NULL;
+		}
+
+		prev_ul = ul;
+	}
+
+	return NULL;
+}
+
+/*
+ * ubifs_lookup_ino_node - find INO node
+ * @c: ubifs context
+ * @ino: inode number
+ *
+ * Returns pointer to found ubifs_ino_node or NULL, if node was not found.
+ */
+struct ubifs_ino_node *ubifs_lookup_ino_node(struct ubifs_info *c, ino_t ino)
+{
+	struct ubifs_ino_node *i;
+	struct ubifs_leaf *leaf;
+	union ubifs_key key;
+	int exact;
+
+	ino_key_init(&key, ino);
+
+	leaf = lookup_leaf(&key, &exact);
+	if (!leaf || !exact)
+		return NULL;
+
+	i = xmalloc(leaf->len);
+	get_ino_node(c, i, leaf->lnum, leaf->offs, leaf->len);
+
+	return i;
+}
+
+/*
+ * ubifs_lookup_data_node - find DATA node
+ * @c: ubifs context
+ * @ino: inode number
+ * @block: block number
+ *
+ * Returns pointer to found ubifs_data_node or NULL, if node was not found.
+ */
+struct ubifs_data_node *ubifs_lookup_data_node(struct ubifs_info *c, ino_t ino,
+					       unsigned int block)
+{
+	struct ubifs_data_node *d;
+	struct ubifs_leaf *leaf;
+	union ubifs_key key;
+	int exact;
+
+	data_key_init(&key, ino, block);
+
+	leaf = lookup_leaf(&key, &exact);
+	if (!leaf || !exact)
+		return NULL;
+
+	d = xmalloc(leaf->len);
+	get_data_node(c, d, leaf->lnum, leaf->offs, leaf->len);
+
+	return d;
+}
+
+/*
+ * ubifs_next_leaf - Find the next leaf node after @ul which belongs to @ino.
+ * @ul: current leaf
+ * @ino: ino of current leaf
+ *
+ * If the end is reached, NULL is returned.
+ * If @ul is NULL, the first leaf node matching @ino is returned.
+ */
+struct ubifs_leaf *ubifs_next_leaf(struct ubifs_leaf *ul, ino_t ino)
+{
+	struct ubifs_leaf *next = NULL;
+	struct ubifs_ino_list *uil;
+
+	if (ul) {
+		next = list_next_entry(ul, l);
+		if (key_inum(&next->key) != ino)
+			next = NULL;
+	} else {
+		/* we don't have a leaf yet, so find the first */
+		uil = lookup_ino_list(ino);
+		if (uil && uil->start)
+			next = list_first_entry(uil->start, struct ubifs_leaf, l);
+	}
+
+	return next;
+}
+
+/*
+ * match_name - Compare @nm with name of node @leaf.
+ * @c: ubifs context
+ * @leaf: leaf node which name we want to match. It must use a hash key
+ * @nm: required name to match on
+ *
+ * This helps to resolve collisions for hash keys where duplicate keys are
+ * possible.
+ * Returns %-1 on error, %0 on full match and %1 on no match.
+ */
+static int match_name(struct ubifs_info *c, struct ubifs_leaf *leaf,
+		      struct qstr *nm)
+{
+	int len, ret;
+	struct ubifs_dent_node *dent;
+	assert(is_hash_key(&leaf->key));
+
+	if (!nm->name)
+		return -1;
+
+	dent = xmalloc(leaf->len);
+	get_dent_node(c, dent, leaf->lnum, leaf->offs, leaf->len);
+
+	len = (nm->len > le16toh(dent->nlen)) ? nm->len : dent->nlen;
+	ret = (memcmp(nm->name, dent->name, len) == 0) ? 0 : 1;
+
+	free(dent);
+
+	return ret;
+}
+
+/*
+ * index_remove_leaf - Safely remove a leaf from the skip list
+ * @leaf: the leaf to remove
+ * @uil: the ino_list entry. If NULL, will be search.
+ *
+ * Updates and removes ino_list entries accordingly.
+ * Returns %0 on success, %1 when last leaf was removed successfully and a
+ * value < 0 on error
+ */
+static int index_remove_leaf(struct ubifs_leaf *leaf, struct ubifs_ino_list *uil)
+{
+	struct ubifs_ino_list *next_uil;
+
+	if (!uil)
+		uil = lookup_ino_list(key_inum(&leaf->key));
+
+	if (!uil)
+		return -ENOENT;
+
+	/* fix ->start of next ino_list if we remove last node for this ino */
+	if (!list_is_last(&uil->l, &ino_index)) {
+		next_uil = list_next_entry(uil, l);
+		if (next_uil->start == &leaf->l)
+			next_uil->start = leaf->l.prev;
+	}
+
+	list_del(&leaf->l);
+	free(leaf);
+
+	/* if this was the last leaf, remove ino_list entry too */
+	leaf = list_first_entry(uil->start, struct ubifs_leaf, l);
+	if (key_inum(&leaf->key) != uil->ino) {
+		list_del(&uil->l);
+		free(uil);
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * ubifs_remove_inode - Remove all nodes for @ino from index
+ * @c: ubifs context
+ * @ino: the inode number to remove
+ *
+ * Returns %0 on success or when @ino was not found, %-1 on error
+ */
+int ubifs_remove_inode(struct ubifs_info *c, ino_t ino)
+{
+	struct ubifs_ino_list *uil;
+	struct ubifs_leaf *ul;
+	int err = 0;
+
+	verbose(c->verbose, "remove all nodes of ino %lu", ino);
+
+	uil = lookup_ino_list(ino);
+	if (!uil)
+		goto out;
+
+	/* remove all leafs of this inode */
+	ul = list_first_entry(uil->start, struct ubifs_leaf, l);
+	while (key_inum(&ul->key) == ino) {
+		err = index_remove_leaf(ul, uil);
+		if (err) {
+			if (err == 1)
+				/* last entry was removed, we're done */
+				err = 0;
+			goto out;
+		}
+
+		ul = list_first_entry(uil->start, struct ubifs_leaf, l);
+	}
+
+out:
+	return err;
+}
+
+/*
+ * ubifs_remove_node_nm - Remove node with @key from index
+ * @c: ubifs context
+ * @key: the key of the node to remove
+ * @nm: when @key is a hash key, this file name is used to resolve collisions
+ *
+ * Returns %0 on success or when no matching node can be found in the index.
+ */
+int ubifs_remove_node_nm(struct ubifs_info *c, union ubifs_key *key,
+			 struct qstr *nm)
+{
+	ino_t ino;
+	struct ubifs_leaf *ul = NULL;
+	int match = -1, err = 0;
+
+	assert(c);
+	assert(key);
+	assert(nm);
+
+	ino = key_inum(key);
+	verbose(c->verbose, "remove node 0x%lx (ino %lu, type %d blk/hash %d, hash_key %d name %s)",
+		key->u64[0], ino, key_type(key), key_block(key), is_hash_key(key), nm->name);
+
+	while ((ul = ubifs_next_leaf(ul, ino))) {
+		match = keys_cmp(&ul->key, key);
+
+		/* no need to search further */
+		if (match > 0) {
+			normsg("ignoring remove of non-existant leaf from index (ino %lu)", ino);
+			break;
+		}
+
+		if (is_hash_key(key) && match == 0) {
+			match = match_name(c, ul, nm);
+			if (match < 0) {
+				err = match;
+				errmsg("name matching failed for key 0x%lx", key->u64[0]);
+				goto out;
+			}
+		}
+
+		if (match == 0) {
+			err = index_remove_leaf(ul, NULL);
+			if (err < 0)
+				errmsg("failed to remove leaf (ino %lu type %d)", ino, key_type(key));
+			/* there can only be a single match */
+			break;
+		}
+	}
+
+out:
+	return err;
+}
+
+/*
+ * ubifs_remove_node - Remove node with @key from index
+ * @c: ubifs context
+ * @key: key of node to remove
+ *
+ * This equivalent to ubifs_remove_node_nm() however, it can only be used for
+ * keys which will never collide.
+ */
+int ubifs_remove_node(struct ubifs_info *c, union ubifs_key *key)
+{
+	struct qstr empty = { .name = NULL };
+
+	if (!is_hash_key(key)) {
+		errmsg("requested key is not hash key!\n");
+		return -EINVAL;
+	}
+
+	return ubifs_remove_node_nm(c, key, &empty);
+}
+
+/**
+ * key_in_range - determine if a key falls within a range of keys.
+ * @key: key to check
+ * @from_key: lowest key in range
+ * @to_key: highest key in range
+ *
+ * This function returns %1 if the key is in range and %0 otherwise.
+ */
+static int key_in_range(union ubifs_key *key, union ubifs_key *from_key,
+			union ubifs_key *to_key)
+{
+	if (keys_cmp(key, from_key) < 0)
+		return 0;
+	if (keys_cmp(key, to_key) > 0)
+		return 0;
+	return 1;
+}
+
+/*
+ * ubifs_remove_node_range - Remove all nodes within the given key range
+ * @c: ubifs context
+ * @from_key: first key in range (min)
+ * @to_key: last key in range to remove (max)
+ *
+ * The keys must be for the same inode. No hash keys are supported.
+ */
+int ubifs_remove_node_range(struct ubifs_info *c, union ubifs_key *from_key,
+			    union ubifs_key *to_key)
+{
+	ino_t ino;
+	struct ubifs_ino_list *uil;
+	struct ubifs_leaf *ul, *rm_ul;
+	int err = 0;
+
+	ino = key_inum(from_key);
+	verbose(c->verbose, "remove node range for ino %lu type %d blk %d - %d",
+		ino, key_type(from_key), key_block(from_key), key_block(to_key));
+
+	uil = lookup_ino_list(ino);
+	if (!uil)
+		goto out;
+
+	ul = list_first_entry(uil->start, struct ubifs_leaf, l);
+	while (key_inum(&ul->key) == ino) {
+		rm_ul = ul;
+		ul = list_next_entry(ul, l);
+		if (key_in_range(&rm_ul->key, from_key, to_key)) {
+			err = index_remove_leaf(rm_ul, uil);
+			if (err) {
+				if (err == 1)
+					/* last entry was removed, we're done */
+					err = 0;
+				goto out;
+			}
+		}
+	}
+
+out:
+	return err;
+}
+
+static void index_insert_ino(struct ubifs_ino_list *uil)
+{
+	struct ubifs_ino_list *i;
+
+	list_for_each_entry(i, &ino_index, l) {
+		if (i->ino > uil->ino)
+			break;
+	}
+
+	if (&i->l != &ino_index) {
+		list_add_tail(&uil->l, &i->l);
+		uil->start = i->start;
+	} else {
+		list_add_tail(&uil->l, &ino_index);
+		uil->start = leaf_index.prev;
+	}
+}
+
+/*
+ * index_insert_leaf - Insert new leaf at correct position in sorted leaf list
+ * @c: ubifs context
+ * @key: key to insert
+ * @lnum: LEB number of new node
+ * @offs: offset in LEB @lnum
+ * @len: length of new node
+ * @nm: file name to resolve key collisions
+ *
+ * In case a corresponding leaf with the same key already exists, this leaf is
+ * just updated and no new leaf is created.
+ * Retruns %0 on succces, or a value < 0 on error.
+ */
+static int index_insert_leaf(struct ubifs_info *c,union ubifs_key *key,
+			     unsigned int lnum, unsigned int offs, unsigned int len,
+			     struct qstr *nm)
+{
+	int match, err, new_ino = 0;
+	ino_t ino = key_inum(key);
+	struct ubifs_ino_list *uil;
+	struct ubifs_leaf *leaf, *ul = NULL;
+	struct list_head *prev;
+
+	verbose(c->verbose, "add replay inode %lu on LEB %d:%d type %d", ino, lnum, offs, key_type(key));
+
+	uil = lookup_ino_list(ino);
+	if (!uil) {
+		uil = xcalloc(1, sizeof(*uil));
+		uil->ino = ino;
+		new_ino = 1;
+		goto insert;
+	}
+
+	/* find position in leaf list */
+	assert(uil->start);
+	prev = uil->start;
+	ul = list_first_entry(uil->start, struct ubifs_leaf, l);
+	do {
+		match = keys_cmp(&ul->key, key);
+
+		/* for hash keys with colliding key values, we insert the new
+		 * leaf right after the colliding ones */
+		if (match > 0)
+			break;
+
+		/* we have a collision, so check the name to
+		 * ensure this is the node we want */
+		if (is_hash_key(key) && match == 0) {
+			match = match_name(c, ul, nm);
+			if (match < 0) {
+				err = match;
+				goto out;
+			}
+		}
+
+		/* just update the existing leaf */
+		if (match == 0) {
+			ul->lnum = lnum;
+			ul->offs = offs;
+			ul->len = len;
+			err = 0;
+			goto out;
+		}
+
+		prev = &ul->l;
+		ul = ubifs_next_leaf(ul, ino);
+	} while (ul);
+
+insert:
+	leaf = xmalloc(sizeof(*leaf));
+	leaf->lnum = lnum;
+	leaf->offs = offs;
+	leaf->len = len;
+	key_copy(key, &leaf->key);
+
+	if (new_ino) {
+		index_insert_ino(uil);
+		prev = uil->start;
+	}
+
+	list_add(&leaf->l, prev);
+
+	/* move skip pointer of next ino when appending to last leaf of ino
+	 * list */
+	if (!ul && !list_is_last(&uil->l, &ino_index)) {
+		uil = list_next_entry(uil, l);
+		uil->start = &leaf->l;
+	}
+	err = 0;
+out:
+	return err;
+}
+
+/*
+ * ubifs_add_node - Add new node to index
+ * @c: ubifs context
+ * @key: key of new node
+ * @lnum: LEB where node is located
+ * @off: offset in LEB where new node starts
+ * @len: length of new node
+ *
+ * This only works for non-hash keys.
+ * Returns %0 on success, or a value < 0 on error.
+ */
+int ubifs_add_node(struct ubifs_info *c, union ubifs_key *key, int lnum,
+		   int offs, unsigned int len)
+{
+	struct qstr empty = { .name = NULL };
+	return index_insert_leaf(c, key, lnum, offs, len, &empty);
+}
+
+/*
+ * ubifs_add_node_nm - Add new node to index with collision resolution
+ * @c: ubifs context
+ * @key: key of new node
+ * @lnum: LEB where new node is located
+ * @offs: offset in LEB where node starts
+ * @len: length of new node
+ * @nm: file name used to resolve collision
+ *
+ * Returns %0 on success, or a value < 0 on error.
+ */
+int ubifs_add_node_nm(struct ubifs_info *c, union ubifs_key *key, int lnum,
+		      int offs, unsigned int len, struct qstr *nm)
+{
+	return index_insert_leaf(c, key, lnum, offs, len, nm);
+}
diff --git a/ubifs-utils/ubifs_unpack/replay.c b/ubifs-utils/ubifs_unpack/replay.c
new file mode 100644
index 0000000..a5fbce9
--- /dev/null
+++ b/ubifs-utils/ubifs_unpack/replay.c
@@ -0,0 +1,865 @@ 
+/*
+ * This file is a modified copy of fs/ubifs/replay.c from the Linux Kernel UBIFS.
+ *
+ * Copyright (C) 2006-2008 Nokia Corporation.
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Adrian Hunter
+ *          Artem Bityutskiy (Битюцкий Артём)
+ */
+/*
+ * Modifications for mtd-utils.
+ *
+ * Copyright (C) 2015 sigma star gmbh
+ *
+ * Authors: Richard Weinberger
+ *          David Gstir
+ */
+
+/*
+ * This file contains journal replay code. It runs when the file-system is being
+ * mounted and requires no locking.
+ *
+ * The larger is the journal, the longer it takes to scan it, so the longer it
+ * takes to mount UBIFS. This is why the journal has limited size which may be
+ * changed depending on the system requirements. But a larger journal gives
+ * faster I/O speed because it writes the index less frequently. So this is a
+ * trade-off. Also, the journal is indexed by the in-memory index (TNC), so the
+ * larger is the journal, the more memory its index may consume.
+ */
+
+#define PROGRAM_NAME "replay"
+
+#include "ubifs_unpack.h"
+#include "key.h"
+#include "list_sort.h"
+
+/**
+ * struct replay_entry - replay list entry.
+ * @lnum: logical eraseblock number of the node
+ * @offs: node offset
+ * @len: node length
+ * @deletion: non-zero if this entry corresponds to a node deletion
+ * @sqnum: node sequence number
+ * @list: links the replay list
+ * @key: node key
+ * @nm: directory entry name
+ * @old_size: truncation old size
+ * @new_size: truncation new size
+ *
+ * The replay process first scans all buds and builds the replay list, then
+ * sorts the replay list in nodes sequence number order, and then inserts all
+ * the replay entries to the TNC.
+ */
+struct replay_entry {
+	int lnum;
+	int offs;
+	int len;
+	unsigned int deletion:1;
+	unsigned long long sqnum;
+	struct list_head list;
+	union ubifs_key key;
+	union {
+		struct qstr nm;
+		struct {
+			loff_t old_size;
+			loff_t new_size;
+		};
+	};
+};
+
+/**
+ * struct ubifs_bud - bud logical eraseblock used for replay.
+ * @lnum: logical eraseblock number
+ * @start: where the (uncommitted) bud data starts
+ * @jhead: journal head number this bud belongs to
+ * @sqnum: reference node sequence number
+ * @list: link in the list buds belonging to the same journal head
+ */
+struct ubifs_bud {
+	int lnum;
+	int start;
+	int jhead;
+	unsigned long long sqnum;
+	struct list_head list;
+};
+
+/**
+ * ubifs_search_bud - search bud LEB.
+ * @c: UBIFS file-system description object
+ * @lnum: logical eraseblock number to search
+ *
+ * This function searches bud LEB @lnum. Returns bud description object in case
+ * of success and %NULL if there is no bud with this LEB number.
+ */
+static struct ubifs_bud *ubifs_search_bud(struct ubifs_info *c, int lnum)
+{
+	struct ubifs_bud *bud;
+
+	list_for_each_entry(bud, &c->replay_buds, list) {
+		if (lnum == bud->lnum)
+			return bud;
+	}
+
+	return NULL;
+}
+/**
+ * trun_remove_range - apply a replay entry for a truncation to the TNC.
+ * @c: UBIFS file-system description object
+ * @r: replay entry of truncation
+ */
+static int trun_remove_range(struct ubifs_info *c, struct replay_entry *r)
+{
+	unsigned min_blk, max_blk;
+	union ubifs_key min_key, max_key;
+	ino_t ino;
+
+	min_blk = r->new_size / UBIFS_BLOCK_SIZE;
+	if (r->new_size & (UBIFS_BLOCK_SIZE - 1))
+		min_blk += 1;
+
+	max_blk = r->old_size / UBIFS_BLOCK_SIZE;
+	if ((r->old_size & (UBIFS_BLOCK_SIZE - 1)) == 0)
+		max_blk -= 1;
+
+	ino = key_inum(&r->key);
+
+	data_key_init(&min_key, ino, min_blk);
+	data_key_init(&max_key, ino, max_blk);
+
+	return ubifs_remove_node_range(c, &min_key, &max_key);
+}
+
+/**
+ * apply_replay_entry - apply a replay entry to the TNC.
+ * @c: UBIFS file-system description object
+ * @r: replay entry to apply
+ *
+ * Apply a replay entry to the TNC.
+ */
+static int apply_replay_entry(struct ubifs_info *c, struct replay_entry *r)
+{
+	int err;
+
+	verbose(c->verbose, "LEB %d:%d len %d deletion %d sqnum %llu key 0x%lx",
+		 r->lnum, r->offs, r->len, r->deletion, r->sqnum, r->key.u64[0]);
+
+	if (is_hash_key(&r->key)) {
+		if (r->deletion)
+			err = ubifs_remove_node_nm(c, &r->key, &r->nm);
+		else
+			err = ubifs_add_node_nm(c, &r->key, r->lnum, r->offs,
+					        r->len, &r->nm);
+	} else {
+		if (r->deletion)
+			switch (key_type(&r->key)) {
+			case UBIFS_INO_KEY:
+			{
+				ino_t inum = key_inum(&r->key);
+
+				err = ubifs_remove_inode(c, inum);
+				break;
+			}
+			case UBIFS_TRUN_KEY:
+				err = trun_remove_range(c, r);
+				break;
+			default:
+				err = ubifs_remove_node(c, &r->key);
+				break;
+			}
+		else
+			err = ubifs_add_node(c, &r->key, r->lnum, r->offs, r->len);
+		if (err)
+			return err;
+	}
+
+	return err;
+}
+
+/**
+ * replay_entries_cmp - compare 2 replay entries.
+ * @priv: UBIFS file-system description object
+ * @a: first replay entry
+ * @a: second replay entry
+ *
+ * This is a comparios function for 'list_sort()' which compares 2 replay
+ * entries @a and @b by comparing their sequence numer.  Returns %1 if @a has
+ * greater sequence number and %-1 otherwise.
+ */
+static int replay_entries_cmp(void *priv __attribute__((unused)),
+			      struct list_head *a, struct list_head *b)
+{
+	struct replay_entry *ra, *rb;
+
+	if (a == b)
+		return 0;
+
+	ra = list_entry(a, struct replay_entry, list);
+	rb = list_entry(b, struct replay_entry, list);
+	ubifs_assert(ra->sqnum != rb->sqnum);
+	if (ra->sqnum > rb->sqnum)
+		return 1;
+	return -1;
+}
+
+/**
+ * apply_replay_list - apply the replay list to the TNC.
+ * @c: UBIFS file-system description object
+ *
+ * Apply all entries in the replay list to the TNC. Returns zero in case of
+ * success and a negative error code in case of failure.
+ */
+static int apply_replay_list(struct ubifs_info *c)
+{
+	struct replay_entry *r;
+	int err;
+
+	list_sort(c, &c->replay_list, &replay_entries_cmp);
+
+	list_for_each_entry(r, &c->replay_list, list) {
+
+		err = apply_replay_entry(c, r);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+/**
+ * destroy_replay_list - destroy the replay.
+ * @c: UBIFS file-system description object
+ *
+ * Destroy the replay list.
+ */
+static void destroy_replay_list(struct ubifs_info *c)
+{
+	struct replay_entry *r, *tmp;
+
+	list_for_each_entry_safe(r, tmp, &c->replay_list, list) {
+		if (is_hash_key(&r->key))
+			kfree(r->nm.name);
+		list_del(&r->list);
+		kfree(r);
+	}
+}
+
+/**
+ * insert_node - insert a node to the replay list
+ * @c: UBIFS file-system description object
+ * @lnum: node logical eraseblock number
+ * @offs: node offset
+ * @len: node length
+ * @key: node key
+ * @sqnum: sequence number
+ * @deletion: non-zero if this is a deletion
+ * @used: number of bytes in use in a LEB
+ * @old_size: truncation old size
+ * @new_size: truncation new size
+ *
+ * This function inserts a scanned non-direntry node to the replay list. The
+ * replay list contains @struct replay_entry elements, and we sort this list in
+ * sequence number order before applying it. The replay list is applied at the
+ * very end of the replay process. Since the list is sorted in sequence number
+ * order, the older modifications are applied first. This function returns zero
+ * in case of success and a negative error code in case of failure.
+ */
+static int insert_node(struct ubifs_info *c, int lnum, int offs, int len,
+		       union ubifs_key *key, unsigned long long sqnum,
+		       int deletion, int *used, loff_t old_size,
+		       loff_t new_size)
+{
+	struct replay_entry *r;
+
+	verbose(c->verbose, "add LEB %d:%d, key 0x%lx", lnum, offs, key->u64[0]);
+
+	if (key_inum(key) >= c->highest_inum)
+		c->highest_inum = key_inum(key);
+
+	r = calloc(1, sizeof(struct replay_entry));
+	if (!r)
+		return -ENOMEM;
+
+	if (!deletion)
+		*used += ALIGN(len, 8);
+	r->lnum = lnum;
+	r->offs = offs;
+	r->len = len;
+	r->deletion = !!deletion;
+	r->sqnum = sqnum;
+	key_copy(key, &r->key);
+	r->old_size = old_size;
+	r->new_size = new_size;
+
+	list_add_tail(&r->list, &c->replay_list);
+	return 0;
+}
+
+/**
+ * insert_dent - insert a directory entry node into the replay list.
+ * @c: UBIFS file-system description object
+ * @lnum: node logical eraseblock number
+ * @offs: node offset
+ * @len: node length
+ * @key: node key
+ * @name: directory entry name
+ * @nlen: directory entry name length
+ * @sqnum: sequence number
+ * @deletion: non-zero if this is a deletion
+ * @used: number of bytes in use in a LEB
+ *
+ * This function inserts a scanned directory entry node or an extended
+ * attribute entry to the replay list. Returns zero in case of success and a
+ * negative error code in case of failure.
+ */
+static int insert_dent(struct ubifs_info *c, int lnum, int offs, int len,
+		       union ubifs_key *key, const char *name, int nlen,
+		       unsigned long long sqnum, int deletion, int *used)
+{
+	struct replay_entry *r;
+	char *nbuf;
+
+	verbose(c->verbose, "add LEB %d:%d, key 0x%lx ", lnum, offs, key->u64[0]);
+	if (key_inum(key) >= c->highest_inum)
+		c->highest_inum = key_inum(key);
+
+	r = calloc(1, sizeof(struct replay_entry));
+	if (!r)
+		return -ENOMEM;
+
+	nbuf = malloc(nlen + 1);
+	if (!nbuf) {
+		free(r);
+		return -ENOMEM;
+	}
+
+	if (!deletion)
+		*used += ALIGN(len, 8);
+	r->lnum = lnum;
+	r->offs = offs;
+	r->len = len;
+	r->deletion = !!deletion;
+	r->sqnum = sqnum;
+	key_copy(key, &r->key);
+	r->nm.len = nlen;
+	memcpy(nbuf, name, nlen);
+	nbuf[nlen] = '\0';
+	r->nm.name = nbuf;
+
+	list_add_tail(&r->list, &c->replay_list);
+	return 0;
+}
+
+/**
+ * ubifs_validate_entry - validate directory or extended attribute entry node.
+ * @c: UBIFS file-system description object
+ * @dent: the node to validate
+ *
+ * This function validates directory or extended attribute entry node @dent.
+ * Returns zero if the node is all right and a %-EINVAL if not.
+ */
+int ubifs_validate_entry(struct ubifs_info *c __attribute__((unused)),
+			 const struct ubifs_dent_node *dent)
+{
+	int key_type = key_type_flash(dent->key);
+	int nlen = le16_to_cpu(dent->nlen);
+
+	if (le32_to_cpu(dent->ch.len) != nlen + UBIFS_DENT_NODE_SZ + 1 ||
+	    dent->type >= UBIFS_ITYPES_CNT ||
+	    nlen > UBIFS_MAX_NLEN || dent->name[nlen] != 0 ||
+	    strnlen((const char *)dent->name, nlen) != nlen ||
+	    le64_to_cpu(dent->inum) > MAX_INUM) {
+		errmsg("bad %s node", key_type == UBIFS_DENT_KEY ?
+			  "directory entry" : "extended attribute entry");
+		return -EINVAL;
+	}
+
+	if (key_type != UBIFS_DENT_KEY && key_type != UBIFS_XENT_KEY) {
+		errmsg("bad key type %d", key_type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * replay_bud - replay a bud logical eraseblock.
+ * @c: UBIFS file-system description object
+ * @b: bud entry which describes the bud
+ *
+ * This function replays bud @bud, recovers it if needed, and adds all nodes
+ * from this bud to the replay list. Returns zero in case of success and a
+ * negative error code in case of failure.
+ */
+static int replay_bud(struct ubifs_info *c, struct ubifs_bud *b)
+{
+	int err = 0, used = 0, lnum = b->lnum, offs = b->start;
+	struct ubifs_scan_leb *sleb;
+	struct ubifs_scan_node *snod;
+
+	verbose(c->verbose, "replay bud LEB %d, head %d, offs %d",
+		lnum, b->jhead, offs);
+
+	// TODO LEB recovery for last bud of each journal head
+	//if (is_last)
+		/*
+		 * Recover only last LEBs in the journal heads, because power
+		 * cuts may cause corruptions only in these LEBs, because only
+		 * these LEBs could possibly be written to at the power cut
+		 * time.
+		 */
+		//sleb = ubifs_recover_leb(c, lnum, offs, c->sbuf, b->bud->jhead);
+	//else
+		sleb = ubifs_scan(c, lnum, offs, c->sbuf, 0);
+
+	if (IS_ERR(sleb))
+		return PTR_ERR(sleb);
+
+	/*
+	 * The bud does not have to start from offset zero - the beginning of
+	 * the 'lnum' LEB may contain previously committed data. One of the
+	 * things we have to do in replay is to correctly update lprops with
+	 * newer information about this LEB.
+	 *
+	 * At this point lprops thinks that this LEB has 'c->leb_size - offs'
+	 * bytes of free space because it only contain information about
+	 * committed data.
+	 *
+	 * But we know that real amount of free space is 'c->leb_size -
+	 * sleb->endpt', and the space in the 'lnum' LEB between 'offs' and
+	 * 'sleb->endpt' is used by bud data. We have to correctly calculate
+	 * how much of these data are dirty and update lprops with this
+	 * information.
+	 *
+	 * The dirt in that LEB region is comprised of padding nodes, deletion
+	 * nodes, truncation nodes and nodes which are obsoleted by subsequent
+	 * nodes in this LEB. So instead of calculating clean space, we
+	 * calculate used space ('used' variable).
+	 */
+
+	list_for_each_entry(snod, &sleb->nodes, list) {
+		int deletion = 0;
+
+		// TODO remove watermark check for unpack
+		if (snod->sqnum >= SQNUM_WATERMARK) {
+			errmsg("file system's life ended");
+			goto out_dump;
+		}
+
+		if (snod->sqnum > c->max_sqnum)
+			c->max_sqnum = snod->sqnum;
+
+		switch (snod->type) {
+		case UBIFS_INO_NODE:
+		{
+			struct ubifs_ino_node *ino = snod->node;
+			loff_t new_size = le64_to_cpu(ino->size);
+
+			if (le32_to_cpu(ino->nlink) == 0)
+				deletion = 1;
+			err = insert_node(c, lnum, snod->offs, snod->len,
+					  &snod->key, snod->sqnum, deletion,
+					  &used, 0, new_size);
+			break;
+		}
+		case UBIFS_DATA_NODE:
+		{
+			struct ubifs_data_node *dn = snod->node;
+			loff_t new_size = le32_to_cpu(dn->size) +
+					  key_block(&snod->key) *
+					  UBIFS_BLOCK_SIZE;
+
+			err = insert_node(c, lnum, snod->offs, snod->len,
+					  &snod->key, snod->sqnum, deletion,
+					  &used, 0, new_size);
+			break;
+		}
+		case UBIFS_DENT_NODE:
+		case UBIFS_XENT_NODE:
+		{
+			struct ubifs_dent_node *dent = snod->node;
+
+			err = ubifs_validate_entry(c, dent);
+			if (err)
+				goto out_dump;
+
+			err = insert_dent(c, lnum, snod->offs, snod->len,
+					  &snod->key, (const char *)dent->name,
+					  le16_to_cpu(dent->nlen), snod->sqnum,
+					  !le64_to_cpu(dent->inum), &used);
+			break;
+		}
+		case UBIFS_TRUN_NODE:
+		{
+			struct ubifs_trun_node *trun = snod->node;
+			loff_t old_size = le64_to_cpu(trun->old_size);
+			loff_t new_size = le64_to_cpu(trun->new_size);
+			union ubifs_key key;
+
+			/* Validate truncation node */
+			if (old_size < 0 || old_size > c->max_inode_sz ||
+			    new_size < 0 || new_size > c->max_inode_sz ||
+			    old_size <= new_size) {
+				errmsg("bad truncation node");
+				goto out_dump;
+			}
+
+			/*
+			 * Create a fake truncation key just to use the same
+			 * functions which expect nodes to have keys.
+			 */
+			trun_key_init(c, &key, le32_to_cpu(trun->inum));
+			err = insert_node(c, lnum, snod->offs, snod->len,
+					  &key, snod->sqnum, 1, &used,
+					  old_size, new_size);
+			break;
+		}
+		default:
+			errmsg("unexpected node type %d in bud LEB %d:%d",
+				  snod->type, lnum, snod->offs);
+			err = -EINVAL;
+			goto out_dump;
+		}
+		if (err)
+			goto out;
+	}
+
+	assert(ubifs_search_bud(c, lnum));
+	assert(sleb->endpt - offs >= used);
+	assert(sleb->endpt % c->min_io_size == 0);
+
+out:
+	ubifs_scan_destroy(sleb);
+	return err;
+
+out_dump:
+	errmsg("bad node is at LEB %d:%d", lnum, snod->offs);
+	ubifs_scan_destroy(sleb);
+	return -EINVAL;
+}
+
+/**
+ * replay_buds - replay all buds.
+ * @c: UBIFS file-system description object
+ *
+ * This function returns zero in case of success and a negative error code in
+ * case of failure.
+ */
+static int replay_buds(struct ubifs_info *c)
+{
+	struct ubifs_bud *bud;
+	int err;
+	unsigned long long prev_sqnum = 0;
+
+	list_for_each_entry(bud, &c->replay_buds, list) {
+		err = replay_bud(c, bud);
+		if (err)
+			return err;
+
+		assert(bud->sqnum > prev_sqnum);
+		prev_sqnum = bud->sqnum;
+	}
+
+	return 0;
+}
+
+/**
+ * destroy_bud_list - destroy the list of buds to replay.
+ * @c: UBIFS file-system description object
+ */
+static void destroy_bud_list(struct ubifs_info *c)
+{
+	struct ubifs_bud *b;
+
+	while (!list_empty(&c->replay_buds)) {
+		b = list_entry(c->replay_buds.next, struct ubifs_bud, list);
+		list_del(&b->list);
+		kfree(b);
+	}
+}
+
+/**
+ * add_replay_bud - add a bud to the list of buds to replay.
+ * @c: UBIFS file-system description object
+ * @lnum: bud logical eraseblock number to replay
+ * @offs: bud start offset
+ * @jhead: journal head to which this bud belongs
+ * @sqnum: reference node sequence number
+ *
+ * This function returns zero in case of success and a negative error code in
+ * case of failure.
+ */
+static int add_replay_bud(struct ubifs_info *c, int lnum, int offs, int jhead,
+			  unsigned long long sqnum)
+{
+	struct ubifs_bud *bud;
+
+	verbose(c->verbose, "add replay bud LEB %d:%d, head %d", lnum, offs, jhead);
+
+	bud = malloc(sizeof(struct ubifs_bud));
+	if (!bud)
+		return -ENOMEM;
+
+	bud->lnum = lnum;
+	bud->start = offs;
+	bud->jhead = jhead;
+	bud->sqnum = sqnum;
+	list_add_tail(&bud->list, &c->replay_buds);
+
+	return 0;
+}
+
+/**
+ * validate_ref - validate a reference node.
+ * @c: UBIFS file-system description object
+ * @ref: the reference node to validate
+ * @ref_lnum: LEB number of the reference node
+ * @ref_offs: reference node offset
+ *
+ * This function returns %1 if a bud reference already exists for the LEB. %0 is
+ * returned if the reference node is new, otherwise %-EINVAL is returned if
+ * validation failed.
+ */
+static int validate_ref(struct ubifs_info *c, const struct ubifs_ref_node *ref)
+{
+	struct ubifs_bud *bud;
+	int lnum = le32_to_cpu(ref->lnum);
+	unsigned int offs = le32_to_cpu(ref->offs);
+	unsigned int jhead = le32_to_cpu(ref->jhead);
+
+	/*
+	 * ref->offs may point to the end of LEB when the journal head points
+	 * to the end of LEB and we write reference node for it during commit.
+	 * So this is why we require 'offs > c->leb_size'.
+	 */
+	if (jhead >= c->jhead_cnt || lnum >= c->leb_cnt ||
+	    lnum < c->main_first || offs > c->leb_size ||
+	    offs & (c->min_io_size - 1))
+		return -EINVAL;
+
+	/* Make sure we have not already looked at this bud */
+	bud = ubifs_search_bud(c, lnum);
+	if (bud) {
+		if (bud->jhead == jhead && bud->start <= offs)
+			return 1;
+		errmsg("bud at LEB %d:%d was already referred", lnum, offs);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * replay_log_leb - replay a log logical eraseblock.
+ * @c: UBIFS file-system description object
+ * @lnum: log logical eraseblock to replay
+ * @offs: offset to start replaying from
+ * @sbuf: scan buffer
+ *
+ * This function replays a log LEB and returns zero in case of success, %1 if
+ * this is the last LEB in the log, and a negative error code in case of
+ * failure.
+ */
+static int replay_log_leb(struct ubifs_info *c, int lnum, int offs, void *sbuf)
+{
+	int err;
+	struct ubifs_scan_leb *sleb;
+	struct ubifs_scan_node *snod;
+	const struct ubifs_cs_node *node;
+
+	verbose(c->verbose, "replay log LEB %d:%d", lnum, offs);
+	sleb = ubifs_scan(c, lnum, offs, sbuf, !c->verbose);
+	if (IS_ERR(sleb)) {
+		if (PTR_ERR(sleb) != -EUCLEAN)
+			return PTR_ERR(sleb);
+		/*
+		 * Note, the below function will recover this log LEB only if
+		 * it is the last, because unclean reboots can possibly corrupt
+		 * only the tail of the log.
+		 */
+		// TODO implement recovery
+		//sleb = ubifs_recover_log_leb(c, lnum, offs, sbuf);
+		//if (IS_ERR(sleb))
+			return PTR_ERR(sleb);
+	}
+
+	if (sleb->nodes_cnt == 0) {
+		err = 1;
+		goto out;
+	}
+
+	node = sleb->buf;
+	snod = list_entry(sleb->nodes.next, struct ubifs_scan_node, list);
+	if (c->cs_sqnum == 0) {
+		/*
+		 * This is the first log LEB we are looking at, make sure that
+		 * the first node is a commit start node. Also record its
+		 * sequence number so that UBIFS can determine where the log
+		 * ends, because all nodes which were have higher sequence
+		 * numbers.
+		 */
+		if (snod->type != UBIFS_CS_NODE) {
+			errmsg("first log node at LEB %d:%d is not CS node",
+				  lnum, offs);
+			goto out_dump;
+		}
+		if (le64_to_cpu(node->cmt_no) != c->cmt_no) {
+			errmsg("first CS node at LEB %d:%d has wrong commit number %llu expected %llu",
+				  lnum, offs,
+				  (unsigned long long)le64_to_cpu(node->cmt_no),
+				  c->cmt_no);
+			goto out_dump;
+		}
+
+		c->cs_sqnum = le64_to_cpu(node->ch.sqnum);
+		verbose(c->verbose, "commit start sqnum %llu", c->cs_sqnum);
+	}
+
+	if (snod->sqnum < c->cs_sqnum) {
+		/*
+		 * This means that we reached end of log and now
+		 * look to the older log data, which was already
+		 * committed but the eraseblock was not erased (UBIFS
+		 * only un-maps it). So this basically means we have to
+		 * exit with "end of log" code.
+		 */
+		err = 1;
+		goto out;
+	}
+
+	/* Make sure the first node sits at offset zero of the LEB */
+	if (snod->offs != 0) {
+		errmsg("first node is not at zero offset");
+		goto out_dump;
+	}
+
+	list_for_each_entry(snod, &sleb->nodes, list) {
+		// TODO remove this check since we still might be able to
+		// unpack a end-of-life UBIFS!
+		if (snod->sqnum >= SQNUM_WATERMARK) {
+			errmsg("file system's life ended");
+			goto out_dump;
+		}
+
+		if (snod->sqnum < c->cs_sqnum) {
+			errmsg("bad sqnum %llu, commit sqnum %llu",
+				  snod->sqnum, c->cs_sqnum);
+			goto out_dump;
+		}
+
+		if (snod->sqnum > c->max_sqnum)
+			c->max_sqnum = snod->sqnum;
+
+		switch (snod->type) {
+		case UBIFS_REF_NODE: {
+			const struct ubifs_ref_node *ref = snod->node;
+
+			err = validate_ref(c, ref);
+			if (err == 1)
+				break; /* Already have this bud */
+			if (err)
+				goto out_dump;
+
+			err = add_replay_bud(c, le32_to_cpu(ref->lnum),
+					     le32_to_cpu(ref->offs),
+					     le32_to_cpu(ref->jhead),
+					     snod->sqnum);
+			if (err)
+				goto out;
+
+			break;
+		}
+		case UBIFS_CS_NODE:
+			/* Make sure it sits at the beginning of LEB */
+			if (snod->offs != 0) {
+				errmsg("unexpected node in log");
+				goto out_dump;
+			}
+			break;
+		default:
+			errmsg("unexpected node in log");
+			goto out_dump;
+		}
+	}
+
+	if (sleb->endpt) {
+		c->lhead_lnum = lnum;
+	}
+
+	err = !sleb->endpt;
+out:
+	ubifs_scan_destroy(sleb);
+	return err;
+
+out_dump:
+	errmsg("log error detected while replaying the log at LEB %d:%d",
+		  lnum, offs + snod->offs);
+	ubifs_scan_destroy(sleb);
+	return -EINVAL;
+}
+
+/**
+ * ubifs_replay_journal - replay journal.
+ * @c: UBIFS file-system description object
+ *
+ * This function scans the journal, replays and cleans it up. It makes sure all
+ * memory data structures related to uncommitted journal are built (dirty TNC
+ * tree, tree of buds, modified lprops, etc).
+ */
+int ubifs_replay_journal(struct ubifs_info *c)
+{
+	int err, lnum, ltail_lnum;
+
+	verbose(c->verbose, "start replaying the journal");
+	lnum = ltail_lnum = c->lhead_lnum;
+
+	do {
+		err = replay_log_leb(c, lnum, 0, c->sbuf);
+		if (err == 1) {
+			if (lnum != c->lhead_lnum)
+				/* We hit the end of the log */
+				break;
+
+			/*
+			 * The head of the log must always start with the
+			 * "commit start" node on a properly formatted UBIFS.
+			 * But we found no nodes at all, which means that
+			 * someting went wrong and we cannot proceed mounting
+			 * the file-system.
+			 */
+			errmsg("no UBIFS nodes found at the log head LEB %d:%d, possibly corrupted",
+				  lnum, 0);
+			err = -EINVAL;
+		}
+		if (err)
+			goto out;
+		lnum = ubifs_next_log_lnum(c, lnum);
+	} while (lnum != ltail_lnum);
+
+	err = replay_buds(c);
+	if (err)
+		goto out;
+
+	err = apply_replay_list(c);
+	if (err)
+		goto out;
+
+	verbose(c->verbose, "finished, log head LEB %d, max_sqnum %llu, highest_inum %lu",
+		c->lhead_lnum, c->max_sqnum, (unsigned long)c->highest_inum);
+out:
+	destroy_replay_list(c);
+	destroy_bud_list(c);
+	return err;
+}
diff --git a/ubifs-utils/ubifs_unpack/ubifs_unpack.c b/ubifs-utils/ubifs_unpack/ubifs_unpack.c
new file mode 100644
index 0000000..321a42f
--- /dev/null
+++ b/ubifs-utils/ubifs_unpack/ubifs_unpack.c
@@ -0,0 +1,619 @@ 
+/*
+ * Copyright (C) 2015 sigma star gmbh
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Richard Weinberger
+ *          David Gstir
+ */
+
+#define PROGRAM_NAME "ubifs_unpack"
+
+#include "ubifs_common.h"
+#include "ubifs_unpack.h"
+
+#include "crc32.h"
+#include "lpt.h"
+#include "key.h"
+#include "emubi.h"
+#include "master.h"
+#include "ubiutils-common.h"
+#include "compr.h"
+
+#include <getopt.h>
+#include <sys/mman.h>
+#include <lzo/lzo1x.h>
+
+extern char *optarg;
+extern int optind;
+
+struct unpack_args {
+	int peb_size;
+	int page_size;
+	int vol_id;
+	char *ubi_dev;
+
+	int emubi_mode;
+	int verbose;
+	char *out_root;
+};
+
+static const char doc[] = PROGRAM_NAME " version " VERSION
+			  " - a tool to unpack UBIFS images.";
+
+static const char optionsstr[] =
+"-o, --output=<output dir>    root directory where extracted files will be\n"
+"                             extracted to (required)\n"
+"-p, --peb-size=<bytes>       size of the physical eraseblock of the flash\n"
+"                             this UBI image is created for in bytes,\n"
+"                             kilobytes (KiB), or megabytes (MiB)\n"
+"                             (required)\n"
+"-m, --min-io-size=<bytes>    minimum input/output unit size of the flash\n"
+"                             in bytes (required)\n"
+"-n, --vol-id=<volume ID>     UBI volume ID (defaults to 0)\n"
+"-u, --ubi-dev=<device path>  UBI volume device to read from\n"
+"-e, --emubi-mode=<mode>      EMUBI mode: 'mmap' or 'file' (defaults to 'file')\n"
+"-v, --verbose                verbose output\n"
+"-h, --help                   print help message\n"
+"-V, --version                print program version";
+
+static const char usage[] =
+"Usage: " PROGRAM_NAME " [-o <output dir>] [-p <bytes>] [-m <bytes>] [-n <volume ID>] [-v]\n"
+"\t\t[--output=<output dir>] [--peb-size=<bytes>] [--min-io-size=<bytes>]\n"
+"\t\t[--vol-id=<volume ID>] [--verbose] [--help] [--version] source\n"
+"Example: " PROGRAM_NAME " -p 16KiB -m 512 ubifs.img - unpack volume 1 of image\n"
+"         'ubifs.img'";
+
+static const struct option long_opts[] = {
+	{ .name = "output",         .has_arg = 1, .flag = NULL, .val = 'o' },
+	{ .name = "peb-size",       .has_arg = 1, .flag = NULL, .val = 'p' },
+	{ .name = "min-io-size",    .has_arg = 1, .flag = NULL, .val = 'm' },
+	{ .name = "vol-id",         .has_arg = 1, .flag = NULL, .val = 'n' },
+	{ .name = "ubi-dev",        .has_arg = 1, .flag = NULL, .val = 'u' },
+	{ .name = "emubi-mode",     .has_arg = 1, .flag = NULL, .val = 'e' },
+	{ .name = "verbose",        .has_arg = 0, .flag = NULL, .val = 'v' },
+	{ .name = "help",           .has_arg = 0, .flag = NULL, .val = 'h' },
+	{ .name = "version",        .has_arg = 0, .flag = NULL, .val = 'V' },
+	{ NULL, 0, NULL, 0 }
+};
+
+// TODO replace all assert()s with proper error handling
+// TODO cleanup logging - esp. unify key logging
+
+static int write_block(struct ubifs_info *c, struct ubifs_dent_node *dent,
+		       int block, char *dst)
+{
+	struct ubifs_data_node *d;
+	size_t out_len = UBIFS_BLOCK_SIZE;
+	int data_len, ret;
+
+	d = ubifs_lookup_data_node(c, le64toh(dent->inum), block);
+	if (!d) {
+		/* file hole */
+		return 0;
+	}
+
+	data_len = le32toh(d->ch.len) - UBIFS_DATA_NODE_SZ;
+	ret = decompress_data(d->data, data_len, dst, &out_len, d->compr_type);
+	free(d);
+
+	return ret;
+}
+
+static void __set_attributes(struct ubifs_ino_node *ino, int target_fd)
+{
+	fchmod(target_fd, le32toh(ino->mode));
+	fchown(target_fd, le32toh(ino->uid), le32toh(ino->gid));
+}
+
+static void set_attributes(struct ubifs_info *c, struct ubifs_dent_node *dent,
+			   int target_fd)
+{
+	struct ubifs_ino_node *ino;
+
+	ino = ubifs_lookup_ino_node(c, le64toh(dent->inum));
+	__set_attributes(ino, target_fd);
+	free(ino);
+}
+
+static void write_symlink(struct ubifs_info *c, struct ubifs_dent_node *dent,
+			  int parent_fd)
+{
+	struct ubifs_ino_node *ino;
+	char *link_tgt;
+	int len;
+
+	ino = ubifs_lookup_ino_node(c, le64toh(dent->inum));
+	assert(ino);
+
+	len = le32toh(ino->data_len);
+	if (len > PATH_MAX) {
+		free(ino);
+		return;
+	}
+
+	link_tgt = xmalloc(len + 1);
+	memcpy(link_tgt, ino->data, len);
+	link_tgt[len] = '\0';
+
+	symlinkat(link_tgt, parent_fd, (char *)dent->name);
+
+	free(link_tgt);
+	free(ino);
+}
+
+static void write_file(struct ubifs_info *c, struct ubifs_dent_node *dent,
+		       int parent_fd)
+{
+	int fd, i, errs;
+	char *tgt;
+	struct ubifs_ino_node *ino;
+	char nul[1] = { 0 };
+	unsigned int file_len;
+	int num_blocks;
+
+	ino = ubifs_lookup_ino_node(c, le64toh(dent->inum));
+	assert(ino);
+	file_len = le64toh(ino->size);
+
+	num_blocks = (file_len + UBIFS_BLOCK_SIZE - 1) >> UBIFS_BLOCK_SHIFT;
+
+	fd = openat(parent_fd, (const char *)dent->name, O_CREAT|O_RDWR|O_TRUNC, 0700);
+	assert(fd);
+
+	if (!file_len) {
+		free(ino);
+		close(fd);
+		return;
+	}
+
+	lseek(fd, file_len - 1, SEEK_SET);
+	write(fd, nul, 1);
+
+	tgt = mmap(NULL, file_len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+	assert(tgt != MAP_FAILED);
+	__set_attributes(ino, fd);
+	free(ino);
+	close(fd);
+
+	errs = 0;
+	for (i = 0; i < num_blocks; i++)
+		if (write_block(c, dent, i, tgt + (UBIFS_BLOCK_SIZE * i)) != 0)
+			errs++;
+
+	if (errs)
+		errmsg("decompress of %d data nodes of %s failed", errs, dent->name);
+
+	munmap(tgt, file_len);
+}
+
+static inline dev_t new_decode_dev(uint32_t dev)
+{
+	unsigned major = (dev & 0xfff00) >> 8;
+	unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
+
+	return makedev(major, minor);
+}
+
+static void write_devnode(struct ubifs_info *c, struct ubifs_dent_node *dent,
+			  int parent_fd)
+{
+	struct ubifs_ino_node *ino;
+	union ubifs_dev_desc *devd;
+	dev_t rdev;
+
+	ino = ubifs_lookup_ino_node(c, le64toh(dent->inum));
+	assert(ino);
+
+	devd = (union ubifs_dev_desc *)ino->data;
+
+	if (ino->data_len == sizeof(devd->new))
+		rdev = new_decode_dev(devd->new);
+	else if (ino->data_len == sizeof(devd->huge))
+		rdev = new_decode_dev(devd->huge);
+	else {
+		errmsg("Invalid inode data len!\n");
+		goto out;
+	}
+
+	mknodat(parent_fd, (const char *)dent->name, le32toh(ino->mode), rdev);
+
+out:
+	free(ino);
+}
+
+static void write_fifo(struct ubifs_info *c, struct ubifs_dent_node *dent,
+		       int parent_fd)
+{
+	struct ubifs_ino_node *ino;
+
+	ino = ubifs_lookup_ino_node(c, le64toh(dent->inum));
+	assert(ino);
+
+	mknodat(parent_fd, (const char *)dent->name, le32toh(ino->mode), 0);
+
+	free(ino);
+}
+
+static void unpack_dir(struct ubifs_info *c, ino_t dir_ino, int parent_fd, int depth)
+{
+	int fd;
+	struct ubifs_dent_node *dent;
+	struct ubifs_leaf *leaf = NULL;
+
+	for (;;) {
+		leaf = ubifs_next_leaf(leaf, dir_ino);
+		if (!leaf)
+			/* end of list */
+			break;
+
+		/* skip any nodes other than UBIFS_DENT_NODE. E.g extended
+		 * attribute nodes */
+		if (key_type(&leaf->key) != UBIFS_DENT_KEY)
+			continue;
+
+		dent = xmalloc(leaf->len);
+		get_dent_node(c, dent, leaf->lnum, leaf->offs, leaf->len);
+		bareverbose(c->verbose, "%.*s", depth, "                 ");
+		switch (dent->type) {
+		case UBIFS_ITYPE_REG:
+			bareverbose(c->verbose, "%s\n", dent->name);
+			write_file(c, dent, parent_fd);
+			break;
+		case UBIFS_ITYPE_DIR:
+			bareverbose(c->verbose, "[%s]\n", dent->name);
+
+			if (mkdirat(parent_fd, (const char *)dent->name, 0755) == -1) {
+				warnmsg("Unable to create %s: %m\n", dent->name);
+				continue;
+			}
+
+			fd = openat(parent_fd, (const char *)dent->name, O_RDONLY);
+			if (fd == -1) {
+				sys_errmsg_die("unpack of %s failed", dent->name);
+			}
+
+			unpack_dir(c, le64toh(dent->inum), fd, depth + 1);
+			set_attributes(c, dent, fd);
+			close(fd);
+			break;
+		case UBIFS_ITYPE_LNK:
+			write_symlink(c, dent, parent_fd);
+			break;
+		case UBIFS_ITYPE_CHR:
+		case UBIFS_ITYPE_BLK:
+			write_devnode(c, dent, parent_fd);
+			break;
+		case UBIFS_ITYPE_FIFO:
+			write_fifo(c, dent, parent_fd);
+			break;
+		case UBIFS_ITYPE_SOCK:
+			warnmsg("Ignoring UNIX domain socket %s", dent->name);
+			break;
+		default:
+			/* TODO handle other inode types (link, devices etc) */
+			warnmsg("Unsupported DENT type: %d", dent->type);
+		}
+
+		free(dent);
+	}
+}
+
+//TODO: make this common with ubifs_dump
+static void destroy_ubifs_info(struct ubifs_info *c)
+{
+	free(c->sbuf);
+}
+
+/*
+ * init_constants_sb - initialize UBIFS constants.
+ * @c: UBIFS file-system description object
+ *
+ * This is a helper function which initializes various UBIFS constants after
+ * the superblock has been read. It also checks various UBIFS parameters and
+ * makes sure they are all right. Returns zero in case of success and a
+ * negative error code in case of failure.
+ */
+//TODO: make this common with ubifs_dump
+static int init_constants_sb(struct ubifs_info *c)
+{
+	int tmp, err;
+
+	tmp = ubifs_idx_node_sz(c, 1);
+	c->min_idx_node_sz = ALIGN(tmp, 8);
+
+	tmp = ubifs_idx_node_sz(c, c->fanout);
+	c->max_idx_node_sz = ALIGN(tmp, 8);
+
+	c->max_znode_sz = sizeof(struct ubifs_znode) +
+			c->fanout * sizeof(struct ubifs_zbranch);
+	/* Make sure LEB size is large enough to fit full commit */
+	tmp = UBIFS_CS_NODE_SZ + UBIFS_REF_NODE_SZ * c->jhead_cnt;
+	tmp = ALIGN(tmp, c->min_io_size);
+	if (tmp > c->leb_size) {
+		errmsg("too small LEB size %d, at least %d needed",
+			  c->leb_size, tmp);
+		return -EINVAL;
+	}
+
+	err = ubifs_calc_lpt_geom(c);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+//TODO: make this common with ubifs_dump
+static int init_ubifs_info(struct ubifs_info *c, struct ubifs_sb_node *sb)
+{
+	int err;
+	unsigned long sup_flags;
+
+        switch (sb->key_hash) {
+        case UBIFS_KEY_HASH_R5:
+                c->key_hash = key_r5_hash;
+                c->key_hash_type = UBIFS_KEY_HASH_R5;
+                break;
+
+        case UBIFS_KEY_HASH_TEST:
+                c->key_hash = key_test_hash;
+                c->key_hash_type = UBIFS_KEY_HASH_TEST;
+                break;
+        };
+
+	c->key_fmt = sb->key_fmt;
+
+	switch (c->key_fmt) {
+	case UBIFS_SIMPLE_KEY_FMT:
+		c->key_len = UBIFS_SK_LEN;
+		break;
+	default:
+		errmsg("unsupported UBIFS key format");
+		err = -EINVAL;
+		goto out;
+	}
+
+	c->leb_size	 = le32_to_cpu(sb->leb_size);
+	c->min_io_size	 = le32_to_cpu(sb->min_io_size);
+	c->leb_cnt       = le32_to_cpu(sb->leb_cnt);
+	c->max_leb_cnt   = le32_to_cpu(sb->max_leb_cnt);
+	c->max_bud_bytes = le64_to_cpu(sb->max_bud_bytes);
+	c->log_lebs      = le32_to_cpu(sb->log_lebs);
+	c->lpt_lebs      = le32_to_cpu(sb->lpt_lebs);
+	c->orph_lebs     = le32_to_cpu(sb->orph_lebs);
+	c->jhead_cnt     = le32_to_cpu(sb->jhead_cnt) + NONDATA_JHEADS_CNT;
+	c->fanout        = le32_to_cpu(sb->fanout);
+	c->lsave_cnt     = le32_to_cpu(sb->lsave_cnt);
+	c->rp_size       = le64_to_cpu(sb->rp_size);
+	sup_flags        = le32_to_cpu(sb->flags);
+	c->cs_sqnum      = 0;
+
+	c->default_compr = le16_to_cpu(sb->default_compr);
+
+	c->big_lpt = !!(sup_flags & UBIFS_FLG_BIGLPT);
+	c->space_fixup = !!(sup_flags & UBIFS_FLG_SPACE_FIXUP);
+
+	c->lpt_first = UBIFS_LOG_LNUM + c->log_lebs;
+	c->lpt_last = c->lpt_first + c->lpt_lebs - 1;
+	c->main_lebs = c->leb_cnt - UBIFS_SB_LEBS - UBIFS_MST_LEBS;
+	c->main_lebs -= c->log_lebs + c->lpt_lebs + c->orph_lebs;
+	c->main_first = c->leb_cnt - c->main_lebs;
+
+	c->max_inode_sz = key_max_inode_size(c);
+
+	err = init_constants_sb(c);
+
+	c->sbuf = xmalloc(c->leb_size);
+
+	INIT_LIST_HEAD(&c->replay_list);
+	INIT_LIST_HEAD(&c->replay_buds);
+out:
+	return err;
+}
+
+static int ubifs_read_sb(struct ubifs_info *c)
+{
+	struct ubifs_sb_node sb;
+
+	ubifs_leb_read(c, UBIFS_SB_LNUM, &sb, 0, sizeof(sb));
+	if (le32toh(sb.ch.magic) != UBIFS_NODE_MAGIC) {
+		return errmsg("invalid superblock node");
+	}
+
+	init_ubifs_info(c, &sb);
+
+	// TODO call validate_sb() here to properly check the sb node.
+	return 0;
+}
+
+static int unpack(struct ubifs_info *c, char *out_root)
+{
+	int fd, err = 0;
+
+	normsg("reading superblock node...");
+	err = ubifs_read_sb(c);
+	if (err)
+		goto out;
+
+	normsg("reading master node...");
+	err = ubifs_read_master(c);
+	if (err)
+		goto out;
+
+	normsg("reading on-flash index...");
+	ubifs_load_index(c, c->zroot.lnum, c->zroot.offs, c->zroot.len);
+
+	normsg("replaying journal...");
+	err = ubifs_replay_journal(c);
+	if (err)
+		goto out;
+
+	normsg("unpacking files...");
+	fd = open(out_root, O_RDONLY);
+	if (fd == -1)
+		return sys_errmsg("open of output folder failed");
+
+	unpack_dir(c, 1, fd, 0);
+
+out:
+	return err;
+}
+
+static int parse_opt(int argc, char *argv[], struct unpack_args *args)
+{
+	long long val;
+	int key, err;
+
+	while ((key = getopt_long(argc, argv, "o:p:m:n:u:e:vhV", long_opts, NULL)) != -1) {
+		switch (key) {
+		case 'o':
+			args->out_root = optarg;
+			err = mkdir(args->out_root, 0755);
+			if (err && errno == EEXIST)
+				return errmsg("output directory exists");
+			else if (err)
+				return sys_errmsg("mkdir of %s failed", optarg);
+			break;
+		case 'p':
+			val = parse_bytes(optarg);
+			if (val <= 0 || val > UINT_MAX)
+				return errmsg("bad physical eraseblock size: \"%s\"", optarg);
+			args->peb_size = val;
+			break;
+		case 'm':
+			val = parse_bytes(optarg);
+			if (val <= 0 || val > UINT_MAX)
+				return errmsg("bad min. I/O unit size: \"%s\"", optarg);
+			if (!is_power_of_2(val))
+				return errmsg("min. I/O unit size must be power of 2");
+			args->page_size = val;
+			break;
+		case 'n':
+			args->vol_id = atoi(optarg);
+			if (args->vol_id < 0)
+				return errmsg("bad volume ID: \"%s\"", optarg);
+			break;
+		case 'u':
+			args->ubi_dev = optarg;
+			break;
+		case 'e':
+			if (strcasecmp(optarg, "mmap") == 0)
+				args->emubi_mode = EMUBI_MODE_MMAP;
+			else if (strcasecmp(optarg, "file") == 0)
+				args->emubi_mode = EMUBI_MODE_FILE;
+			else
+				return errmsg("invalid emubi mode %s (use 'file' or 'mmap')", optarg);
+			break;
+		case 'v':
+			args->verbose = 1;
+			break;
+		case 'h':
+			printf("%s\n\n", doc);
+			printf("%s\n\n", usage);
+			printf("%s\n\n", optionsstr);
+			exit(EXIT_SUCCESS);
+		case 'V':
+			common_print_version();
+			exit(EXIT_SUCCESS);
+		default:
+			return errmsg("unknown option. Use -h for help");
+		}
+
+	}
+
+	if (!args->out_root)
+		return errmsg("no output directory specified");
+
+	if (!args->ubi_dev) {
+		if (optind == argc)
+			return errmsg("image file is missing (use -h for help)");
+
+		if (args->peb_size < 0)
+			return errmsg("physical eraseblock size was not specified (use -p <bytes>)");
+
+		if (args->page_size < 0)
+			return errmsg("min. I/O unit size was not specified (use -m <bytes>)");
+
+		if (args->peb_size % args->page_size)
+			return errmsg("physical eraseblock size must be multiple of min. I/O unit size");
+	}
+
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	int err;
+	struct ubifs_info info;
+	struct emubi_ctx emubi;
+	struct unpack_args args = {
+		.peb_size = -1,
+		.page_size = -1,
+		.vol_id = 0,
+		.ubi_dev = NULL,
+		.verbose = 0,
+		.out_root = NULL,
+		.emubi_mode = EMUBI_MODE_FILE,
+	};
+
+	err = parse_opt(argc, argv, &args);
+	if (err)
+		return EXIT_FAILURE;
+
+	emubi.mode = args.emubi_mode;
+	if (args.ubi_dev) {
+		output = xstrdup(args.ubi_dev);
+		open_ubi(&info, output);
+		if (open_target(&info, 1)) {
+			errmsg("Unable to open UBI volume device\n");
+			return EXIT_FAILURE;
+		}
+	}
+	else {
+		emubi.peb_size = args.peb_size;
+		emubi.page_size = args.page_size;
+		emubi.vol_id = args.vol_id;
+		info.emubi = &emubi;
+		info.verbose = args.verbose;
+
+		err = emubi_open(&emubi, argv[optind]);
+		if (err)
+			goto out;
+
+		err = emubi_scan(&emubi);
+		if (err)
+			goto out;
+
+
+		emubi_print(&emubi);
+	}
+
+	err = unpack(&info, args.out_root);
+
+out:
+	if (args.ubi_dev)
+		close_target();
+
+	ubifs_destroy_index();
+	emubi_close(&emubi);
+	destroy_ubifs_info(&info);
+
+	if (err) {
+		errmsg("unpack failed");
+		return EXIT_FAILURE;
+	}
+
+	normsg("unpack finished");
+	return EXIT_SUCCESS;
+}
diff --git a/ubifs-utils/ubifs_unpack/ubifs_unpack.h b/ubifs-utils/ubifs_unpack/ubifs_unpack.h
new file mode 100644
index 0000000..5c033e8
--- /dev/null
+++ b/ubifs-utils/ubifs_unpack/ubifs_unpack.h
@@ -0,0 +1,107 @@ 
+/*
+ * Copyright (C) 2015 sigma star gmbh
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors: Richard Weinberger
+ *          David Gstir
+ */
+
+#ifndef __UBIFS_UNPACK_H__
+#define __UBIFS_UNPACK_H__
+
+#include "ubifs_common.h"
+#include "common.h"
+#include "ubifs.h"
+#include "scan.h"
+#include "key.h"
+#include "xalloc.h"
+#include "io.h"
+
+struct ubifs_leaf {
+	union ubifs_key key;
+	unsigned int lnum;
+	unsigned int offs;
+	unsigned int len;
+	struct list_head l;
+};
+
+static inline void get_idx_node(struct ubifs_info *c, struct ubifs_idx_node *n,
+				unsigned int lnum, unsigned int offset,
+				unsigned int len)
+{
+	ubifs_leb_read(c, lnum, n, offset, len);
+	assert(le32toh(n->ch.magic) == UBIFS_NODE_MAGIC);
+	assert(n->ch.node_type == UBIFS_IDX_NODE);
+}
+
+static inline void get_dent_node(struct ubifs_info *c, struct ubifs_dent_node *n,
+				 unsigned int lnum, unsigned int offset,
+				 unsigned int len)
+{
+	ubifs_leb_read(c, lnum, n, offset, len);
+	assert(le32toh(n->ch.magic) == UBIFS_NODE_MAGIC);
+	assert(n->ch.node_type == UBIFS_DENT_NODE);
+}
+
+static inline void get_ino_node(struct ubifs_info *c, struct ubifs_ino_node *n,
+				unsigned int lnum, unsigned int offset,
+				unsigned int len)
+{
+	ubifs_leb_read(c, lnum, n, offset, len);
+	assert(le32toh(n->ch.magic) == UBIFS_NODE_MAGIC);
+	assert(n->ch.node_type == UBIFS_INO_NODE);
+}
+
+static inline void get_data_node(struct ubifs_info *c, struct ubifs_data_node *n,
+				 unsigned int lnum, unsigned int offset,
+				 unsigned int len)
+{
+	ubifs_leb_read(c, lnum, n, offset, len);
+	assert(le32toh(n->ch.magic) == UBIFS_NODE_MAGIC);
+	assert(n->ch.node_type == UBIFS_DATA_NODE);
+}
+
+static inline struct ubifs_branch *get_branch(struct ubifs_idx_node *idx,
+					      unsigned int n)
+{
+	return (struct ubifs_branch *)(idx->branches
+		 + ((UBIFS_BRANCH_SZ + UBIFS_SK_LEN) * n));
+}
+
+/* replay.c */
+int ubifs_replay_journal(struct ubifs_info *c);
+
+/* index.c */
+void ubifs_destroy_index();
+void ubifs_load_index(struct ubifs_info *c, unsigned int lnum,
+		      unsigned int offset, unsigned int len);
+struct ubifs_ino_node *ubifs_lookup_ino_node(struct ubifs_info *c, ino_t ino);
+struct ubifs_data_node *ubifs_lookup_data_node(struct ubifs_info *c, ino_t ino,
+					       unsigned int block);
+struct ubifs_leaf *ubifs_next_leaf(struct ubifs_leaf *ul, ino_t ino);
+
+int ubifs_add_node(struct ubifs_info *c, union ubifs_key *key, int lnum,
+		   int offs, unsigned int len);
+int ubifs_add_node_nm(struct ubifs_info *c, union ubifs_key *key, int lnum,
+		      int offs, unsigned int len, struct qstr *nm);
+int ubifs_remove_inode(struct ubifs_info *c, ino_t ino);
+int ubifs_remove_node(struct ubifs_info *c, union ubifs_key *key);
+int ubifs_remove_node_nm(struct ubifs_info *c, union ubifs_key *key,
+			 struct qstr *nm);
+int ubifs_remove_node_range(struct ubifs_info *c, union ubifs_key *min_key,
+			    union ubifs_key *max_key);
+
+
+#endif