diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 11 | ||||
-rw-r--r-- | lib/cpuset.c | 380 | ||||
-rw-r--r-- | lib/ismounted.c | 7 | ||||
-rw-r--r-- | lib/tt.c | 725 |
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 |