]>
Commit | Line | Data |
---|---|---|
7d3aab1c MM |
1 | /* |
2 | * BIRD Internet Routing Daemon -- Command-Line Interface | |
3 | * | |
3d675cdb | 4 | * (c) 1999--2000 Martin Mares <mj@ucw.cz> |
7d3aab1c MM |
5 | * |
6 | * Can be freely distributed and used under the terms of the GNU GPL. | |
7 | */ | |
8 | ||
3d675cdb MM |
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 | * | |
58f7d004 | 29 | * Reply codes starting with 0 stand for `action successfully completed' messages, |
3d675cdb MM |
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, | |
58f7d004 | 34 | * so that it can be easily freed whenever the connection gets closed, not depending |
3d675cdb MM |
35 | * on the current state of command processing. |
36 | * | |
37 | * The CLI commands are declared as a part of the configuration grammar | |
725270cb | 38 | * by using the |CF_CLI| macro. When a command is received, it is processed |
2e9b2421 | 39 | * by the same lexical analyzer and parser as used for the configuration, but |
3d675cdb MM |
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 | |
725270cb | 43 | * the whole reply and returns it back or (in case it expects the reply will be long) |
3d675cdb | 44 | * it prints a partial reply and asks the CLI module (using the @cont hook) |
58f7d004 | 45 | * to call it again when the output is transferred to the user. |
3d675cdb MM |
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. | |
6baef17e OZ |
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 | * | |
3d675cdb MM |
64 | */ |
65 | ||
7d3aab1c | 66 | #include "nest/bird.h" |
7d3aab1c | 67 | #include "nest/cli.h" |
bc2fb680 MM |
68 | #include "conf/conf.h" |
69 | #include "lib/string.h" | |
7d3aab1c MM |
70 | |
71 | pool *cli_pool; | |
72 | ||
34350a52 MM |
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; | |
34350a52 MM |
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; | |
f75e3bbc | 95 | o->next = NULL; |
34350a52 MM |
96 | } |
97 | o->wpos += size; | |
98 | return o->wpos - size; | |
99 | } | |
100 | ||
3d675cdb MM |
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 | */ | |
7d3aab1c MM |
119 | void |
120 | cli_printf(cli *c, int code, char *msg, ...) | |
121 | { | |
122 | va_list args; | |
fdf16eb6 | 123 | byte buf[CLI_LINE_SIZE]; |
b9672a84 | 124 | int cd = code; |
a92cf57d | 125 | int errcode; |
b9672a84 | 126 | int size, cnt; |
7d3aab1c | 127 | |
b9672a84 MM |
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); | |
a92cf57d OZ |
135 | errcode = -8000; |
136 | } | |
137 | else if (cd == CLI_ASYNC_CODE) | |
138 | { | |
139 | size = 1; buf[0] = '+'; | |
140 | errcode = cd; | |
b9672a84 | 141 | } |
7d3aab1c | 142 | else |
a92cf57d OZ |
143 | { |
144 | size = bsprintf(buf, "%04d ", cd); | |
145 | errcode = 8000; | |
146 | } | |
147 | ||
b9672a84 | 148 | c->last_reply = cd; |
277a34ef | 149 | va_start(args, msg); |
b9672a84 | 150 | cnt = bvsnprintf(buf+size, sizeof(buf)-size-1, msg, args); |
277a34ef | 151 | va_end(args); |
b9672a84 MM |
152 | if (cnt < 0) |
153 | { | |
a92cf57d | 154 | cli_printf(c, errcode, "<line overflow>"); |
b9672a84 MM |
155 | return; |
156 | } | |
157 | size += cnt; | |
7d3aab1c | 158 | buf[size++] = '\n'; |
34350a52 MM |
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 | unsigned int cnt = 2; | |
167 | ||
168 | if (c->ring_overflow) | |
7d3aab1c | 169 | { |
34350a52 MM |
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); | |
7d3aab1c | 174 | } |
34350a52 MM |
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'; | |
7d3aab1c MM |
197 | } |
198 | ||
b9672a84 MM |
199 | static void |
200 | cli_hello(cli *c) | |
201 | { | |
202 | cli_printf(c, 1, "BIRD " BIRD_VERSION " ready."); | |
203 | c->cont = NULL; | |
204 | } | |
205 | ||
7d3aab1c MM |
206 | static void |
207 | cli_free_out(cli *c) | |
208 | { | |
209 | struct cli_out *o, *p; | |
210 | ||
211 | if (o = c->tx_buf) | |
212 | { | |
7d3aab1c MM |
213 | o->wpos = o->outpos = o->buf; |
214 | while (p = o->next) | |
215 | { | |
216 | o->next = p->next; | |
217 | mb_free(p); | |
218 | } | |
219 | } | |
f75e3bbc | 220 | c->tx_write = c->tx_pos = NULL; |
34350a52 | 221 | c->async_msg_size = 0; |
7d3aab1c MM |
222 | } |
223 | ||
6baef17e OZ |
224 | void |
225 | cli_written(cli *c) | |
226 | { | |
227 | cli_free_out(c); | |
228 | ev_schedule(c->event); | |
229 | } | |
230 | ||
231 | ||
bc2fb680 MM |
232 | static byte *cli_rh_pos; |
233 | static unsigned int cli_rh_len; | |
234 | static int cli_rh_trick_flag; | |
235 | struct cli *this_cli; | |
236 | ||
237 | static int | |
9b7fdfc8 | 238 | cli_cmd_read_hook(byte *buf, unsigned int max, UNUSED int fd) |
bc2fb680 MM |
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 | ||
4761efdb MM |
260 | if (config->cli_debug > 1) |
261 | log(L_TRACE "CLI: %s", c->rx_buf); | |
1d2664a4 | 262 | bzero(&f, sizeof(f)); |
bc2fb680 MM |
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; | |
bc2fb680 | 269 | lp_flush(c->parser_pool); |
f611f0ee | 270 | res = cli_parse(&f); |
bc2fb680 MM |
271 | if (!res) |
272 | cli_printf(c, 9001, f.err_msg); | |
273 | } | |
274 | ||
f75e3bbc | 275 | static void |
7d3aab1c MM |
276 | cli_event(void *data) |
277 | { | |
278 | cli *c = data; | |
279 | int err; | |
280 | ||
34350a52 MM |
281 | while (c->ring_read != c->ring_write && |
282 | c->async_msg_size < CLI_MAX_ASYNC_QUEUE) | |
283 | cli_copy_message(c); | |
284 | ||
b9672a84 MM |
285 | if (c->tx_pos) |
286 | ; | |
287 | else if (c->cont) | |
288 | c->cont(c); | |
289 | else | |
7d3aab1c | 290 | { |
b9672a84 MM |
291 | err = cli_get_command(c); |
292 | if (!err) | |
f75e3bbc | 293 | return; |
b9672a84 MM |
294 | if (err < 0) |
295 | cli_printf(c, 9000, "Command too long"); | |
296 | else | |
bc2fb680 | 297 | cli_command(c); |
7d3aab1c | 298 | } |
6baef17e OZ |
299 | |
300 | cli_write_trigger(c); | |
7d3aab1c MM |
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 | ||
34350a52 | 309 | bzero(c, sizeof(cli)); |
7d3aab1c MM |
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; | |
b9672a84 | 315 | c->cont = cli_hello; |
bc2fb680 | 316 | c->parser_pool = lp_new(c->pool, 4096); |
34350a52 | 317 | c->rx_buf = mb_alloc(c->pool, CLI_RX_BUF_SIZE); |
b9672a84 | 318 | ev_schedule(c->event); |
7d3aab1c MM |
319 | return c; |
320 | } | |
321 | ||
322 | void | |
323 | cli_kick(cli *c) | |
324 | { | |
b9672a84 MM |
325 | if (!c->cont && !c->tx_pos) |
326 | ev_schedule(c->event); | |
7d3aab1c MM |
327 | } |
328 | ||
34350a52 MM |
329 | static list cli_log_hooks; |
330 | static int cli_log_inited; | |
331 | ||
332 | void | |
333 | cli_set_log_echo(cli *c, unsigned int mask, unsigned int 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(unsigned int 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; | |
e81b440f OZ |
371 | if ((len > free) || |
372 | (free < c->log_threshold && class < (unsigned) L_INFO[0])) | |
34350a52 MM |
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 | ||
a92cf57d OZ |
399 | /* Hack for scheduled undo notification */ |
400 | extern cli *cmd_reconfig_stored_cli; | |
401 | ||
7d3aab1c MM |
402 | void |
403 | cli_free(cli *c) | |
404 | { | |
34350a52 | 405 | cli_set_log_echo(c, 0, 0); |
ffb59d24 MM |
406 | if (c->cleanup) |
407 | c->cleanup(c); | |
a92cf57d OZ |
408 | if (c == cmd_reconfig_stored_cli) |
409 | cmd_reconfig_stored_cli = NULL; | |
7d3aab1c MM |
410 | rfree(c->pool); |
411 | } | |
412 | ||
3d675cdb MM |
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 | */ | |
7d3aab1c MM |
419 | void |
420 | cli_init(void) | |
421 | { | |
422 | cli_pool = rp_new(&root_pool, "CLI"); | |
34350a52 MM |
423 | init_list(&cli_log_hooks); |
424 | cli_log_inited = 1; | |
7d3aab1c | 425 | } |