tmux/format.c
deraadt 8a8e2eb04a correctly use HOST_NAME_MAX.
Some notes:
POSIX HOST_NAME_MAX doesn't include the NUL.
POSIX LOGIN_NAME_MAX and TTY_NAME_MAX do include the NUL.

BSD MAXHOSTNAMELEN includes the NUL.  Actually, most of the historical
BSD MAX* defines did include the NUL, except for the historical
mistake of utmp fields without NULs in the string, which directly led
to strncpy..  just showing how error prone this kind of accounting is.
CSRG did right.  Somehow POSIX missed the memo on the concepts of
carefulness and consistancy, and we are still paying the price when
people trip over this.  Of course, glibc is even more amazing (that is
a hint to blackhats)

ok guenther
2015-01-11 04:14:40 +00:00

685 lines
16 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2011 Nicholas Marriott <nicm@users.sourceforge.net>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "tmux.h"
/*
* Build a list of key-value pairs and use them to expand #{key} entries in a
* string.
*/
int format_replace(struct format_tree *, const char *, size_t, char **,
size_t *, size_t *);
char *format_get_command(struct window_pane *);
void format_window_pane_tabs(struct format_tree *, struct window_pane *);
/* Entry in format tree. */
struct format_entry {
char *key;
char *value;
RB_ENTRY(format_entry) entry;
};
/* Tree of format entries. */
struct format_tree {
struct window *w;
struct session *s;
RB_HEAD(format_rb_tree, format_entry) tree;
};
/* Format key-value replacement entry. */
int format_cmp(struct format_entry *, struct format_entry *);
RB_PROTOTYPE(format_rb_tree, format_entry, entry, format_cmp);
RB_GENERATE(format_rb_tree, format_entry, entry, format_cmp);
/* Format tree comparison function. */
int
format_cmp(struct format_entry *fe1, struct format_entry *fe2)
{
return (strcmp(fe1->key, fe2->key));
}
/* Single-character uppercase aliases. */
const char *format_upper[] = {
NULL, /* A */
NULL, /* B */
NULL, /* C */
"pane_id", /* D */
NULL, /* E */
"window_flags", /* F */
NULL, /* G */
"host", /* H */
"window_index", /* I */
NULL, /* J */
NULL, /* K */
NULL, /* L */
NULL, /* M */
NULL, /* N */
NULL, /* O */
"pane_index", /* P */
NULL, /* Q */
NULL, /* R */
"session_name", /* S */
"pane_title", /* T */
NULL, /* U */
NULL, /* V */
"window_name", /* W */
NULL, /* X */
NULL, /* Y */
NULL /* Z */
};
/* Single-character lowercase aliases. */
const char *format_lower[] = {
NULL, /* a */
NULL, /* b */
NULL, /* c */
NULL, /* d */
NULL, /* e */
NULL, /* f */
NULL, /* g */
"host_short", /* h */
NULL, /* i */
NULL, /* j */
NULL, /* k */
NULL, /* l */
NULL, /* m */
NULL, /* n */
NULL, /* o */
NULL, /* p */
NULL, /* q */
NULL, /* r */
NULL, /* s */
NULL, /* t */
NULL, /* u */
NULL, /* v */
NULL, /* w */
NULL, /* x */
NULL, /* y */
NULL /* z */
};
/* Create a new tree. */
struct format_tree *
format_create(void)
{
struct format_tree *ft;
char host[HOST_NAME_MAX+1], *ptr;
ft = xcalloc(1, sizeof *ft);
RB_INIT(&ft->tree);
if (gethostname(host, sizeof host) == 0) {
format_add(ft, "host", "%s", host);
if ((ptr = strchr(host, '.')) != NULL)
*ptr = '\0';
format_add(ft, "host_short", "%s", host);
}
return (ft);
}
/* Free a tree. */
void
format_free(struct format_tree *ft)
{
struct format_entry *fe, *fe1;
RB_FOREACH_SAFE(fe, format_rb_tree, &ft->tree, fe1) {
RB_REMOVE(format_rb_tree, &ft->tree, fe);
free(fe->value);
free(fe->key);
free(fe);
}
free(ft);
}
/* Add a key-value pair. */
void
format_add(struct format_tree *ft, const char *key, const char *fmt, ...)
{
struct format_entry *fe;
struct format_entry *fe_now;
va_list ap;
fe = xmalloc(sizeof *fe);
fe->key = xstrdup(key);
va_start(ap, fmt);
xvasprintf(&fe->value, fmt, ap);
va_end(ap);
fe_now = RB_INSERT(format_rb_tree, &ft->tree, fe);
if (fe_now != NULL) {
free(fe_now->value);
fe_now->value = fe->value;
free(fe->key);
free(fe);
}
}
/* Find a format entry. */
const char *
format_find(struct format_tree *ft, const char *key)
{
struct format_entry *fe, fe_find;
struct options_entry *o;
static char s[16];
o = options_find(&global_options, key);
if (o == NULL && ft->w != NULL)
o = options_find(&ft->w->options, key);
if (o == NULL)
o = options_find(&global_w_options, key);
if (o == NULL && ft->s != NULL)
o = options_find(&ft->s->options, key);
if (o == NULL)
o = options_find(&global_s_options, key);
if (o != NULL) {
switch (o->type) {
case OPTIONS_STRING:
return (o->str);
case OPTIONS_NUMBER:
snprintf(s, sizeof s, "%lld", o->num);
return (s);
case OPTIONS_STYLE:
return (style_tostring(&o->style));
}
}
fe_find.key = (char *) key;
fe = RB_FIND(format_rb_tree, &ft->tree, &fe_find);
if (fe == NULL)
return (NULL);
return (fe->value);
}
/*
* Replace a key/value pair in buffer. #{blah} is expanded directly,
* #{?blah,a,b} is replace with a if blah exists and is nonzero else b.
*/
int
format_replace(struct format_tree *ft, const char *key, size_t keylen,
char **buf, size_t *len, size_t *off)
{
char *copy, *copy0, *endptr, *ptr, *saved, *trimmed;
const char *value;
size_t valuelen;
u_long limit = 0;
/* Make a copy of the key. */
copy0 = copy = xmalloc(keylen + 1);
memcpy(copy, key, keylen);
copy[keylen] = '\0';
/* Is there a length limit or whatnot? */
if (!islower((u_char) *copy) && *copy != '@' && *copy != '?') {
while (*copy != ':' && *copy != '\0') {
switch (*copy) {
case '=':
errno = 0;
limit = strtoul(copy + 1, &endptr, 10);
if (errno == ERANGE && limit == ULONG_MAX)
goto fail;
copy = endptr;
break;
default:
copy++;
break;
}
}
if (*copy != ':')
goto fail;
copy++;
}
/*
* Is this a conditional? If so, check it exists and extract either the
* first or second element. If not, look up the key directly.
*/
if (*copy == '?') {
ptr = strchr(copy, ',');
if (ptr == NULL)
goto fail;
*ptr = '\0';
value = format_find(ft, copy + 1);
if (value != NULL && *value != '\0' &&
(value[0] != '0' || value[1] != '\0')) {
value = ptr + 1;
ptr = strchr(value, ',');
if (ptr == NULL)
goto fail;
*ptr = '\0';
} else {
ptr = strchr(ptr + 1, ',');
if (ptr == NULL)
goto fail;
value = ptr + 1;
}
saved = format_expand(ft, value);
value = saved;
} else {
value = format_find(ft, copy);
if (value == NULL)
value = "";
saved = NULL;
}
/* Truncate the value if needed. */
if (limit != 0) {
value = trimmed = utf8_trimcstr(value, limit);
free(saved);
saved = trimmed;
}
valuelen = strlen(value);
/* Expand the buffer and copy in the value. */
while (*len - *off < valuelen + 1) {
*buf = xreallocarray(*buf, 2, *len);
*len *= 2;
}
memcpy(*buf + *off, value, valuelen);
*off += valuelen;
free(saved);
free(copy0);
return (0);
fail:
free(copy0);
return (-1);
}
/* Expand keys in a template. */
char *
format_expand(struct format_tree *ft, const char *fmt)
{
char *buf;
const char *ptr, *s;
size_t off, len, n;
int ch, brackets;
len = 64;
buf = xmalloc(len);
off = 0;
while (*fmt != '\0') {
if (*fmt != '#') {
while (len - off < 2) {
buf = xreallocarray(buf, 2, len);
len *= 2;
}
buf[off++] = *fmt++;
continue;
}
fmt++;
ch = (u_char) *fmt++;
switch (ch) {
case '{':
brackets = 1;
for (ptr = fmt; *ptr != '\0'; ptr++) {
if (*ptr == '{')
brackets++;
if (*ptr == '}' && --brackets == 0)
break;
}
if (*ptr != '}' || brackets != 0)
break;
n = ptr - fmt;
if (format_replace(ft, fmt, n, &buf, &len, &off) != 0)
break;
fmt += n + 1;
continue;
case '#':
while (len - off < 2) {
buf = xreallocarray(buf, 2, len);
len *= 2;
}
buf[off++] = '#';
continue;
default:
s = NULL;
if (ch >= 'A' && ch <= 'Z')
s = format_upper[ch - 'A'];
else if (ch >= 'a' && ch <= 'z')
s = format_lower[ch - 'a'];
if (s == NULL) {
while (len - off < 3) {
buf = xreallocarray(buf, 2, len);
len *= 2;
}
buf[off++] = '#';
buf[off++] = ch;
continue;
}
n = strlen(s);
if (format_replace(ft, s, n, &buf, &len, &off) != 0)
break;
continue;
}
break;
}
buf[off] = '\0';
return (buf);
}
/* Get command name for format. */
char *
format_get_command(struct window_pane *wp)
{
char *cmd, *out;
cmd = get_proc_name(wp->fd, wp->tty);
if (cmd == NULL || *cmd == '\0') {
free(cmd);
cmd = cmd_stringify_argv(wp->argc, wp->argv);
if (cmd == NULL || *cmd == '\0') {
free(cmd);
cmd = xstrdup(wp->shell);
}
}
out = parse_window_name(cmd);
free(cmd);
return (out);
}
/* Set default format keys for a session. */
void
format_session(struct format_tree *ft, struct session *s)
{
struct session_group *sg;
char *tim;
time_t t;
ft->s = s;
format_add(ft, "session_name", "%s", s->name);
format_add(ft, "session_windows", "%u", winlink_count(&s->windows));
format_add(ft, "session_width", "%u", s->sx);
format_add(ft, "session_height", "%u", s->sy);
format_add(ft, "session_id", "$%u", s->id);
sg = session_group_find(s);
format_add(ft, "session_grouped", "%d", sg != NULL);
if (sg != NULL)
format_add(ft, "session_group", "%u", session_group_index(sg));
t = s->creation_time.tv_sec;
format_add(ft, "session_created", "%lld", (long long) t);
tim = ctime(&t);
*strchr(tim, '\n') = '\0';
format_add(ft, "session_created_string", "%s", tim);
format_add(ft, "session_attached", "%u", s->attached);
format_add(ft, "session_many_attached", "%u", s->attached > 1);
}
/* Set default format keys for a client. */
void
format_client(struct format_tree *ft, struct client *c)
{
char *tim;
time_t t;
struct session *s;
if (ft->s == NULL)
ft->s = c->session;
format_add(ft, "client_height", "%u", c->tty.sy);
format_add(ft, "client_width", "%u", c->tty.sx);
if (c->tty.path != NULL)
format_add(ft, "client_tty", "%s", c->tty.path);
if (c->tty.termname != NULL)
format_add(ft, "client_termname", "%s", c->tty.termname);
t = c->creation_time.tv_sec;
format_add(ft, "client_created", "%lld", (long long) t);
tim = ctime(&t);
*strchr(tim, '\n') = '\0';
format_add(ft, "client_created_string", "%s", tim);
t = c->activity_time.tv_sec;
format_add(ft, "client_activity", "%lld", (long long) t);
tim = ctime(&t);
*strchr(tim, '\n') = '\0';
format_add(ft, "client_activity_string", "%s", tim);
format_add(ft, "client_prefix", "%d", !!(c->flags & CLIENT_PREFIX));
if (c->tty.flags & TTY_UTF8)
format_add(ft, "client_utf8", "%d", 1);
else
format_add(ft, "client_utf8", "%d", 0);
if (c->flags & CLIENT_READONLY)
format_add(ft, "client_readonly", "%d", 1);
else
format_add(ft, "client_readonly", "%d", 0);
s = c->session;
if (s != NULL)
format_add(ft, "client_session", "%s", s->name);
s = c->last_session;
if (s != NULL && session_alive(s))
format_add(ft, "client_last_session", "%s", s->name);
}
/* Set default format keys for a window. */
void
format_window(struct format_tree *ft, struct window *w)
{
char *layout;
ft->w = w;
layout = layout_dump(w);
format_add(ft, "window_id", "@%u", w->id);
format_add(ft, "window_name", "%s", w->name);
format_add(ft, "window_width", "%u", w->sx);
format_add(ft, "window_height", "%u", w->sy);
format_add(ft, "window_layout", "%s", layout);
format_add(ft, "window_panes", "%u", window_count_panes(w));
format_add(ft, "window_zoomed_flag", "%u",
!!(w->flags & WINDOW_ZOOMED));
free(layout);
}
/* Set default format keys for a winlink. */
void
format_winlink(struct format_tree *ft, struct session *s, struct winlink *wl)
{
struct window *w = wl->window;
char *flags;
if (ft->w == NULL)
ft->w = wl->window;
flags = window_printable_flags(s, wl);
format_window(ft, w);
format_add(ft, "window_index", "%d", wl->idx);
format_add(ft, "window_flags", "%s", flags);
format_add(ft, "window_active", "%d", wl == s->curw);
format_add(ft, "window_bell_flag", "%u",
!!(wl->flags & WINLINK_BELL));
format_add(ft, "window_activity_flag", "%u",
!!(wl->flags & WINLINK_ACTIVITY));
format_add(ft, "window_silence_flag", "%u",
!!(wl->flags & WINLINK_SILENCE));
format_add(ft, "window_last_flag", "%u",
!!(wl == TAILQ_FIRST(&s->lastw)));
free(flags);
}
/* Add window pane tabs. */
void
format_window_pane_tabs(struct format_tree *ft, struct window_pane *wp)
{
struct evbuffer *buffer;
u_int i;
buffer = evbuffer_new();
for (i = 0; i < wp->base.grid->sx; i++) {
if (!bit_test(wp->base.tabs, i))
continue;
if (EVBUFFER_LENGTH(buffer) > 0)
evbuffer_add(buffer, ",", 1);
evbuffer_add_printf(buffer, "%d", i);
}
format_add(ft, "pane_tabs", "%.*s", (int) EVBUFFER_LENGTH(buffer),
EVBUFFER_DATA(buffer));
evbuffer_free(buffer);
}
/* Set default format keys for a window pane. */
void
format_window_pane(struct format_tree *ft, struct window_pane *wp)
{
struct grid *gd = wp->base.grid;
struct grid_line *gl;
unsigned long long size;
u_int i, idx;
char *cmd;
int status;
if (ft->w == NULL)
ft->w = wp->window;
size = 0;
for (i = 0; i < gd->hsize; i++) {
gl = &gd->linedata[i];
size += gl->cellsize * sizeof *gl->celldata;
}
size += gd->hsize * sizeof *gd->linedata;
format_add(ft, "history_size", "%u", gd->hsize);
format_add(ft, "history_limit", "%u", gd->hlimit);
format_add(ft, "history_bytes", "%llu", size);
if (window_pane_index(wp, &idx) != 0)
fatalx("index not found");
format_add(ft, "pane_index", "%u", idx);
format_add(ft, "pane_width", "%u", wp->sx);
format_add(ft, "pane_height", "%u", wp->sy);
format_add(ft, "pane_title", "%s", wp->base.title);
format_add(ft, "pane_id", "%%%u", wp->id);
format_add(ft, "pane_active", "%d", wp == wp->window->active);
format_add(ft, "pane_input_off", "%d", !!(wp->flags & PANE_INPUTOFF));
status = wp->status;
if (wp->fd == -1 && WIFEXITED(status))
format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status));
format_add(ft, "pane_dead", "%d", wp->fd == -1);
if (window_pane_visible(wp)) {
format_add(ft, "pane_left", "%u", wp->xoff);
format_add(ft, "pane_top", "%u", wp->yoff);
format_add(ft, "pane_right", "%u", wp->xoff + wp->sx - 1);
format_add(ft, "pane_bottom", "%u", wp->yoff + wp->sy - 1);
}
format_add(ft, "pane_in_mode", "%d", wp->screen != &wp->base);
format_add(ft, "pane_synchronized", "%d",
!!options_get_number(&wp->window->options, "synchronize-panes"));
if (wp->tty != NULL)
format_add(ft, "pane_tty", "%s", wp->tty);
format_add(ft, "pane_pid", "%ld", (long) wp->pid);
if ((cmd = cmd_stringify_argv(wp->argc, wp->argv)) != NULL) {
format_add(ft, "pane_start_command", "%s", cmd);
free(cmd);
}
if ((cmd = format_get_command(wp)) != NULL) {
format_add(ft, "pane_current_command", "%s", cmd);
free(cmd);
}
format_add(ft, "cursor_x", "%d", wp->base.cx);
format_add(ft, "cursor_y", "%d", wp->base.cy);
format_add(ft, "scroll_region_upper", "%d", wp->base.rupper);
format_add(ft, "scroll_region_lower", "%d", wp->base.rlower);
format_add(ft, "saved_cursor_x", "%d", wp->ictx.old_cx);
format_add(ft, "saved_cursor_y", "%d", wp->ictx.old_cy);
format_add(ft, "alternate_on", "%d", wp->saved_grid ? 1 : 0);
format_add(ft, "alternate_saved_x", "%d", wp->saved_cx);
format_add(ft, "alternate_saved_y", "%d", wp->saved_cy);
format_add(ft, "cursor_flag", "%d",
!!(wp->base.mode & MODE_CURSOR));
format_add(ft, "insert_flag", "%d",
!!(wp->base.mode & MODE_INSERT));
format_add(ft, "keypad_cursor_flag", "%d",
!!(wp->base.mode & MODE_KCURSOR));
format_add(ft, "keypad_flag", "%d",
!!(wp->base.mode & MODE_KKEYPAD));
format_add(ft, "wrap_flag", "%d",
!!(wp->base.mode & MODE_WRAP));
format_add(ft, "mouse_standard_flag", "%d",
!!(wp->base.mode & MODE_MOUSE_STANDARD));
format_add(ft, "mouse_button_flag", "%d",
!!(wp->base.mode & MODE_MOUSE_BUTTON));
format_add(ft, "mouse_utf8_flag", "%d",
!!(wp->base.mode & MODE_MOUSE_UTF8));
format_window_pane_tabs(ft, wp);
}
/* Set default format keys for paste buffer. */
void
format_paste_buffer(struct format_tree *ft, struct paste_buffer *pb,
int utf8flag)
{
char *s;
format_add(ft, "buffer_size", "%zu", pb->size);
format_add(ft, "buffer_name", "%s", pb->name);
s = paste_make_sample(pb, utf8flag);
format_add(ft, "buffer_sample", "%s", s);
free(s);
}