summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am11
-rw-r--r--lib/cpuset.c380
-rw-r--r--lib/ismounted.c7
-rw-r--r--lib/tt.c725
4 files changed, 1122 insertions, 1 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 0f008b30..2a185f34 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -3,13 +3,22 @@ include $(top_srcdir)/config/include-Makefile.am
AM_CPPFLAGS += -DTEST_PROGRAM
noinst_PROGRAMS = test_blkdev test_ismounted test_wholedisk test_mangle \
- test_strtosize
+ test_strtosize test_tt
+if LINUX
+if HAVE_CPU_SET_T
+noinst_PROGRAMS += test_cpuset
+endif
+endif
test_blkdev_SOURCES = blkdev.c
test_ismounted_SOURCES = ismounted.c
test_wholedisk_SOURCES = wholedisk.c
test_mangle_SOURCES = mangle.c
test_strtosize_SOURCES = strtosize.c
+if LINUX
+test_cpuset_SOURCES = cpuset.c
+endif
+test_tt_SOURCES = tt.c
if LINUX
test_blkdev_SOURCES += linux_version.c
diff --git a/lib/cpuset.c b/lib/cpuset.c
new file mode 100644
index 00000000..27b23498
--- /dev/null
+++ b/lib/cpuset.c
@@ -0,0 +1,380 @@
+/*
+ * Terminology:
+ *
+ * cpuset - (libc) cpu_set_t data structure represents set of CPUs
+ * cpumask - string with hex mask (e.g. "0x00000001")
+ * cpulist - string with CPU ranges (e.g. "0-3,5,7,8")
+ *
+ * Based on code from taskset.c and Linux kernel.
+ *
+ * Copyright (C) 2010 Karel Zak <kzak@redhat.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sched.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/syscall.h>
+
+#include "cpuset.h"
+
+static inline int val_to_char(int v)
+{
+ if (v >= 0 && v < 10)
+ return '0' + v;
+ else if (v >= 10 && v < 16)
+ return ('a' - 10) + v;
+ else
+ return -1;
+}
+
+static inline int char_to_val(int c)
+{
+ int cl;
+
+ cl = tolower(c);
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ else if (cl >= 'a' && cl <= 'f')
+ return cl + (10 - 'a');
+ else
+ return -1;
+}
+
+static const char *nexttoken(const char *q, int sep)
+{
+ if (q)
+ q = strchr(q, sep);
+ if (q)
+ q++;
+ return q;
+}
+
+/*
+ * Number of bits in a CPU bitmask on current system
+ */
+int get_max_number_of_cpus(void)
+{
+ int n, cpus = 2048;
+ size_t setsize;
+ cpu_set_t *set = cpuset_alloc(cpus, &setsize, NULL);
+
+ if (!set)
+ return -1; /* error */
+
+ for (;;) {
+ CPU_ZERO_S(setsize, set);
+
+ /* the library version does not return size of cpumask_t */
+ n = syscall(SYS_sched_getaffinity, 0, setsize, set);
+
+ if (n < 0 && errno == EINVAL && cpus < 1024 * 1024) {
+ cpuset_free(set);
+ cpus *= 2;
+ set = cpuset_alloc(cpus, &setsize, NULL);
+ if (!set)
+ return -1; /* error */
+ continue;
+ }
+ cpuset_free(set);
+ return n * 8;
+ }
+ return -1;
+}
+
+/*
+ * Allocates a new set for ncpus and returns size in bytes and size in bits
+ */
+cpu_set_t *cpuset_alloc(int ncpus, size_t *setsize, size_t *nbits)
+{
+ cpu_set_t *set = CPU_ALLOC(ncpus);
+
+ if (!set)
+ return NULL;
+ if (setsize)
+ *setsize = CPU_ALLOC_SIZE(ncpus);
+ if (nbits)
+ *nbits = cpuset_nbits(CPU_ALLOC_SIZE(ncpus));
+ return set;
+}
+
+void cpuset_free(cpu_set_t *set)
+{
+ CPU_FREE(set);
+}
+
+#if !HAVE_DECL_CPU_ALLOC
+/* Please, use CPU_COUNT_S() macro. This is fallback */
+int __cpuset_count_s(size_t setsize, const cpu_set_t *set)
+{
+ int s = 0;
+ const __cpu_mask *p = set->__bits;
+ const __cpu_mask *end = &set->__bits[setsize / sizeof (__cpu_mask)];
+
+ while (p < end) {
+ __cpu_mask l = *p++;
+
+ if (l == 0)
+ continue;
+# if LONG_BIT > 32
+ l = (l & 0x5555555555555555ul) + ((l >> 1) & 0x5555555555555555ul);
+ l = (l & 0x3333333333333333ul) + ((l >> 2) & 0x3333333333333333ul);
+ l = (l & 0x0f0f0f0f0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0f0f0f0f0ful);
+ l = (l & 0x00ff00ff00ff00fful) + ((l >> 8) & 0x00ff00ff00ff00fful);
+ l = (l & 0x0000ffff0000fffful) + ((l >> 16) & 0x0000ffff0000fffful);
+ l = (l & 0x00000000fffffffful) + ((l >> 32) & 0x00000000fffffffful);
+# else
+ l = (l & 0x55555555ul) + ((l >> 1) & 0x55555555ul);
+ l = (l & 0x33333333ul) + ((l >> 2) & 0x33333333ul);
+ l = (l & 0x0f0f0f0ful) + ((l >> 4) & 0x0f0f0f0ful);
+ l = (l & 0x00ff00fful) + ((l >> 8) & 0x00ff00fful);
+ l = (l & 0x0000fffful) + ((l >> 16) & 0x0000fffful);
+# endif
+ s += l;
+ }
+ return s;
+}
+#endif
+
+/*
+ * Returns human readable representation of the cpuset. The output format is
+ * a list of CPUs with ranges (for example, "0,1,3-9").
+ */
+char *cpulist_create(char *str, size_t len,
+ cpu_set_t *set, size_t setsize)
+{
+ int i;
+ char *ptr = str;
+ int entry_made = 0;
+ size_t max = cpuset_nbits(setsize);
+
+ for (i = 0; i < max; i++) {
+ if (CPU_ISSET_S(i, setsize, set)) {
+ int j, rlen;
+ int run = 0;
+ entry_made = 1;
+ for (j = i + 1; j < max; j++) {
+ if (CPU_ISSET_S(j, setsize, set))
+ run++;
+ else
+ break;
+ }
+ if (!run)
+ rlen = snprintf(ptr, len, "%d,", i);
+ else if (run == 1) {
+ rlen = snprintf(ptr, len, "%d,%d,", i, i + 1);
+ i++;
+ } else {
+ rlen = snprintf(ptr, len, "%d-%d,", i, i + run);
+ i += run;
+ }
+ if (rlen < 0 || rlen + 1 > len)
+ return NULL;
+ ptr += rlen;
+ len -= rlen;
+ }
+ }
+ ptr -= entry_made;
+ *ptr = '\0';
+
+ return str;
+}
+
+/*
+ * Returns string with CPU mask.
+ */
+char *cpumask_create(char *str, size_t len,
+ cpu_set_t *set, size_t setsize)
+{
+ char *ptr = str;
+ char *ret = NULL;
+ int cpu;
+
+ for (cpu = cpuset_nbits(setsize) - 4; cpu >= 0; cpu -= 4) {
+ char val = 0;
+
+ if (len == (ptr - str))
+ break;
+
+ if (CPU_ISSET_S(cpu, setsize, set))
+ val |= 1;
+ if (CPU_ISSET_S(cpu + 1, setsize, set))
+ val |= 2;
+ if (CPU_ISSET_S(cpu + 2, setsize, set))
+ val |= 4;
+ if (CPU_ISSET_S(cpu + 3, setsize, set))
+ val |= 8;
+
+ if (!ret && val)
+ ret = ptr;
+ *ptr++ = val_to_char(val);
+ }
+ *ptr = '\0';
+ return ret ? ret : ptr - 1;
+}
+
+/*
+ * Parses string with list of CPU ranges.
+ */
+int cpumask_parse(const char *str, cpu_set_t *set, size_t setsize)
+{
+ int len = strlen(str);
+ const char *ptr = str + len - 1;
+ int cpu = 0;
+
+ /* skip 0x, it's all hex anyway */
+ if (len > 1 && !memcmp(str, "0x", 2L))
+ str += 2;
+
+ CPU_ZERO_S(setsize, set);
+
+ while (ptr >= str) {
+ char val;
+
+ /* cpu masks in /sys uses comma as a separator */
+ if (*ptr == ',')
+ ptr--;
+
+ val = char_to_val(*ptr);
+ if (val == (char) -1)
+ return -1;
+ if (val & 1)
+ CPU_SET_S(cpu, setsize, set);
+ if (val & 2)
+ CPU_SET_S(cpu + 1, setsize, set);
+ if (val & 4)
+ CPU_SET_S(cpu + 2, setsize, set);
+ if (val & 8)
+ CPU_SET_S(cpu + 3, setsize, set);
+ len--;
+ ptr--;
+ cpu += 4;
+ }
+
+ return 0;
+}
+
+/*
+ * Parses string with CPUs mask.
+ */
+int cpulist_parse(const char *str, cpu_set_t *set, size_t setsize)
+{
+ const char *p, *q;
+ q = str;
+
+ CPU_ZERO_S(setsize, set);
+
+ while (p = q, q = nexttoken(q, ','), p) {
+ unsigned int a; /* beginning of range */
+ unsigned int b; /* end of range */
+ unsigned int s; /* stride */
+ const char *c1, *c2;
+
+ if (sscanf(p, "%u", &a) < 1)
+ return 1;
+ b = a;
+ s = 1;
+
+ c1 = nexttoken(p, '-');
+ c2 = nexttoken(p, ',');
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ if (sscanf(c1, "%u", &b) < 1)
+ return 1;
+ c1 = nexttoken(c1, ':');
+ if (c1 != NULL && (c2 == NULL || c1 < c2))
+ if (sscanf(c1, "%u", &s) < 1) {
+ return 1;
+ }
+ }
+
+ if (!(a <= b))
+ return 1;
+ while (a <= b) {
+ CPU_SET_S(a, setsize, set);
+ a += s;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef TEST_PROGRAM
+
+#include <err.h>
+#include <getopt.h>
+
+int main(int argc, char *argv[])
+{
+ cpu_set_t *set;
+ size_t setsize, buflen, nbits;
+ char *buf, *mask = NULL, *range = NULL;
+ int ncpus = 2048, rc, c;
+
+ struct option longopts[] = {
+ { "ncpus", 1, 0, 'n' },
+ { "mask", 1, 0, 'm' },
+ { "range", 1, 0, 'r' },
+ { NULL, 0, 0, 0 }
+ };
+
+ while ((c = getopt_long(argc, argv, "n:m:r:", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'n':
+ ncpus = atoi(optarg);
+ break;
+ case 'm':
+ mask = strdup(optarg);
+ break;
+ case 'r':
+ range = strdup(optarg);
+ break;
+ default:
+ goto usage_err;
+ }
+ }
+
+ if (!mask && !range)
+ goto usage_err;
+
+ set = cpuset_alloc(ncpus, &setsize, &nbits);
+ if (!set)
+ err(EXIT_FAILURE, "failed to allocate cpu set");
+
+ /*
+ fprintf(stderr, "ncpus: %d, cpuset bits: %zd, cpuset bytes: %zd\n",
+ ncpus, nbits, setsize);
+ */
+
+ buflen = 7 * nbits;
+ buf = malloc(buflen);
+ if (!buf)
+ err(EXIT_FAILURE, "failed to allocate cpu set buffer");
+
+ if (mask)
+ rc = cpumask_parse(mask, set, setsize);
+ else
+ rc = cpulist_parse(range, set, setsize);
+
+ if (rc)
+ errx(EXIT_FAILURE, "failed to parse string: %s", mask ? : range);
+
+ printf("%-15s = %15s ", mask ? : range,
+ cpumask_create(buf, buflen, set, setsize));
+ printf("[%s]\n", cpulist_create(buf, buflen, set, setsize));
+
+ free(buf);
+ cpuset_free(set);
+
+ return EXIT_SUCCESS;
+
+usage_err:
+ fprintf(stderr,
+ "usage: %s [--ncpus <num>] --mask <mask> | --range <list>",
+ program_invocation_short_name);
+ exit(EXIT_FAILURE);
+}
+#endif
diff --git a/lib/ismounted.c b/lib/ismounted.c
index 94fe425c..592df308 100644
--- a/lib/ismounted.c
+++ b/lib/ismounted.c
@@ -11,14 +11,21 @@
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
+#if HAVE_MNTENT_H
#include <mntent.h>
+#endif
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>
#include <sys/param.h>
+#ifdef __APPLE__
+#include <sys/ucred.h>
+#include <sys/mount.h>
+#endif
#include "pathnames.h"
#include "ismounted.h"
+#include "c.h"
#ifdef HAVE_MNTENT_H
/*
diff --git a/lib/tt.c b/lib/tt.c
new file mode 100644
index 00000000..7d87bf54
--- /dev/null
+++ b/lib/tt.c
@@ -0,0 +1,725 @@
+/*
+ * 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>
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include "nls.h"
+#include "widechar.h"
+#include "c.h"
+#include "tt.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 mbs_width(_s) mbstowcs(NULL, _s, 0)
+
+#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,
+};
+
+#else /* !HAVE_WIDECHAR */
+# define mbs_width strlen(_s)
+#endif /* !HAVE_WIDECHAR */
+
+#define is_last_column(_tb, _cl) \
+ list_last_entry(&(_cl)->cl_columns, &(_tb)->tb_columns)
+
+/* TODO: move to lib/mbalign.c */
+#ifdef HAVE_WIDECHAR
+static size_t wc_truncate (wchar_t *wc, size_t width)
+{
+ size_t cells = 0;
+ int next_cells = 0;
+
+ while (*wc)
+ {
+ next_cells = wcwidth (*wc);
+ if (next_cells == -1) /* non printable */
+ {
+ *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
+ next_cells = 1;
+ }
+ if (cells + next_cells > width)
+ break;
+ cells += next_cells;
+ wc++;
+ }
+ *wc = L'\0';
+ return cells;
+}
+#endif
+
+
+/* TODO: move to lib/mbalign.c */
+static size_t mbs_truncate(char *str, size_t width)
+{
+ size_t bytes = strlen(str) + 1;
+#ifdef HAVE_WIDECHAR
+ size_t sz = mbs_width(str);
+ wchar_t *wcs = NULL;
+ int rc = -1;
+
+ if (sz <= width)
+ return sz; /* truncate is unnecessary */
+
+ if (sz == (size_t) -1)
+ goto done;
+
+ wcs = malloc(sz * sizeof(wchar_t));
+ if (!wcs)
+ goto done;
+
+ if (!mbstowcs(wcs, str, sz))
+ goto done;
+ rc = wc_truncate(wcs, width);
+ wcstombs(str, wcs, bytes);
+done:
+ free(wcs);
+ return rc;
+#else
+ if (width < bytes) {
+ str[width] = '\0';
+ return width;
+ }
+ return bytes; /* truncate is unnecessary */
+#endif
+}
+
+/*
+ * @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);
+
+#ifdef 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;
+ return tb;
+}
+
+void tt_free_table(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);
+ }
+ 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 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, int 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;
+ ln->data[cl->seqnum] = data;
+ return 0;
+}
+
+static int get_terminal_width(void)
+{
+#ifdef TIOCGSIZE
+ struct ttysize t_win;
+#endif
+#ifdef TIOCGWINSZ
+ struct winsize w_win;
+#endif
+ const char *cp;
+
+#ifdef TIOCGSIZE
+ if (ioctl (0, TIOCGSIZE, &t_win) == 0)
+ return t_win.ts_cols;
+#endif
+#ifdef TIOCGWINSZ
+ if (ioctl (0, TIOCGWINSZ, &w_win) == 0)
+ return w_win.ws_col;
+#endif
+ cp = getenv("COLUMNS");
+ if (cp)
+ return strtol(cp, NULL, 10);
+ 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_last_entry(&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;
+ }
+ 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_last_entry(&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;
+}
+
+static void recount_widths(struct tt *tb, char *buf, size_t bufsz)
+{
+ struct list_head *p;
+ int width = 0, trunc_only;
+
+ /* 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);
+ struct list_head *lp;
+
+ 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_width(data) : 0;
+
+ if (cl->width < len)
+ cl->width = len;
+ }
+ }
+
+ /* set minimal width (= size of column header)
+ */
+ list_for_each(p, &tb->tb_columns) {
+ struct tt_column *cl =
+ list_entry(p, struct tt_column, cl_columns);
+
+ cl->width_min = mbs_width(cl->name);
+
+ if (cl->width < cl->width_min)
+ cl->width = cl->width_min;
+ else if (cl->width_hint >= 1 &&
+ cl->width_min < (int) cl->width_hint)
+ cl->width = (int) cl->width_hint;
+
+ width += cl->width + (is_last_column(tb, cl) ? 0 : 1);
+ }
+
+ if (width == tb->termwidth)
+ goto leave;
+ if (width < tb->termwidth) {
+ /* cool, use the extra space for the last column */
+ struct tt_column *cl = list_entry(
+ tb->tb_columns.prev, struct tt_column, cl_columns);
+
+ cl->width += tb->termwidth - width;
+ goto leave;
+ }
+
+ /* 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) {
+ int 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)
+ 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_TRUNCATE))
+ continue;
+ if (cl->width == cl->width_min)
+ continue;
+ if (cl->width > cl->width_hint * tb->termwidth) {
+ cl->width--;
+ width--;
+ }
+ }
+ if (org == width) {
+ if (trunc_only)
+ trunc_only = 0;
+ else
+ break;
+ }
+ }
+leave:
+/*
+ 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=%d [%d]\n",
+ cl->name, cl->width,
+ cl->width_hint > 1 ? (int) cl->width_hint :
+ (int) (cl->width_hint * tb->termwidth));
+ }
+*/
+ return;
+}
+
+/* note that this function modifies @data
+ */
+static void print_data(struct tt *tb, struct tt_column *cl, char *data)
+{
+ size_t len, i;
+ int width;
+
+ if (!data)
+ data = "";
+
+ /* raw mode */
+ if (tb->flags & TT_FL_RAW) {
+ fputs(data, stdout);
+ if (!is_last_column(tb, cl))
+ fputc(' ', stdout);
+ return;
+ }
+
+ /* note that 'len' and 'width' are number of cells, not bytes */
+ len = mbs_width(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_TRUNCATE)) {
+ len = mbs_truncate(data, width);
+ if (!data || len == (size_t) -1) {
+ len = 0;
+ data = NULL;
+ }
+ }
+ if (data)
+ 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_TRUNCATE)) {
+ fputc('\n', stdout);
+ for (i = 0; i <= cl->seqnum; i++) {
+ struct tt_column *x = tt_get_column(tb, i);
+ printf("%*s ", -x->width, " ");
+ }
+ } else
+ fputc(' ', stdout); /* columns separator */
+ }
+}
+
+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->flags & TT_FL_NOHEADINGS) || 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;
+
+ if (!tb)
+ return -1;
+ if (!tb->termwidth) {
+ tb->termwidth = get_terminal_width();
+ if (tb->termwidth <= 0)
+ tb->termwidth = 80;
+ }
+ line = malloc(tb->termwidth);
+ if (!line)
+ return -1;
+ if (!(tb->flags & TT_FL_RAW))
+ recount_widths(tb, line, tb->termwidth);
+ if (tb->flags & TT_FL_TREE)
+ print_tree(tb, line, tb->termwidth);
+ else
+ print_table(tb, line, tb->termwidth);
+
+ free(line);
+ return 0;
+}
+
+#ifdef TEST_PROGRAM
+#include <err.h>
+#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], "--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_TRUNCATE);
+ 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