Annotation of embedaddon/bird/nest/cli.c, revision 1.1
1.1 ! misho 1: /*
! 2: * BIRD Internet Routing Daemon -- Command-Line Interface
! 3: *
! 4: * (c) 1999--2000 Martin Mares <mj@ucw.cz>
! 5: *
! 6: * Can be freely distributed and used under the terms of the GNU GPL.
! 7: */
! 8:
! 9: /**
! 10: * DOC: Command line interface
! 11: *
! 12: * This module takes care of the BIRD's command-line interface (CLI).
! 13: * The CLI exists to provide a way to control BIRD remotely and to inspect
! 14: * its status. It uses a very simple textual protocol over a stream
! 15: * connection provided by the platform dependent code (on UNIX systems,
! 16: * it's a UNIX domain socket).
! 17: *
! 18: * Each session of the CLI consists of a sequence of request and replies,
! 19: * slightly resembling the FTP and SMTP protocols.
! 20: * Requests are commands encoded as a single line of text, replies are
! 21: * sequences of lines starting with a four-digit code followed by either
! 22: * a space (if it's the last line of the reply) or a minus sign (when the
! 23: * reply is going to continue with the next line), the rest of the line
! 24: * contains a textual message semantics of which depends on the numeric
! 25: * code. If a reply line has the same code as the previous one and it's
! 26: * a continuation line, the whole prefix can be replaced by a single
! 27: * white space character.
! 28: *
! 29: * Reply codes starting with 0 stand for `action successfully completed' messages,
! 30: * 1 means `table entry', 8 `runtime error' and 9 `syntax error'.
! 31: *
! 32: * Each CLI session is internally represented by a &cli structure and a
! 33: * resource pool containing all resources associated with the connection,
! 34: * so that it can be easily freed whenever the connection gets closed, not depending
! 35: * on the current state of command processing.
! 36: *
! 37: * The CLI commands are declared as a part of the configuration grammar
! 38: * by using the |CF_CLI| macro. When a command is received, it is processed
! 39: * by the same lexical analyzer and parser as used for the configuration, but
! 40: * it's switched to a special mode by prepending a fake token to the text,
! 41: * so that it uses only the CLI command rules. Then the parser invokes
! 42: * an execution routine corresponding to the command, which either constructs
! 43: * the whole reply and returns it back or (in case it expects the reply will be long)
! 44: * it prints a partial reply and asks the CLI module (using the @cont hook)
! 45: * to call it again when the output is transferred to the user.
! 46: *
! 47: * The @this_cli variable points to a &cli structure of the session being
! 48: * currently parsed, but it's of course available only in command handlers
! 49: * not entered using the @cont hook.
! 50: *
! 51: * TX buffer management works as follows: At cli.tx_buf there is a
! 52: * list of TX buffers (struct cli_out), cli.tx_write is the buffer
! 53: * currently used by the producer (cli_printf(), cli_alloc_out()) and
! 54: * cli.tx_pos is the buffer currently used by the consumer
! 55: * (cli_write(), in system dependent code). The producer uses
! 56: * cli_out.wpos ptr as the current write position and the consumer
! 57: * uses cli_out.outpos ptr as the current read position. When the
! 58: * producer produces something, it calls cli_write_trigger(). If there
! 59: * is not enough space in the current buffer, the producer allocates
! 60: * the new one. When the consumer processes everything in the buffer
! 61: * queue, it calls cli_written(), tha frees all buffers (except the
! 62: * first one) and schedules cli.event .
! 63: *
! 64: */
! 65:
! 66: #include "nest/bird.h"
! 67: #include "nest/cli.h"
! 68: #include "conf/conf.h"
! 69: #include "lib/string.h"
! 70:
! 71: pool *cli_pool;
! 72:
! 73: static byte *
! 74: cli_alloc_out(cli *c, int size)
! 75: {
! 76: struct cli_out *o;
! 77:
! 78: if (!(o = c->tx_write) || o->wpos + size > o->end)
! 79: {
! 80: if (!o && c->tx_buf)
! 81: o = c->tx_buf;
! 82: else
! 83: {
! 84: o = mb_alloc(c->pool, sizeof(struct cli_out) + CLI_TX_BUF_SIZE);
! 85: if (c->tx_write)
! 86: c->tx_write->next = o;
! 87: else
! 88: c->tx_buf = o;
! 89: o->wpos = o->outpos = o->buf;
! 90: o->end = o->buf + CLI_TX_BUF_SIZE;
! 91: }
! 92: c->tx_write = o;
! 93: if (!c->tx_pos)
! 94: c->tx_pos = o;
! 95: o->next = NULL;
! 96: }
! 97: o->wpos += size;
! 98: return o->wpos - size;
! 99: }
! 100:
! 101: /**
! 102: * cli_printf - send reply to a CLI connection
! 103: * @c: CLI connection
! 104: * @code: numeric code of the reply, negative for continuation lines
! 105: * @msg: a printf()-like formatting string.
! 106: *
! 107: * This function send a single line of reply to a given CLI connection.
! 108: * In works in all aspects like bsprintf() except that it automatically
! 109: * prepends the reply line prefix.
! 110: *
! 111: * Please note that if the connection can be already busy sending some
! 112: * data in which case cli_printf() stores the output to a temporary buffer,
! 113: * so please avoid sending a large batch of replies without waiting
! 114: * for the buffers to be flushed.
! 115: *
! 116: * If you want to write to the current CLI output, you can use the cli_msg()
! 117: * macro instead.
! 118: */
! 119: void
! 120: cli_printf(cli *c, int code, char *msg, ...)
! 121: {
! 122: va_list args;
! 123: byte buf[CLI_LINE_SIZE];
! 124: int cd = code;
! 125: int errcode;
! 126: int size, cnt;
! 127:
! 128: if (cd < 0)
! 129: {
! 130: cd = -cd;
! 131: if (cd == c->last_reply)
! 132: size = bsprintf(buf, " ");
! 133: else
! 134: size = bsprintf(buf, "%04d-", cd);
! 135: errcode = -8000;
! 136: }
! 137: else if (cd == CLI_ASYNC_CODE)
! 138: {
! 139: size = 1; buf[0] = '+';
! 140: errcode = cd;
! 141: }
! 142: else
! 143: {
! 144: size = bsprintf(buf, "%04d ", cd);
! 145: errcode = 8000;
! 146: }
! 147:
! 148: c->last_reply = cd;
! 149: va_start(args, msg);
! 150: cnt = bvsnprintf(buf+size, sizeof(buf)-size-1, msg, args);
! 151: va_end(args);
! 152: if (cnt < 0)
! 153: {
! 154: cli_printf(c, errcode, "<line overflow>");
! 155: return;
! 156: }
! 157: size += cnt;
! 158: buf[size++] = '\n';
! 159: memcpy(cli_alloc_out(c, size), buf, size);
! 160: }
! 161:
! 162: static void
! 163: cli_copy_message(cli *c)
! 164: {
! 165: byte *p, *q;
! 166: uint cnt = 2;
! 167:
! 168: if (c->ring_overflow)
! 169: {
! 170: byte buf[64];
! 171: int n = bsprintf(buf, "<%d messages lost>\n", c->ring_overflow);
! 172: c->ring_overflow = 0;
! 173: memcpy(cli_alloc_out(c, n), buf, n);
! 174: }
! 175: p = c->ring_read;
! 176: while (*p)
! 177: {
! 178: cnt++;
! 179: p++;
! 180: if (p == c->ring_end)
! 181: p = c->ring_buf;
! 182: ASSERT(p != c->ring_write);
! 183: }
! 184: c->async_msg_size += cnt;
! 185: q = cli_alloc_out(c, cnt);
! 186: *q++ = '+';
! 187: p = c->ring_read;
! 188: do
! 189: {
! 190: *q = *p++;
! 191: if (p == c->ring_end)
! 192: p = c->ring_buf;
! 193: }
! 194: while (*q++);
! 195: c->ring_read = p;
! 196: q[-1] = '\n';
! 197: }
! 198:
! 199: static void
! 200: cli_hello(cli *c)
! 201: {
! 202: cli_printf(c, 1, "BIRD " BIRD_VERSION " ready.");
! 203: c->cont = NULL;
! 204: }
! 205:
! 206: static void
! 207: cli_free_out(cli *c)
! 208: {
! 209: struct cli_out *o, *p;
! 210:
! 211: if (o = c->tx_buf)
! 212: {
! 213: o->wpos = o->outpos = o->buf;
! 214: while (p = o->next)
! 215: {
! 216: o->next = p->next;
! 217: mb_free(p);
! 218: }
! 219: }
! 220: c->tx_write = c->tx_pos = NULL;
! 221: c->async_msg_size = 0;
! 222: }
! 223:
! 224: void
! 225: cli_written(cli *c)
! 226: {
! 227: cli_free_out(c);
! 228: ev_schedule(c->event);
! 229: }
! 230:
! 231:
! 232: static byte *cli_rh_pos;
! 233: static uint cli_rh_len;
! 234: static int cli_rh_trick_flag;
! 235: struct cli *this_cli;
! 236:
! 237: static int
! 238: cli_cmd_read_hook(byte *buf, uint max, UNUSED int fd)
! 239: {
! 240: if (!cli_rh_trick_flag)
! 241: {
! 242: cli_rh_trick_flag = 1;
! 243: buf[0] = '!';
! 244: return 1;
! 245: }
! 246: if (max > cli_rh_len)
! 247: max = cli_rh_len;
! 248: memcpy(buf, cli_rh_pos, max);
! 249: cli_rh_pos += max;
! 250: cli_rh_len -= max;
! 251: return max;
! 252: }
! 253:
! 254: static void
! 255: cli_command(struct cli *c)
! 256: {
! 257: struct config f;
! 258: int res;
! 259:
! 260: if (config->cli_debug > 1)
! 261: log(L_TRACE "CLI: %s", c->rx_buf);
! 262: bzero(&f, sizeof(f));
! 263: f.mem = c->parser_pool;
! 264: cf_read_hook = cli_cmd_read_hook;
! 265: cli_rh_pos = c->rx_buf;
! 266: cli_rh_len = strlen(c->rx_buf);
! 267: cli_rh_trick_flag = 0;
! 268: this_cli = c;
! 269: lp_flush(c->parser_pool);
! 270: res = cli_parse(&f);
! 271: if (!res)
! 272: cli_printf(c, 9001, f.err_msg);
! 273: }
! 274:
! 275: static void
! 276: cli_event(void *data)
! 277: {
! 278: cli *c = data;
! 279: int err;
! 280:
! 281: while (c->ring_read != c->ring_write &&
! 282: c->async_msg_size < CLI_MAX_ASYNC_QUEUE)
! 283: cli_copy_message(c);
! 284:
! 285: if (c->tx_pos)
! 286: ;
! 287: else if (c->cont)
! 288: c->cont(c);
! 289: else
! 290: {
! 291: err = cli_get_command(c);
! 292: if (!err)
! 293: return;
! 294: if (err < 0)
! 295: cli_printf(c, 9000, "Command too long");
! 296: else
! 297: cli_command(c);
! 298: }
! 299:
! 300: cli_write_trigger(c);
! 301: }
! 302:
! 303: cli *
! 304: cli_new(void *priv)
! 305: {
! 306: pool *p = rp_new(cli_pool, "CLI");
! 307: cli *c = mb_alloc(p, sizeof(cli));
! 308:
! 309: bzero(c, sizeof(cli));
! 310: c->pool = p;
! 311: c->priv = priv;
! 312: c->event = ev_new(p);
! 313: c->event->hook = cli_event;
! 314: c->event->data = c;
! 315: c->cont = cli_hello;
! 316: c->parser_pool = lp_new(c->pool, 4096);
! 317: c->rx_buf = mb_alloc(c->pool, CLI_RX_BUF_SIZE);
! 318: ev_schedule(c->event);
! 319: return c;
! 320: }
! 321:
! 322: void
! 323: cli_kick(cli *c)
! 324: {
! 325: if (!c->cont && !c->tx_pos)
! 326: ev_schedule(c->event);
! 327: }
! 328:
! 329: static list cli_log_hooks;
! 330: static int cli_log_inited;
! 331:
! 332: void
! 333: cli_set_log_echo(cli *c, uint mask, uint size)
! 334: {
! 335: if (c->ring_buf)
! 336: {
! 337: mb_free(c->ring_buf);
! 338: c->ring_buf = c->ring_end = c->ring_read = c->ring_write = NULL;
! 339: rem_node(&c->n);
! 340: }
! 341: c->log_mask = mask;
! 342: if (mask && size)
! 343: {
! 344: c->ring_buf = mb_alloc(c->pool, size);
! 345: c->ring_end = c->ring_buf + size;
! 346: c->ring_read = c->ring_write = c->ring_buf;
! 347: add_tail(&cli_log_hooks, &c->n);
! 348: c->log_threshold = size / 8;
! 349: }
! 350: c->ring_overflow = 0;
! 351: }
! 352:
! 353: void
! 354: cli_echo(uint class, byte *msg)
! 355: {
! 356: unsigned len, free, i, l;
! 357: cli *c;
! 358: byte *m;
! 359:
! 360: if (!cli_log_inited || EMPTY_LIST(cli_log_hooks))
! 361: return;
! 362: len = strlen(msg) + 1;
! 363: WALK_LIST(c, cli_log_hooks)
! 364: {
! 365: if (!(c->log_mask & (1 << class)))
! 366: continue;
! 367: if (c->ring_read <= c->ring_write)
! 368: free = (c->ring_end - c->ring_buf) - (c->ring_write - c->ring_read + 1);
! 369: else
! 370: free = c->ring_read - c->ring_write - 1;
! 371: if ((len > free) ||
! 372: (free < c->log_threshold && class < (unsigned) L_INFO[0]))
! 373: {
! 374: c->ring_overflow++;
! 375: continue;
! 376: }
! 377: if (c->ring_read == c->ring_write)
! 378: ev_schedule(c->event);
! 379: m = msg;
! 380: l = len;
! 381: while (l)
! 382: {
! 383: if (c->ring_read <= c->ring_write)
! 384: i = c->ring_end - c->ring_write;
! 385: else
! 386: i = c->ring_read - c->ring_write;
! 387: if (i > l)
! 388: i = l;
! 389: memcpy(c->ring_write, m, i);
! 390: m += i;
! 391: l -= i;
! 392: c->ring_write += i;
! 393: if (c->ring_write == c->ring_end)
! 394: c->ring_write = c->ring_buf;
! 395: }
! 396: }
! 397: }
! 398:
! 399: /* Hack for scheduled undo notification */
! 400: extern cli *cmd_reconfig_stored_cli;
! 401:
! 402: void
! 403: cli_free(cli *c)
! 404: {
! 405: cli_set_log_echo(c, 0, 0);
! 406: if (c->cleanup)
! 407: c->cleanup(c);
! 408: if (c == cmd_reconfig_stored_cli)
! 409: cmd_reconfig_stored_cli = NULL;
! 410: rfree(c->pool);
! 411: }
! 412:
! 413: /**
! 414: * cli_init - initialize the CLI module
! 415: *
! 416: * This function is called during BIRD startup to initialize
! 417: * the internal data structures of the CLI module.
! 418: */
! 419: void
! 420: cli_init(void)
! 421: {
! 422: cli_pool = rp_new(&root_pool, "CLI");
! 423: init_list(&cli_log_hooks);
! 424: cli_log_inited = 1;
! 425: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>