+static const char *tipc_netid_name(int type)
+{
+ switch (type) {
+ case SOCK_STREAM:
+ return "ti_st";
+ case SOCK_DGRAM:
+ return "ti_dg";
+ case SOCK_RDM:
+ return "ti_rd";
+ case SOCK_SEQPACKET:
+ return "ti_sq";
+ default:
+ return "???";
+ }
+}
+
+/* Allocate and initialize a new buffer chunk */
+static struct buf_chunk *buf_chunk_new(void)
+{
+ struct buf_chunk *new = malloc(BUF_CHUNK);
+
+ if (!new)
+ abort();
+
+ new->next = NULL;
+
+ /* This is also the last block */
+ buffer.tail = new;
+
+ /* Next token will be stored at the beginning of chunk data area, and
+ * its initial length is zero.
+ */
+ buffer.cur = (struct buf_token *)new->data;
+ buffer.cur->len = 0;
+
+ new->end = buffer.cur->data;
+
+ buffer.chunks++;
+
+ return new;
+}
+
+/* Return available tail room in given chunk */
+static int buf_chunk_avail(struct buf_chunk *chunk)
+{
+ return BUF_CHUNK - offsetof(struct buf_chunk, data) -
+ (chunk->end - chunk->data);
+}
+
+/* Update end pointer and token length, link new chunk if we hit the end of the
+ * current one. Return -EAGAIN if we got a new chunk, caller has to print again.
+ */
+static int buf_update(int len)
+{
+ struct buf_chunk *chunk = buffer.tail;
+ struct buf_token *t = buffer.cur;
+
+ /* Claim success if new content fits in the current chunk, and anyway
+ * if this is the first token in the chunk: in the latter case,
+ * allocating a new chunk won't help, so we'll just cut the output.
+ */
+ if ((len < buf_chunk_avail(chunk) && len != -1 /* glibc < 2.0.6 */) ||
+ t == (struct buf_token *)chunk->data) {
+ len = min(len, buf_chunk_avail(chunk));
+
+ /* Total field length can't exceed 2^16 bytes, cut as needed */
+ len = min(len, USHRT_MAX - t->len);
+
+ chunk->end += len;
+ t->len += len;
+ return 0;
+ }
+
+ /* Content truncated, time to allocate more */
+ chunk->next = buf_chunk_new();
+
+ /* Copy current token over to new chunk, including length descriptor */
+ memcpy(chunk->next->data, t, sizeof(t->len) + t->len);
+ chunk->next->end += t->len;
+
+ /* Discard partially written field in old chunk */
+ chunk->end -= t->len + sizeof(t->len);
+
+ return -EAGAIN;
+}
+
+/* Append content to buffer as part of the current field */
+__attribute__((format(printf, 1, 2)))
+static void out(const char *fmt, ...)
+{
+ struct column *f = current_field;
+ va_list args;
+ char *pos;
+ int len;
+
+ if (f->disabled)
+ return;
+
+ if (!buffer.head)
+ buffer.head = buf_chunk_new();
+
+again: /* Append to buffer: if we have a new chunk, print again */
+
+ pos = buffer.cur->data + buffer.cur->len;
+ va_start(args, fmt);
+
+ /* Limit to tail room. If we hit the limit, buf_update() will tell us */
+ len = vsnprintf(pos, buf_chunk_avail(buffer.tail), fmt, args);
+ va_end(args);
+
+ if (buf_update(len))
+ goto again;
+}
+
+static int print_left_spacing(struct column *f, int stored, int printed)
+{
+ int s;
+
+ if (!f->width || f->align == ALIGN_LEFT)
+ return 0;
+
+ s = f->width - stored - printed;
+ if (f->align == ALIGN_CENTER)
+ /* If count of total spacing is odd, shift right by one */
+ s = (s + 1) / 2;
+
+ if (s > 0)
+ return printf("%*c", s, ' ');
+
+ return 0;
+}
+
+static void print_right_spacing(struct column *f, int printed)
+{
+ int s;
+
+ if (!f->width || f->align == ALIGN_RIGHT)
+ return;
+
+ s = f->width - printed;
+ if (f->align == ALIGN_CENTER)
+ s /= 2;
+
+ if (s > 0)
+ printf("%*c", s, ' ');
+}
+
+/* Done with field: update buffer pointer, start new token after current one */
+static void field_flush(struct column *f)
+{
+ struct buf_chunk *chunk;
+ unsigned int pad;
+
+ if (f->disabled)
+ return;
+
+ chunk = buffer.tail;
+ pad = buffer.cur->len % 2;
+
+ if (buffer.cur->len > f->max_len)
+ f->max_len = buffer.cur->len;
+
+ /* We need a new chunk if we can't store the next length descriptor.
+ * Mind the gap between end of previous token and next aligned position
+ * for length descriptor.
+ */
+ if (buf_chunk_avail(chunk) - pad < sizeof(buffer.cur->len)) {
+ chunk->end += pad;
+ chunk->next = buf_chunk_new();
+ return;
+ }
+
+ buffer.cur = (struct buf_token *)(buffer.cur->data +
+ LEN_ALIGN(buffer.cur->len));
+ buffer.cur->len = 0;
+ buffer.tail->end = buffer.cur->data;
+}
+
+static int field_is_last(struct column *f)
+{
+ return f - columns == COL_MAX - 1;
+}
+
+/* Get the next available token in the buffer starting from the current token */
+static struct buf_token *buf_token_next(struct buf_token *cur)
+{
+ struct buf_chunk *chunk = buffer.tail;
+
+ /* If we reached the end of chunk contents, get token from next chunk */
+ if (cur->data + LEN_ALIGN(cur->len) == chunk->end) {
+ buffer.tail = chunk = chunk->next;
+ return chunk ? (struct buf_token *)chunk->data : NULL;
+ }
+
+ return (struct buf_token *)(cur->data + LEN_ALIGN(cur->len));
+}
+
+/* Free up all allocated buffer chunks */
+static void buf_free_all(void)
+{
+ struct buf_chunk *tmp;
+
+ for (buffer.tail = buffer.head; buffer.tail; ) {
+ tmp = buffer.tail;
+ buffer.tail = buffer.tail->next;
+ free(tmp);
+ }
+ buffer.head = NULL;
+ buffer.chunks = 0;
+}
+
+/* Get current screen width, default to 80 columns if TIOCGWINSZ fails */
+static int render_screen_width(void)
+{
+ int width = 80;
+
+ if (isatty(STDOUT_FILENO)) {
+ struct winsize w;
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) {
+ if (w.ws_col > 0)
+ width = w.ws_col;
+ }
+ }
+
+ return width;
+}
+
+/* Calculate column width from contents length. If columns don't fit on one
+ * line, break them into the least possible amount of lines and keep them
+ * aligned across lines. Available screen space is equally spread between fields
+ * as additional spacing.
+ */
+static void render_calc_width(void)
+{
+ int screen_width = render_screen_width();
+ struct column *c, *eol = columns - 1;
+ int first, len = 0, linecols = 0;
+
+ /* First pass: set width for each column to measured content length */
+ for (first = 1, c = columns; c - columns < COL_MAX; c++) {
+ if (c->disabled)
+ continue;
+
+ if (!first && c->max_len)
+ c->width = c->max_len + strlen(c->ldelim);
+ else
+ c->width = c->max_len;
+
+ /* But don't exceed screen size. If we exceed the screen size
+ * for even a single field, it will just start on a line of its
+ * own and then naturally wrap.
+ */
+ c->width = min(c->width, screen_width);
+
+ if (c->width)
+ first = 0;
+ }
+
+ /* Second pass: find out newlines and distribute available spacing */
+ for (c = columns; c - columns < COL_MAX; c++) {
+ int pad, spacing, rem, last;
+ struct column *tmp;
+
+ if (!c->width)
+ continue;
+
+ linecols++;
+ len += c->width;
+
+ for (last = 1, tmp = c + 1; tmp - columns < COL_MAX; tmp++) {
+ if (tmp->width) {
+ last = 0;
+ break;
+ }
+ }
+
+ if (!last && len < screen_width) {
+ /* Columns fit on screen so far, nothing to do yet */
+ continue;
+ }
+
+ if (len == screen_width) {
+ /* Exact fit, just start with new line */
+ goto newline;
+ }
+
+ if (len > screen_width) {
+ /* Screen width exceeded: go back one column */
+ len -= c->width;
+ c--;
+ linecols--;
+ }
+
+ /* Distribute remaining space to columns on this line */
+ pad = screen_width - len;
+ spacing = pad / linecols;
+ rem = pad % linecols;
+ for (tmp = c; tmp > eol; tmp--) {
+ if (!tmp->width)
+ continue;
+
+ tmp->width += spacing;
+ if (rem) {
+ tmp->width++;
+ rem--;
+ }
+ }
+
+newline:
+ /* Line break: reset line counters, mark end-of-line */
+ eol = c;
+ len = 0;
+ linecols = 0;
+ }
+}
+
+/* Render buffered output with spacing and delimiters, then free up buffers */
+static void render(void)
+{
+ struct buf_token *token;
+ int printed, line_started = 0;
+ struct column *f;
+
+ if (!buffer.head)
+ return;
+
+ token = (struct buf_token *)buffer.head->data;
+
+ /* Ensure end alignment of last token, it wasn't necessarily flushed */
+ buffer.tail->end += buffer.cur->len % 2;
+
+ render_calc_width();
+
+ /* Rewind and replay */
+ buffer.tail = buffer.head;
+
+ f = columns;
+ while (!f->width)
+ f++;
+
+ while (token) {
+ /* Print left delimiter only if we already started a line */
+ if (line_started++)
+ printed = printf("%s", f->ldelim);
+ else
+ printed = 0;
+
+ /* Print field content from token data with spacing */
+ printed += print_left_spacing(f, token->len, printed);
+ printed += fwrite(token->data, 1, token->len, stdout);
+ print_right_spacing(f, printed);
+
+ /* Go to next non-empty field, deal with end-of-line */
+ do {
+ if (field_is_last(f)) {
+ printf("\n");
+ f = columns;
+ line_started = 0;
+ } else {
+ f++;
+ }
+ } while (f->disabled);
+
+ token = buf_token_next(token);
+ }
+
+ buf_free_all();
+ current_field = columns;
+}
+
+/* Move to next field, and render buffer if we reached the maximum number of
+ * chunks, at the last field in a line.
+ */
+static void field_next(void)
+{
+ if (field_is_last(current_field) && buffer.chunks >= BUF_CHUNKS_MAX) {
+ render();
+ return;
+ }
+
+ field_flush(current_field);
+ if (field_is_last(current_field))
+ current_field = columns;
+ else
+ current_field++;
+}
+
+/* Walk through fields and flush them until we reach the desired one */
+static void field_set(enum col_id id)
+{
+ while (id != current_field - columns)
+ field_next();
+}
+
+/* Print header for all non-empty columns */
+static void print_header(void)
+{
+ while (!field_is_last(current_field)) {
+ if (!current_field->disabled)
+ out("%s", current_field->header);
+ field_next();
+ }
+}
+