diff --git a/ubifs-utils/.gitignore b/ubifs-utils/.gitignore
index 6b0e85c..05d6d9f 100644
--- a/ubifs-utils/.gitignore
+++ b/ubifs-utils/.gitignore
@@ -1 +1,2 @@
 /mkfs.ubifs
+/tuneubifs
diff --git a/ubifs-utils/Makefile b/ubifs-utils/Makefile
index 499f9e5..fb81871 100644
--- a/ubifs-utils/Makefile
+++ b/ubifs-utils/Makefile
@@ -4,12 +4,15 @@ CPPFLAGS += $(ZLIBCPPFLAGS) $(LZOCPPFLAGS)
 
 ALL_SOURCES=*.[ch] hashtable/*.[ch]
 
-TARGETS = mkfs.ubifs
+TARGETS = tuneubifs mkfs.ubifs
 
 LDLIBS_mkfs.ubifs = -lz -llzo2 -lm -luuid -L$(BUILDDIR)/../ubi-utils/ -lubi
 LDLIBS_mkfs.ubifs += -L$(BUILDDIR)/../lib -lmtd -lcrc32
 LDLIBS_mkfs.ubifs += $(ZLIBLDFLAGS) $(LZOLDFLAGS)
 
+LDLIBS_tuneubifs = -L$(BUILDDIR)/../ubi-utils/ -lubi
+LDLIBS_tuneubifs += -L$(BUILDDIR)/../lib -lmtd
+
 include ../common.mk
 
 $(BUILDDIR)/mkfs.ubifs: $(addprefix $(BUILDDIR)/,\
diff --git a/ubifs-utils/tuneubifs.c b/ubifs-utils/tuneubifs.c
new file mode 100644
index 0000000..9546281
--- /dev/null
+++ b/ubifs-utils/tuneubifs.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2007, 2008, 2010 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
+ */
+
+/*
+ * An utility to tune a UBI filesystem.
+ *
+ * Author: Stefani Seibold <stefani@seibold.net>
+ *         in order of NSN Nokia Siemens Networks Ulm/Germany
+ *         based on work by Artem Bityutskiy
+ *
+ */
+
+#define PROGRAM_VERSION "0.4"
+#define PROGRAM_NAME "tuneubifs"
+
+#define _GNU_SOURCE
+#define _LARGEFILE64_SOURCE
+#include <getopt.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <stdint.h>
+#include <endian.h>
+#include <byteswap.h>
+#include <linux/types.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <libubi.h>
+#include <crc32.h>
+#include "common.h"
+#include "ubiutils-common.h"
+#include "ubifs-media.h"
+#include "defs.h"
+
+static void *super_buf;
+struct ubi_dev_info dev_info;
+
+/* The variables below are set by command line arguments */
+struct args {
+	int devn;
+	int vol_id;
+	const char *node;
+	const char *vol_name;
+	int compr;
+	long long reserved;
+	int verbose;
+};
+
+static struct args args = {
+	.vol_id = -1,
+	.devn = -1,
+	.node = NULL,
+	.vol_name = NULL,
+	.compr = -1,
+	.reserved = -1,
+	.verbose = 0,
+};
+
+static const char doc[] = PROGRAM_NAME " version " PROGRAM_VERSION
+			 " - a tool for UBI filesystem tuning.";
+
+static const char optionsstr[] =
+"-d, --devn=<UBI device number>  UBI device number to tune\n"
+"-n, --vol_id=<volume ID>        ID of UBI volume to tune\n"
+"-N, --name=<volume name>        name of UBI volume to tune\n"
+"-x, --compr=<none|lzo|zlib>     compression type\n"
+"-R, --reserved=SIZE             how much space should be reserved for super-user\n"
+"-v, --verbose                   verbose output\n"
+"-h, --help                      print help message\n"
+"-V, --version                   print program version";
+
+static const char usage[] =
+"Usage 1: " PROGRAM_NAME " [-d <UBI device number>] [-n <volume ID> | -N <volume name>]\n"
+"\t\t[-h] [-V] [--vol_id=<volume ID> | --name <volume name>]\n"
+"\t\t[--devn <UBI device number>] [--help] [--version]\n"
+"Usage 2: " PROGRAM_NAME " <UBI volume node file name> [-h] [-V] [--help] [--version]\n\n";
+
+static const struct option long_options[] = {
+	{ .name = "devn",      .has_arg = 1, .flag = NULL, .val = 'd' },
+	{ .name = "vol_id",    .has_arg = 1, .flag = NULL, .val = 'n' },
+	{ .name = "name",      .has_arg = 1, .flag = NULL, .val = 'N' },
+	{ .name = "help",      .has_arg = 0, .flag = NULL, .val = 'h' },
+	{ .name = "version",   .has_arg = 0, .flag = NULL, .val = 'V' },
+	{ .name = "compr",     .has_arg = 1, .flag = NULL, .val = 'c' },
+	{ .name = "reserved",  .has_arg = 1, .flag = NULL, .val = 'r' },
+	{ .name = "verbose",   .has_arg = 0, .flag = NULL, .val = 'v' },
+	{ NULL, 0, NULL, 0},
+};
+
+/**
+ * get_multiplier - convert size specifier to an integer multiplier.
+ * @str: the size specifier string
+ *
+ * This function parses the @str size specifier, which may be one of
+ * 'KiB', 'MiB', or 'GiB' into an integer multiplier. Returns positive
+ * size multiplier in case of success and %-1 in case of failure.
+ */
+static int get_multiplier(const char *str)
+{
+	if (!str)
+		return 1;
+
+	/* Remove spaces before the specifier */
+	while (*str == ' ' || *str == '\t')
+		str++;
+
+	if (!strcasecmp(str, "KB"))
+		return 1000;
+	if (!strcasecmp(str, "MB"))
+		return 1000 * 1000;
+	if (!strcasecmp(str, "GB"))
+		return 1000 * 1000 * 1000;
+	if (!strcasecmp(str, "KiB"))
+		return 1024;
+	if (!strcasecmp(str, "MiB"))
+		return 1024 * 1024;
+	if (!strcasecmp(str, "GiB"))
+		return 1024 * 1024 * 1024;
+
+	return -1;
+}
+
+/**
+ * get_bytes - convert a string containing amount of bytes into an
+ *             integer.
+ * @str: string to convert
+ *
+ * This function parses @str which may have one of 'KiB', 'MiB', or 'GiB' size
+ * specifiers. Returns positive amount of bytes in case of success and %-1 in
+ * case of failure.
+ */
+static long long get_bytes(const char *str)
+{
+	char *endp;
+	long long bytes = strtoull(str, &endp, 0);
+
+	if (endp == str || bytes < 0)
+		return errmsg("incorrect amount of bytes: \"%s\"", str);
+
+	if (*endp != '\0') {
+		int mult = get_multiplier(endp);
+
+		if (mult == -1)
+			return errmsg("bad size specifier: \"%s\" - "
+				"should be 'KiB', 'MiB' or 'GiB'", endp);
+		bytes *= mult;
+	}
+
+	return bytes;
+}
+
+static int parse_opt(int argc, char * const argv[])
+{
+	while (1) {
+		int key;
+		char *endp;
+
+		key = getopt_long(argc, argv, "an:N:d:hVx:R:v", long_options, NULL);
+		if (key == -1)
+			break;
+
+		switch (key) {
+		case 'n':
+			args.vol_id = strtoul(optarg, &endp, 0);
+			if (*endp != '\0' || endp == optarg || args.vol_id < 0)
+				return errmsg("bad volume ID: " "\"%s\"", optarg);
+			break;
+
+		case 'N':
+			args.vol_name = optarg;
+			break;
+
+		case 'd':
+			args.devn = strtoul(optarg, &endp, 0);
+			if (*endp != '\0' || endp == optarg || args.devn < 0)
+				return errmsg("bad UBI device number: \"%s\"", optarg);
+
+			break;
+
+		case 'h':
+			fprintf(stderr, "%s\n\n", doc);
+			fprintf(stderr, "%s\n\n", usage);
+			fprintf(stderr, "%s\n", optionsstr);
+			exit(EXIT_SUCCESS);
+
+		case 'V':
+			fprintf(stderr, "%s\n", PROGRAM_VERSION);
+			exit(EXIT_SUCCESS);
+
+		case 'x':
+			if (!strcmp(optarg, "none"))
+				args.compr = UBIFS_COMPR_NONE;
+			else
+			if (!strcmp(optarg, "lzo"))
+				args.compr = UBIFS_COMPR_LZO;
+			else
+			if (!strcmp(optarg, "zlib"))
+				args.compr = UBIFS_COMPR_ZLIB;
+			else
+				return errmsg("bad compr type: \"%s\"", optarg);
+
+			break;
+
+		case 'R':
+			args.reserved = get_bytes(optarg);
+			break;
+
+		case 'v':
+			args.verbose = 1;
+			break;
+
+		case ':':
+			return errmsg("parameter is missing");
+
+		default:
+			fprintf(stderr, "Use -h for help\n");
+			return -1;
+		}
+	}
+
+	if (optind == argc - 1)
+		args.node = argv[optind];
+	else if (optind < argc)
+		return errmsg("more then one UBI device specified (use -h for help)");
+
+	return 0;
+}
+
+static int translate_dev(libubi_t libubi, const char *node)
+{
+	int err;
+
+	err = ubi_probe_node(libubi, node);
+	if (err == -1) {
+		if (errno != ENODEV)
+			return sys_errmsg("error while probing \"%s\"", node);
+		return errmsg("\"%s\" does not correspond to any UBI device or volume", node);
+	}
+
+	if (err == 1) {
+		struct ubi_dev_info dev_info;
+
+		err = ubi_get_dev_info(libubi, node, &dev_info);
+		if (err)
+			return sys_errmsg("cannot get information about UBI device \"%s\"", node);
+
+		args.devn = dev_info.dev_num;
+	} else {
+		struct ubi_vol_info vol_info;
+
+		err = ubi_get_vol_info(libubi, node, &vol_info);
+		if (err)
+			return sys_errmsg("cannot get information about UBI volume \"%s\"", node);
+
+		if (args.vol_id != -1)
+			return errmsg("both volume character device node (\"%s\") and "
+			   "volume ID (%d) are specify, use only one of them"
+			   "(use -h for help)", node, args.vol_id);
+
+		args.devn = vol_info.dev_num;
+		args.vol_id = vol_info.vol_id;
+	}
+
+	return 0;
+}
+
+static int get_vol_id_by_name(libubi_t libubi, int dev_num, const char *name)
+{
+	int err;
+	struct ubi_vol_info vol_info;
+
+	err = ubi_get_vol_info1_nm(libubi, dev_num, name, &vol_info);
+	if (err)
+		return sys_errmsg("cannot get information about volume \"%s\" on ubi%d\n", name, dev_num);
+
+	args.vol_id = vol_info.vol_id;
+
+	return 0;
+}
+
+/**
+ * read_super - read the ubifs super block
+ * @fd: device node file handle
+ * @buf: buffer (must be at least leb_size bytes)
+ */
+static int read_super(int fd, void *buf)
+{
+	struct ubifs_sb_node *sup = buf;
+	off64_t pos = UBIFS_SB_LNUM * dev_info.leb_size;
+
+	if (lseek64(fd, pos, SEEK_SET) != pos)
+		return sys_errmsg("failed seeking super block");
+
+	if (read(fd, buf, UBIFS_SB_NODE_SZ) != UBIFS_SB_NODE_SZ)
+		return sys_errmsg("read super block failed");
+
+	if (le32_to_cpu(sup->ch.magic) != UBIFS_NODE_MAGIC)
+		return sys_errmsg("invalid super block magic");
+
+	if (le32_to_cpu(sup->ch.crc) != mtd_crc32(UBIFS_CRC32_INIT, buf + 8, le32_to_cpu(sup->ch.len) - 8))
+		return sys_errmsg("invalid super block crc");
+
+	if (le32_to_cpu(sup->leb_size) != dev_info.leb_size)
+		return sys_errmsg("invalid super block leb_size");
+
+	return 0;
+}
+
+/**
+ * update_super - write the super block.
+ * @ubi: ubi descriptor
+ * @fd: device node file handle
+ * @buf: buffer (must be at least leb_size bytes)
+ */
+static int update_super(libubi_t ubi, int fd, void *buf)
+{
+	int len;
+	struct ubifs_sb_node *sup = buf;
+	off64_t pos = (off64_t)UBIFS_SB_LNUM * dev_info.leb_size;
+	int update = 0;
+
+	if (args.compr != -1) {
+		if (args.compr != le16_to_cpu(sup->default_compr)) {
+			sup->default_compr = cpu_to_le16(args.compr);
+			update = 1;
+		}
+	}
+
+	if (args.reserved != -1) {
+		if (args.reserved != le64_to_cpu(sup->rp_size)) {
+			sup->rp_size = cpu_to_le64(args.reserved);
+			update = 1;
+		}
+	}
+
+	if (!update) {
+		puts("Nothing changed, hence super block NOT written to flash!");
+		return 1;
+	}
+
+	len = ALIGN(ALIGN(UBIFS_SB_NODE_SZ, 8), dev_info.min_io_size);
+	memset(buf + len, 0xff, dev_info.leb_size - len);
+
+	sup->ch.crc = cpu_to_le32(mtd_crc32(UBIFS_CRC32_INIT, buf + 8, le32_to_cpu(sup->ch.len) - 8));
+
+	if (ubi_leb_change_start(ubi, fd, UBIFS_SB_LNUM, dev_info.leb_size, UBI_LONGTERM))
+		return sys_errmsg("ubi_leb_change_start failed");
+
+	if (lseek64(fd, pos, SEEK_SET) != pos)
+		return sys_errmsg("failed seeking super block");
+
+	if (write(fd, buf, dev_info.leb_size) != dev_info.leb_size)
+		return sys_errmsg("write super block failed");
+
+	return 0;
+}
+
+/**
+ * show_super - show the super block.
+ */
+static void show_super(void *buf)
+{
+	struct ubifs_sb_node *sup = buf;
+	const char *p;
+
+	switch(le16_to_cpu(sup->default_compr)) {
+	case UBIFS_COMPR_NONE:
+		p = "none";
+		break;
+	case UBIFS_COMPR_LZO:
+		p = "lzo";
+		break;
+	case UBIFS_COMPR_ZLIB:
+		p = "zlib";
+		break;
+	default:
+		p = "unknown";
+		break;
+	}
+
+	printf("UBIFS:\n");
+	printf(" default compressor: %s\n", p);
+	printf(" reserved for root: %llu bytes\n", le64_to_cpu(sup->rp_size));
+}
+
+int main(int argc, char * const argv[])
+{
+	int err;
+	libubi_t libubi;
+	char devname[128];
+	int fd;
+
+	err = parse_opt(argc, argv);
+	if (err)
+		return -1;
+
+	libubi = libubi_open();
+	if (!libubi) {
+		if (errno == 0)
+			return errmsg("UBI is not present in the system");
+		return sys_errmsg("cannot open libubi");
+	}
+
+	if (args.node) {
+		/*
+		 * A character device was specified, translate this into UBI
+		 * device number and volume ID.
+		 */
+		err = translate_dev(libubi, args.node);
+		if (err)
+			goto out_libubi;
+	}
+
+	if (args.devn == -1) {
+		errmsg("device number is missing (use -h for help)\n");
+		goto out_libubi;
+	}
+
+	err = ubi_get_dev_info1(libubi, args.devn, &dev_info);
+	if (err) {
+		errmsg("cannot get information about UBI device %d", args.devn);
+		goto out_libubi;
+	}
+
+	if (args.vol_name) {
+		err = get_vol_id_by_name(libubi, args.devn, args.vol_name);
+		if (err)
+			goto out_libubi;
+	}
+
+	if (args.vol_id == -1) {
+		errmsg("volume ID is missing (use -h for help)\n");
+		goto out_libubi;
+	}
+
+	if (!args.node) {
+		sprintf(devname, "/dev/ubi%d_%d", args.devn, args.vol_id);
+		args.node = devname;
+	}
+
+	super_buf = malloc(dev_info.leb_size);
+	if (!super_buf) {
+		errmsg("out of memory");
+		goto out_libubi;
+	}
+
+	fd = open(args.node, O_RDWR|O_EXCL);
+	if (fd == -1) {
+		errmsg("cannot open the UBI volume '%s'", args.node);
+		goto out_libubi;
+	}
+
+	err = read_super(fd, super_buf);
+	if (err)
+		goto out_libubi;
+
+	if (args.compr != -1 || args.reserved != -1)
+		update_super(libubi, fd, super_buf);
+	else
+		args.verbose = 1;
+
+	if (args.verbose)
+		show_super(super_buf);
+
+	if (err)
+		goto out_libubi;
+
+	libubi_close(libubi);
+	return 0;
+
+out_libubi:
+	libubi_close(libubi);
+	return -1;
+}
