use libblkid to get filesystem type
we can now use libblkid to detect exfat
diff --git a/libblkid/tt.c b/libblkid/tt.c
new file mode 100644
index 0000000..cbe4e3b
--- /dev/null
+++ b/libblkid/tt.c
@@ -0,0 +1,1005 @@
+/*
+ * TT - Table or Tree, features:
+ * - column width could be defined as absolute or relative to the terminal width
+ * - allows to truncate or wrap data in columns
+ * - prints tree if parent->child relation is defined
+ * - draws the tree by ASCII or UTF8 lines (depends on terminal setting)
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <termios.h>
+#include <ctype.h>
+
+#include "c.h"
+#include "nls.h"
+#include "widechar.h"
+#include "tt.h"
+#include "mbsalign.h"
+#include "ttyutils.h"
+
+struct tt_symbols {
+	const char *branch;
+	const char *vert;
+	const char *right;
+};
+
+static const struct tt_symbols ascii_tt_symbols = {
+	.branch = "|-",
+	.vert	= "| ",
+	.right	= "`-",
+};
+
+#ifdef HAVE_WIDECHAR
+#define UTF_V	"\342\224\202"	/* U+2502, Vertical line drawing char */
+#define UTF_VR	"\342\224\234"	/* U+251C, Vertical and right */
+#define UTF_H	"\342\224\200"	/* U+2500, Horizontal */
+#define UTF_UR	"\342\224\224"	/* U+2514, Up and right */
+
+static const struct tt_symbols utf8_tt_symbols = {
+	.branch = UTF_VR UTF_H,
+	.vert   = UTF_V " ",
+	.right	= UTF_UR UTF_H,
+};
+#endif /* !HAVE_WIDECHAR */
+
+#define is_last_column(_tb, _cl) \
+		list_entry_is_last(&(_cl)->cl_columns, &(_tb)->tb_columns)
+
+/*
+ * Counts number of cells in multibyte string. For all control and
+ * non-printable chars is the result width enlarged to store \x?? hex
+ * sequence. See mbs_safe_encode().
+ */
+static size_t mbs_safe_width(const char *s)
+{
+	mbstate_t st;
+	const char *p = s;
+	size_t width = 0;
+
+	memset(&st, 0, sizeof(st));
+
+	while (p && *p) {
+		if (iscntrl((unsigned char) *p)) {
+			width += 4;			/* *p encoded to \x?? */
+			p++;
+		}
+#ifdef HAVE_WIDECHAR
+		else {
+			wchar_t wc;
+			size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+			if (len == 0)
+				break;
+
+			if (len == (size_t) -1 || len == (size_t) -2) {
+				len = 1;
+				width += (isprint((unsigned char) *p) ? 1 : 4);
+
+			} if (!iswprint(wc))
+				width += len * 4;	/* hex encode whole sequence */
+			else
+				width += wcwidth(wc);	/* number of cells */
+			p += len;
+		}
+#else
+		else if (!isprint((unsigned char) *p)) {
+			width += 4;			/* *p encoded to \x?? */
+			p++;
+		} else {
+			width++;
+			p++;
+		}
+#endif
+	}
+
+	return width;
+}
+
+/*
+ * Returns allocated string where all control and non-printable chars are
+ * replaced with \x?? hex sequence.
+ */
+static char *mbs_safe_encode(const char *s, size_t *width)
+{
+	mbstate_t st;
+	const char *p = s;
+	char *res, *r;
+	size_t sz = s ? strlen(s) : 0;
+
+
+	if (!sz)
+		return NULL;
+
+	memset(&st, 0, sizeof(st));
+
+	res = malloc((sz * 4) + 1);
+	if (!res)
+		return NULL;
+
+	r = res;
+	*width = 0;
+
+	while (p && *p) {
+		if (iscntrl((unsigned char) *p)) {
+			sprintf(r, "\\x%02x", (unsigned char) *p);
+			r += 4;
+			*width += 4;
+			p++;
+		}
+#ifdef HAVE_WIDECHAR
+		else {
+			wchar_t wc;
+			size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
+
+			if (len == 0)
+				break;		/* end of string */
+
+			if (len == (size_t) -1 || len == (size_t) -2) {
+				len = 1;
+				/*
+				 * Not valid multibyte sequence -- maybe it's
+				 * printable char according to the current locales.
+				 */
+				if (!isprint((unsigned char) *p)) {
+					sprintf(r, "\\x%02x", (unsigned char) *p);
+					r += 4;
+					*width += 4;
+				} else {
+					width++;
+					*r++ = *p;
+				}
+			} else if (!iswprint(wc)) {
+				size_t i;
+				for (i = 0; i < len; i++) {
+					sprintf(r, "\\x%02x", (unsigned char) *p);
+					r += 4;
+					*width += 4;
+				}
+			} else {
+				memcpy(r, p, len);
+				r += len;
+				*width += wcwidth(wc);
+			}
+			p += len;
+		}
+#else
+		else if (!isprint((unsigned char) *p)) {
+			sprintf(r, "\\x%02x", (unsigned char) *p);
+			p++;
+			r += 4;
+			*width += 4;
+		} else {
+			*r++ = *p++;
+			*width++;
+		}
+#endif
+	}
+
+	*r = '\0';
+
+	return res;
+}
+
+/*
+ * @flags: TT_FL_* flags (usually TT_FL_{ASCII,RAW})
+ *
+ * Returns: newly allocated table
+ */
+struct tt *tt_new_table(int flags)
+{
+	struct tt *tb;
+
+	tb = calloc(1, sizeof(struct tt));
+	if (!tb)
+		return NULL;
+
+	tb->flags = flags;
+	INIT_LIST_HEAD(&tb->tb_lines);
+	INIT_LIST_HEAD(&tb->tb_columns);
+
+#if defined(HAVE_WIDECHAR)
+	if (!(flags & TT_FL_ASCII) && !strcmp(nl_langinfo(CODESET), "UTF-8"))
+		tb->symbols = &utf8_tt_symbols;
+	else
+#endif
+		tb->symbols = &ascii_tt_symbols;
+
+	tb->first_run = TRUE;
+	return tb;
+}
+
+void tt_remove_lines(struct tt *tb)
+{
+	if (!tb)
+		return;
+
+	while (!list_empty(&tb->tb_lines)) {
+		struct tt_line *ln = list_entry(tb->tb_lines.next,
+						struct tt_line, ln_lines);
+		list_del(&ln->ln_lines);
+		free(ln->data);
+		free(ln);
+	}
+}
+
+void tt_free_table(struct tt *tb)
+{
+	if (!tb)
+		return;
+
+	tt_remove_lines(tb);
+
+	while (!list_empty(&tb->tb_columns)) {
+		struct tt_column *cl = list_entry(tb->tb_columns.next,
+						struct tt_column, cl_columns);
+		list_del(&cl->cl_columns);
+		free(cl);
+	}
+	free(tb);
+}
+
+
+/*
+ * @tb: table
+ * @name: column header
+ * @whint: column width hint (absolute width: N > 1; relative width: N < 1)
+ * @flags: usually TT_FL_{TREE,TRUNCATE}
+ *
+ * The column width is possible to define by three ways:
+ *
+ *  @whint = 0..1    : relative width, percent of terminal width
+ *
+ *  @whint = 1..N    : absolute width, empty colum will be truncated to
+ *                     the column header width
+ *
+ *  @whint = 1..N
+ *  @flags = TT_FL_STRICTWIDTH
+ *                   : absolute width, empty colum won't be truncated
+ *
+ * The column is necessary to address (for example for tt_line_set_data()) by
+ * sequential number. The first defined column has the colnum = 0. For example:
+ *
+ *	tt_define_column(tab, "FOO", 0.5, 0);		// colnum = 0
+ *	tt_define_column(tab, "BAR", 0.5, 0);		// colnum = 1
+ *      .
+ *      .
+ *	tt_line_set_data(line, 0, "foo-data");		// FOO column
+ *	tt_line_set_data(line, 1, "bar-data");		// BAR column
+ *
+ * Returns: newly allocated column definition
+ */
+struct tt_column *tt_define_column(struct tt *tb, const char *name,
+					double whint, int flags)
+{
+	struct tt_column *cl;
+
+	if (!tb)
+		return NULL;
+	cl = calloc(1, sizeof(*cl));
+	if (!cl)
+		return NULL;
+
+	cl->name = name;
+	cl->width_hint = whint;
+	cl->flags = flags;
+	cl->seqnum = tb->ncols++;
+
+	if (flags & TT_FL_TREE)
+		tb->flags |= TT_FL_TREE;
+
+	INIT_LIST_HEAD(&cl->cl_columns);
+	list_add_tail(&cl->cl_columns, &tb->tb_columns);
+	return cl;
+}
+
+/*
+ * @tb: table
+ * @parent: parental line or NULL
+ *
+ * Returns: newly allocate line
+ */
+struct tt_line *tt_add_line(struct tt *tb, struct tt_line *parent)
+{
+	struct tt_line *ln = NULL;
+
+	if (!tb || !tb->ncols)
+		goto err;
+	ln = calloc(1, sizeof(*ln));
+	if (!ln)
+		goto err;
+	ln->data = calloc(tb->ncols, sizeof(char *));
+	if (!ln->data)
+		goto err;
+
+	ln->table = tb;
+	ln->parent = parent;
+	INIT_LIST_HEAD(&ln->ln_lines);
+	INIT_LIST_HEAD(&ln->ln_children);
+	INIT_LIST_HEAD(&ln->ln_branch);
+
+	list_add_tail(&ln->ln_lines, &tb->tb_lines);
+
+	if (parent)
+		list_add_tail(&ln->ln_children, &parent->ln_branch);
+	return ln;
+err:
+	free(ln);
+	return NULL;
+}
+
+/*
+ * @tb: table
+ * @colnum: number of column (0..N)
+ *
+ * Returns: pointer to column or NULL
+ */
+struct tt_column *tt_get_column(struct tt *tb, size_t colnum)
+{
+	struct list_head *p;
+
+	list_for_each(p, &tb->tb_columns) {
+		struct tt_column *cl =
+				list_entry(p, struct tt_column, cl_columns);
+		if (cl->seqnum == colnum)
+			return cl;
+	}
+	return NULL;
+}
+
+/*
+ * @ln: line
+ * @colnum: number of column (0..N)
+ * @data: printable data
+ *
+ * Stores data that will be printed to the table cell.
+ */
+int tt_line_set_data(struct tt_line *ln, int colnum, const char *data)
+{
+	struct tt_column *cl;
+
+	if (!ln)
+		return -1;
+	cl = tt_get_column(ln->table, colnum);
+	if (!cl)
+		return -1;
+
+	if (ln->data[cl->seqnum]) {
+		size_t sz = strlen(ln->data[cl->seqnum]);;
+		ln->data_sz = ln->data_sz > sz ? ln->data_sz - sz : 0;
+	}
+
+	ln->data[cl->seqnum] = data;
+	if (data)
+		ln->data_sz += strlen(data);
+	return 0;
+}
+
+int tt_line_set_userdata(struct tt_line *ln, void *data)
+{
+	if (!ln)
+		return -1;
+	ln->userdata = data;
+	return 0;
+}
+
+static char *line_get_ascii_art(struct tt_line *ln, char *buf, size_t *bufsz)
+{
+	const char *art;
+	size_t len;
+
+	if (!ln->parent)
+		return buf;
+
+	buf = line_get_ascii_art(ln->parent, buf, bufsz);
+	if (!buf)
+		return NULL;
+
+	if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch))
+		art = "  ";
+	else
+		art = ln->table->symbols->vert;
+
+	len = strlen(art);
+	if (*bufsz < len)
+		return NULL;	/* no space, internal error */
+
+	memcpy(buf, art, len);
+	*bufsz -= len;
+	return buf + len;
+}
+
+static char *line_get_data(struct tt_line *ln, struct tt_column *cl,
+				char *buf, size_t bufsz)
+{
+	const char *data = ln->data[cl->seqnum];
+	const struct tt_symbols *sym;
+	char *p = buf;
+
+	memset(buf, 0, bufsz);
+
+	if (!data)
+		return NULL;
+	if (!(cl->flags & TT_FL_TREE)) {
+		strncpy(buf, data, bufsz);
+		buf[bufsz - 1] = '\0';
+		return buf;
+	}
+
+	/*
+	 * Tree stuff
+	 */
+	if (ln->parent) {
+		p = line_get_ascii_art(ln->parent, buf, &bufsz);
+		if (!p)
+			return NULL;
+	}
+
+	sym = ln->table->symbols;
+
+	if (!ln->parent)
+		snprintf(p, bufsz, "%s", data);			/* root node */
+	else if (list_entry_is_last(&ln->ln_children, &ln->parent->ln_branch))
+		snprintf(p, bufsz, "%s%s", sym->right, data);	/* last chaild */
+	else
+		snprintf(p, bufsz, "%s%s", sym->branch, data);	/* any child */
+
+	return buf;
+}
+
+/*
+ * This function counts column width.
+ *
+ * For the TT_FL_NOEXTREMES columns is possible to call this function two
+ * times.  The first pass counts width and average width. If the column
+ * contains too large fields (width greater than 2 * average) then the column
+ * is marked as "extreme". In the second pass all extreme fields are ignored
+ * and column width is counted from non-extreme fields only.
+ */
+static void count_column_width(struct tt *tb, struct tt_column *cl,
+				 char *buf, size_t bufsz)
+{
+	struct list_head *lp;
+	int count = 0;
+	size_t sum = 0;
+
+	cl->width = 0;
+
+	list_for_each(lp, &tb->tb_lines) {
+		struct tt_line *ln = list_entry(lp, struct tt_line, ln_lines);
+		char *data = line_get_data(ln, cl, buf, bufsz);
+		size_t len = data ? mbs_safe_width(data) : 0;
+
+		if (len == (size_t) -1)		/* ignore broken multibyte strings */
+			len = 0;
+
+		if (len > cl->width_max)
+			cl->width_max = len;
+
+		if (cl->is_extreme && len > cl->width_avg * 2)
+			continue;
+		else if (cl->flags & TT_FL_NOEXTREMES) {
+			sum += len;
+			count++;
+		}
+		if (len > cl->width)
+			cl->width = len;
+	}
+
+	if (count && cl->width_avg == 0) {
+		cl->width_avg = sum / count;
+
+		if (cl->width_max > cl->width_avg * 2)
+			cl->is_extreme = 1;
+	}
+
+	/* check and set minimal column width */
+	if (cl->name)
+		cl->width_min = mbs_safe_width(cl->name);
+
+	/* enlarge to minimal width */
+	if (cl->width < cl->width_min && !(cl->flags & TT_FL_STRICTWIDTH))
+		cl->width = cl->width_min;
+
+	/* use relative size for large columns */
+	else if (cl->width_hint >= 1 && cl->width < (size_t) cl->width_hint &&
+		 cl->width_min < (size_t) cl->width_hint)
+
+		cl->width = (size_t) cl->width_hint;
+}
+
+/*
+ * This is core of the tt_* voodo...
+ */
+static void recount_widths(struct tt *tb, char *buf, size_t bufsz)
+{
+	struct list_head *p;
+	size_t width = 0;	/* output width */
+	int trunc_only;
+	int extremes = 0;
+
+	/* set basic columns width
+	 */
+	list_for_each(p, &tb->tb_columns) {
+		struct tt_column *cl =
+				list_entry(p, struct tt_column, cl_columns);
+
+		count_column_width(tb, cl, buf, bufsz);
+		width += cl->width + (is_last_column(tb, cl) ? 0 : 1);
+		extremes += cl->is_extreme;
+	}
+
+	if (!tb->is_term)
+		return;
+
+	/* reduce columns with extreme fields
+	 */
+	if (width > tb->termwidth && extremes) {
+		list_for_each(p, &tb->tb_columns) {
+			struct tt_column *cl = list_entry(p, struct tt_column, cl_columns);
+			size_t org_width;
+
+			if (!cl->is_extreme)
+				continue;
+
+			org_width = cl->width;
+			count_column_width(tb, cl, buf, bufsz);
+
+			if (org_width > cl->width)
+				width -= org_width - cl->width;
+			else
+				extremes--;	/* hmm... nothing reduced */
+		}
+	}
+
+	if (width < tb->termwidth) {
+		/* try to found extreme column which fits into available space
+		 */
+		if (extremes) {
+			/* enlarge the first extreme column */
+			list_for_each(p, &tb->tb_columns) {
+				struct tt_column *cl =
+					list_entry(p, struct tt_column, cl_columns);
+				size_t add;
+
+				if (!cl->is_extreme)
+					continue;
+
+				/* this column is tooo large, ignore?
+				if (cl->width_max - cl->width >
+						(tb->termwidth - width))
+					continue;
+				*/
+
+				add = tb->termwidth - width;
+				if (add && cl->width + add > cl->width_max)
+					add = cl->width_max - cl->width;
+
+				cl->width += add;
+				width += add;
+
+				if (width == tb->termwidth)
+					break;
+			}
+		}
+		if (width < tb->termwidth) {
+			/* enalarge the last column */
+			struct tt_column *cl = list_entry(
+				tb->tb_columns.prev, struct tt_column, cl_columns);
+
+			if (!(cl->flags & TT_FL_RIGHT) && tb->termwidth - width > 0) {
+				cl->width += tb->termwidth - width;
+				width = tb->termwidth;
+			}
+		}
+	}
+
+	/* bad, we have to reduce output width, this is done in two steps:
+	 * 1/ reduce columns with a relative width and with truncate flag
+	 * 2) reduce columns with a relative width without truncate flag
+	 */
+	trunc_only = 1;
+	while (width > tb->termwidth) {
+		size_t org = width;
+
+		list_for_each(p, &tb->tb_columns) {
+			struct tt_column *cl =
+				list_entry(p, struct tt_column, cl_columns);
+
+			if (width <= tb->termwidth)
+				break;
+			if (cl->width_hint > 1 && !(cl->flags & TT_FL_TRUNC))
+				continue;	/* never truncate columns with absolute sizes */
+			if (cl->flags & TT_FL_TREE)
+				continue;	/* never truncate the tree */
+			if (trunc_only && !(cl->flags & TT_FL_TRUNC))
+				continue;
+			if (cl->width == cl->width_min)
+				continue;
+
+			/* truncate column with relative sizes */
+			if (cl->width_hint < 1 && cl->width > 0 && width > 0 &&
+			    cl->width > cl->width_hint * tb->termwidth) {
+				cl->width--;
+				width--;
+			}
+			/* truncate column with absolute size */
+			if (cl->width_hint > 1 && cl->width > 0 && width > 0 &&
+			    !trunc_only) {
+				cl->width--;
+				width--;
+			}
+
+		}
+		if (org == width) {
+			if (trunc_only)
+				trunc_only = 0;
+			else
+				break;
+		}
+	}
+
+/*
+	fprintf(stderr, "terminal: %d, output: %d\n", tb->termwidth, width);
+
+	list_for_each(p, &tb->tb_columns) {
+		struct tt_column *cl =
+			list_entry(p, struct tt_column, cl_columns);
+
+		fprintf(stderr, "width: %s=%zd [hint=%d, avg=%zd, max=%zd, extreme=%s]\n",
+			cl->name, cl->width,
+			cl->width_hint > 1 ? (int) cl->width_hint :
+					     (int) (cl->width_hint * tb->termwidth),
+			cl->width_avg,
+			cl->width_max,
+			cl->is_extreme ? "yes" : "not");
+	}
+*/
+	return;
+}
+
+void tt_fputs_quoted(const char *data, FILE *out)
+{
+	const char *p;
+
+	fputc('"', out);
+	for (p = data; p && *p; p++) {
+		if ((unsigned char) *p == 0x22 ||		/* " */
+		    (unsigned char) *p == 0x5c ||		/* \ */
+		    !isprint((unsigned char) *p) ||
+		    iscntrl((unsigned char) *p)) {
+
+			fprintf(out, "\\x%02x", (unsigned char) *p);
+		} else
+			fputc(*p, out);
+	}
+	fputc('"', out);
+}
+
+void tt_fputs_nonblank(const char *data, FILE *out)
+{
+	const char *p;
+
+	for (p = data; p && *p; p++) {
+		if (isblank((unsigned char) *p) ||
+		    (unsigned char) *p == 0x5c ||		/* \ */
+		    !isprint((unsigned char) *p) ||
+		    iscntrl((unsigned char) *p)) {
+
+			fprintf(out, "\\x%02x", (unsigned char) *p);
+
+		} else
+			fputc(*p, out);
+	}
+}
+
+/*
+ * Prints data, data maybe be printed in more formats (raw, NAME=xxx pairs) and
+ * control and non-printable chars maybe encoded in \x?? hex encoding.
+ */
+static void print_data(struct tt *tb, struct tt_column *cl, char *data)
+{
+	size_t len = 0, i, width;
+	char *buf;
+
+	if (!data)
+		data = "";
+
+	/* raw mode */
+	if (tb->flags & TT_FL_RAW) {
+		tt_fputs_nonblank(data, stdout);
+		if (!is_last_column(tb, cl))
+			fputc(' ', stdout);
+		return;
+	}
+
+	/* NAME=value mode */
+	if (tb->flags & TT_FL_EXPORT) {
+		fprintf(stdout, "%s=", cl->name);
+		tt_fputs_quoted(data, stdout);
+		if (!is_last_column(tb, cl))
+			fputc(' ', stdout);
+		return;
+	}
+
+	/* note that 'len' and 'width' are number of cells, not bytes */
+	buf = mbs_safe_encode(data, &len);
+	data = buf;
+	if (!data)
+		data = "";
+
+	if (!len || len == (size_t) -1) {
+		len = 0;
+		data = NULL;
+	}
+	width = cl->width;
+
+	if (is_last_column(tb, cl) && len < width)
+		width = len;
+
+	/* truncate data */
+	if (len > width && (cl->flags & TT_FL_TRUNC)) {
+		if (data)
+			len = mbs_truncate(data, &width);
+		if (!data || len == (size_t) -1) {
+			len = 0;
+			data = NULL;
+		}
+	}
+	if (data) {
+		if (!(tb->flags & TT_FL_RAW) && (cl->flags & TT_FL_RIGHT)) {
+			size_t xw = cl->width;
+			fprintf(stdout, "%*s", (int) xw, data);
+			if (len < xw)
+				len = xw;
+		}
+		else
+			fputs(data, stdout);
+	}
+	for (i = len; i < width; i++)
+		fputc(' ', stdout);		/* padding */
+
+	if (!is_last_column(tb, cl)) {
+		if (len > width && !(cl->flags & TT_FL_TRUNC)) {
+			fputc('\n', stdout);
+			for (i = 0; i <= (size_t) cl->seqnum; i++) {
+				struct tt_column *x = tt_get_column(tb, i);
+				printf("%*s ", -((int)x->width), " ");
+			}
+		} else
+			fputc(' ', stdout);	/* columns separator */
+	}
+
+	free(buf);
+}
+
+static void print_line(struct tt_line *ln, char *buf, size_t bufsz)
+{
+	struct list_head *p;
+
+	/* set width according to the size of data
+	 */
+	list_for_each(p, &ln->table->tb_columns) {
+		struct tt_column *cl =
+				list_entry(p, struct tt_column, cl_columns);
+
+		print_data(ln->table, cl, line_get_data(ln, cl, buf, bufsz));
+	}
+	fputc('\n', stdout);
+}
+
+static void print_header(struct tt *tb, char *buf, size_t bufsz)
+{
+	struct list_head *p;
+
+	if (!tb->first_run ||
+	    (tb->flags & TT_FL_NOHEADINGS) ||
+	    (tb->flags & TT_FL_EXPORT) ||
+	    list_empty(&tb->tb_lines))
+		return;
+
+	/* set width according to the size of data
+	 */
+	list_for_each(p, &tb->tb_columns) {
+		struct tt_column *cl =
+				list_entry(p, struct tt_column, cl_columns);
+
+		strncpy(buf, cl->name, bufsz);
+		buf[bufsz - 1] = '\0';
+		print_data(tb, cl, buf);
+	}
+	fputc('\n', stdout);
+}
+
+static void print_table(struct tt *tb, char *buf, size_t bufsz)
+{
+	struct list_head *p;
+
+	print_header(tb, buf, bufsz);
+
+	list_for_each(p, &tb->tb_lines) {
+		struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
+
+		print_line(ln, buf, bufsz);
+	}
+}
+
+static void print_tree_line(struct tt_line *ln, char *buf, size_t bufsz)
+{
+	struct list_head *p;
+
+	print_line(ln, buf, bufsz);
+
+	if (list_empty(&ln->ln_branch))
+		return;
+
+	/* print all children */
+	list_for_each(p, &ln->ln_branch) {
+		struct tt_line *chld =
+				list_entry(p, struct tt_line, ln_children);
+		print_tree_line(chld, buf, bufsz);
+	}
+}
+
+static void print_tree(struct tt *tb, char *buf, size_t bufsz)
+{
+	struct list_head *p;
+
+	print_header(tb, buf, bufsz);
+
+	list_for_each(p, &tb->tb_lines) {
+		struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
+
+		if (ln->parent)
+			continue;
+
+		print_tree_line(ln, buf, bufsz);
+	}
+}
+
+/*
+ * @tb: table
+ *
+ * Prints the table to stdout
+ */
+int tt_print_table(struct tt *tb)
+{
+	char *line;
+	size_t line_sz;
+	struct list_head *p;
+
+	if (!tb)
+		return -1;
+
+	if (tb->first_run) {
+		tb->is_term = isatty(STDOUT_FILENO);
+
+		if (tb->is_term && !tb->termwidth)
+			tb->termwidth = get_terminal_width();
+		if (tb->termwidth <= 0)
+			tb->termwidth = 80;
+	}
+
+	line_sz = tb->termwidth;
+
+	list_for_each(p, &tb->tb_lines) {
+		struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
+		if (ln->data_sz > line_sz)
+			line_sz = ln->data_sz;
+	}
+
+	line_sz++;			/* make a space for \0 */
+	line = malloc(line_sz);
+	if (!line)
+		return -1;
+
+	if (tb->first_run &&
+	    !((tb->flags & TT_FL_RAW) || (tb->flags & TT_FL_EXPORT)))
+		recount_widths(tb, line, line_sz);
+
+	if (tb->flags & TT_FL_TREE)
+		print_tree(tb, line, line_sz);
+	else
+		print_table(tb, line, line_sz);
+
+	free(line);
+
+	tb->first_run = FALSE;
+	return 0;
+}
+
+#ifdef TEST_PROGRAM
+#include <errno.h>
+
+enum { MYCOL_NAME, MYCOL_FOO, MYCOL_BAR, MYCOL_PATH };
+
+int main(int argc, char *argv[])
+{
+	struct tt *tb;
+	struct tt_line *ln, *pr, *root;
+	int flags = 0, notree = 0, i;
+
+	if (argc == 2 && !strcmp(argv[1], "--help")) {
+		printf("%s [--ascii | --raw | --list]\n",
+				program_invocation_short_name);
+		return EXIT_SUCCESS;
+	} else if (argc == 2 && !strcmp(argv[1], "--ascii")) {
+		flags |= TT_FL_ASCII;
+	} else if (argc == 2 && !strcmp(argv[1], "--raw")) {
+		flags |= TT_FL_RAW;
+		notree = 1;
+	} else if (argc == 2 && !strcmp(argv[1], "--export")) {
+		flags |= TT_FL_EXPORT;
+		notree = 1;
+	} else if (argc == 2 && !strcmp(argv[1], "--list"))
+		notree = 1;
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+
+	tb = tt_new_table(flags);
+	if (!tb)
+		err(EXIT_FAILURE, "table initialization failed");
+
+	tt_define_column(tb, "NAME", 0.3, notree ? 0 : TT_FL_TREE);
+	tt_define_column(tb, "FOO", 0.3, TT_FL_TRUNC);
+	tt_define_column(tb, "BAR", 0.3, 0);
+	tt_define_column(tb, "PATH", 0.3, 0);
+
+	for (i = 0; i < 2; i++) {
+		root = ln = tt_add_line(tb, NULL);
+		tt_line_set_data(ln, MYCOL_NAME, "AAA");
+		tt_line_set_data(ln, MYCOL_FOO, "a-foo-foo");
+		tt_line_set_data(ln, MYCOL_BAR, "barBar-A");
+		tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA");
+
+		pr = ln = tt_add_line(tb, ln);
+		tt_line_set_data(ln, MYCOL_NAME, "AAA.A");
+		tt_line_set_data(ln, MYCOL_FOO, "a.a-foo-foo");
+		tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A");
+		tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A");
+
+		ln = tt_add_line(tb, pr);
+		tt_line_set_data(ln, MYCOL_NAME, "AAA.A.AAA");
+		tt_line_set_data(ln, MYCOL_FOO, "a.a.a-foo-foo");
+		tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.A");
+		tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/AAA");
+
+		ln = tt_add_line(tb, root);
+		tt_line_set_data(ln, MYCOL_NAME, "AAA.B");
+		tt_line_set_data(ln, MYCOL_FOO, "a.b-foo-foo");
+		tt_line_set_data(ln, MYCOL_BAR, "barBar-A.B");
+		tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/B");
+
+		ln = tt_add_line(tb, pr);
+		tt_line_set_data(ln, MYCOL_NAME, "AAA.A.BBB");
+		tt_line_set_data(ln, MYCOL_FOO, "a.a.b-foo-foo");
+		tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.BBB");
+		tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/BBB");
+
+		ln = tt_add_line(tb, pr);
+		tt_line_set_data(ln, MYCOL_NAME, "AAA.A.CCC");
+		tt_line_set_data(ln, MYCOL_FOO, "a.a.c-foo-foo");
+		tt_line_set_data(ln, MYCOL_BAR, "barBar-A.A.CCC");
+		tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/A/CCC");
+
+		ln = tt_add_line(tb, root);
+		tt_line_set_data(ln, MYCOL_NAME, "AAA.C");
+		tt_line_set_data(ln, MYCOL_FOO, "a.c-foo-foo");
+		tt_line_set_data(ln, MYCOL_BAR, "barBar-A.C");
+		tt_line_set_data(ln, MYCOL_PATH, "/mnt/AAA/C");
+	}
+
+	tt_print_table(tb);
+	tt_free_table(tb);
+
+	return EXIT_SUCCESS;
+}
+#endif