]>
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. | |
50 | */ | |
51 | ||
7d3aab1c | 52 | #include "nest/bird.h" |
7d3aab1c | 53 | #include "nest/cli.h" |
bc2fb680 MM |
54 | #include "conf/conf.h" |
55 | #include "lib/string.h" | |
7d3aab1c MM |
56 | |
57 | pool *cli_pool; | |
58 | ||
34350a52 MM |
59 | static byte * |
60 | cli_alloc_out(cli *c, int size) | |
61 | { | |
62 | struct cli_out *o; | |
63 | ||
64 | if (!(o = c->tx_write) || o->wpos + size > o->end) | |
65 | { | |
66 | if (!o && c->tx_buf) | |
67 | o = c->tx_buf; | |
68 | else | |
69 | { | |
70 | o = mb_alloc(c->pool, sizeof(struct cli_out) + CLI_TX_BUF_SIZE); | |
71 | if (c->tx_write) | |
72 | c->tx_write->next = o; | |
73 | else | |
74 | c->tx_buf = o; | |
34350a52 MM |
75 | o->wpos = o->outpos = o->buf; |
76 | o->end = o->buf + CLI_TX_BUF_SIZE; | |
77 | } | |
78 | c->tx_write = o; | |
79 | if (!c->tx_pos) | |
80 | c->tx_pos = o; | |
f75e3bbc | 81 | o->next = NULL; |
34350a52 MM |
82 | } |
83 | o->wpos += size; | |
84 | return o->wpos - size; | |
85 | } | |
86 | ||
3d675cdb MM |
87 | /** |
88 | * cli_printf - send reply to a CLI connection | |
89 | * @c: CLI connection | |
90 | * @code: numeric code of the reply, negative for continuation lines | |
91 | * @msg: a printf()-like formatting string. | |
92 | * | |
93 | * This function send a single line of reply to a given CLI connection. | |
94 | * In works in all aspects like bsprintf() except that it automatically | |
95 | * prepends the reply line prefix. | |
96 | * | |
97 | * Please note that if the connection can be already busy sending some | |
98 | * data in which case cli_printf() stores the output to a temporary buffer, | |
99 | * so please avoid sending a large batch of replies without waiting | |
100 | * for the buffers to be flushed. | |
101 | * | |
102 | * If you want to write to the current CLI output, you can use the cli_msg() | |
103 | * macro instead. | |
104 | */ | |
7d3aab1c MM |
105 | void |
106 | cli_printf(cli *c, int code, char *msg, ...) | |
107 | { | |
108 | va_list args; | |
109 | byte buf[1024]; | |
b9672a84 MM |
110 | int cd = code; |
111 | int size, cnt; | |
7d3aab1c | 112 | |
b9672a84 MM |
113 | if (cd < 0) |
114 | { | |
115 | cd = -cd; | |
116 | if (cd == c->last_reply) | |
117 | size = bsprintf(buf, " "); | |
118 | else | |
119 | size = bsprintf(buf, "%04d-", cd); | |
120 | } | |
7d3aab1c | 121 | else |
b9672a84 MM |
122 | size = bsprintf(buf, "%04d ", cd); |
123 | c->last_reply = cd; | |
277a34ef | 124 | va_start(args, msg); |
b9672a84 | 125 | cnt = bvsnprintf(buf+size, sizeof(buf)-size-1, msg, args); |
277a34ef | 126 | va_end(args); |
b9672a84 MM |
127 | if (cnt < 0) |
128 | { | |
129 | cli_printf(c, code < 0 ? -8000 : 8000, "<line overflow>"); | |
130 | return; | |
131 | } | |
132 | size += cnt; | |
7d3aab1c | 133 | buf[size++] = '\n'; |
34350a52 MM |
134 | memcpy(cli_alloc_out(c, size), buf, size); |
135 | } | |
136 | ||
137 | static void | |
138 | cli_copy_message(cli *c) | |
139 | { | |
140 | byte *p, *q; | |
141 | unsigned int cnt = 2; | |
142 | ||
143 | if (c->ring_overflow) | |
7d3aab1c | 144 | { |
34350a52 MM |
145 | byte buf[64]; |
146 | int n = bsprintf(buf, "<%d messages lost>\n", c->ring_overflow); | |
147 | c->ring_overflow = 0; | |
148 | memcpy(cli_alloc_out(c, n), buf, n); | |
7d3aab1c | 149 | } |
34350a52 MM |
150 | p = c->ring_read; |
151 | while (*p) | |
152 | { | |
153 | cnt++; | |
154 | p++; | |
155 | if (p == c->ring_end) | |
156 | p = c->ring_buf; | |
157 | ASSERT(p != c->ring_write); | |
158 | } | |
159 | c->async_msg_size += cnt; | |
160 | q = cli_alloc_out(c, cnt); | |
161 | *q++ = '+'; | |
162 | p = c->ring_read; | |
163 | do | |
164 | { | |
165 | *q = *p++; | |
166 | if (p == c->ring_end) | |
167 | p = c->ring_buf; | |
168 | } | |
169 | while (*q++); | |
170 | c->ring_read = p; | |
171 | q[-1] = '\n'; | |
7d3aab1c MM |
172 | } |
173 | ||
b9672a84 MM |
174 | static void |
175 | cli_hello(cli *c) | |
176 | { | |
177 | cli_printf(c, 1, "BIRD " BIRD_VERSION " ready."); | |
178 | c->cont = NULL; | |
179 | } | |
180 | ||
7d3aab1c MM |
181 | static void |
182 | cli_free_out(cli *c) | |
183 | { | |
184 | struct cli_out *o, *p; | |
185 | ||
186 | if (o = c->tx_buf) | |
187 | { | |
7d3aab1c MM |
188 | o->wpos = o->outpos = o->buf; |
189 | while (p = o->next) | |
190 | { | |
191 | o->next = p->next; | |
192 | mb_free(p); | |
193 | } | |
194 | } | |
f75e3bbc | 195 | c->tx_write = c->tx_pos = NULL; |
34350a52 | 196 | c->async_msg_size = 0; |
7d3aab1c MM |
197 | } |
198 | ||
bc2fb680 MM |
199 | static byte *cli_rh_pos; |
200 | static unsigned int cli_rh_len; | |
201 | static int cli_rh_trick_flag; | |
202 | struct cli *this_cli; | |
203 | ||
204 | static int | |
205 | cli_cmd_read_hook(byte *buf, unsigned int max) | |
206 | { | |
207 | if (!cli_rh_trick_flag) | |
208 | { | |
209 | cli_rh_trick_flag = 1; | |
210 | buf[0] = '!'; | |
211 | return 1; | |
212 | } | |
213 | if (max > cli_rh_len) | |
214 | max = cli_rh_len; | |
215 | memcpy(buf, cli_rh_pos, max); | |
216 | cli_rh_pos += max; | |
217 | cli_rh_len -= max; | |
218 | return max; | |
219 | } | |
220 | ||
221 | static void | |
222 | cli_command(struct cli *c) | |
223 | { | |
224 | struct config f; | |
225 | int res; | |
226 | ||
4761efdb MM |
227 | if (config->cli_debug > 1) |
228 | log(L_TRACE "CLI: %s", c->rx_buf); | |
1d2664a4 | 229 | bzero(&f, sizeof(f)); |
bc2fb680 MM |
230 | f.mem = c->parser_pool; |
231 | cf_read_hook = cli_cmd_read_hook; | |
232 | cli_rh_pos = c->rx_buf; | |
233 | cli_rh_len = strlen(c->rx_buf); | |
234 | cli_rh_trick_flag = 0; | |
235 | this_cli = c; | |
bc2fb680 | 236 | lp_flush(c->parser_pool); |
f611f0ee | 237 | res = cli_parse(&f); |
bc2fb680 MM |
238 | if (!res) |
239 | cli_printf(c, 9001, f.err_msg); | |
240 | } | |
241 | ||
f75e3bbc | 242 | static void |
7d3aab1c MM |
243 | cli_event(void *data) |
244 | { | |
245 | cli *c = data; | |
246 | int err; | |
247 | ||
34350a52 MM |
248 | while (c->ring_read != c->ring_write && |
249 | c->async_msg_size < CLI_MAX_ASYNC_QUEUE) | |
250 | cli_copy_message(c); | |
251 | ||
b9672a84 MM |
252 | if (c->tx_pos) |
253 | ; | |
254 | else if (c->cont) | |
255 | c->cont(c); | |
256 | else | |
7d3aab1c | 257 | { |
b9672a84 MM |
258 | err = cli_get_command(c); |
259 | if (!err) | |
f75e3bbc | 260 | return; |
b9672a84 MM |
261 | if (err < 0) |
262 | cli_printf(c, 9000, "Command too long"); | |
263 | else | |
bc2fb680 | 264 | cli_command(c); |
7d3aab1c | 265 | } |
b9672a84 MM |
266 | if (cli_write(c)) |
267 | { | |
268 | cli_free_out(c); | |
f75e3bbc | 269 | ev_schedule(c->event); |
b9672a84 | 270 | } |
7d3aab1c MM |
271 | } |
272 | ||
273 | cli * | |
274 | cli_new(void *priv) | |
275 | { | |
276 | pool *p = rp_new(cli_pool, "CLI"); | |
277 | cli *c = mb_alloc(p, sizeof(cli)); | |
278 | ||
34350a52 | 279 | bzero(c, sizeof(cli)); |
7d3aab1c MM |
280 | c->pool = p; |
281 | c->priv = priv; | |
282 | c->event = ev_new(p); | |
283 | c->event->hook = cli_event; | |
284 | c->event->data = c; | |
b9672a84 | 285 | c->cont = cli_hello; |
bc2fb680 | 286 | c->parser_pool = lp_new(c->pool, 4096); |
34350a52 | 287 | c->rx_buf = mb_alloc(c->pool, CLI_RX_BUF_SIZE); |
b9672a84 | 288 | ev_schedule(c->event); |
7d3aab1c MM |
289 | return c; |
290 | } | |
291 | ||
292 | void | |
293 | cli_kick(cli *c) | |
294 | { | |
b9672a84 MM |
295 | if (!c->cont && !c->tx_pos) |
296 | ev_schedule(c->event); | |
7d3aab1c MM |
297 | } |
298 | ||
299 | void | |
300 | cli_written(cli *c) | |
301 | { | |
7d3aab1c | 302 | cli_free_out(c); |
b9672a84 | 303 | ev_schedule(c->event); |
7d3aab1c MM |
304 | } |
305 | ||
34350a52 MM |
306 | static list cli_log_hooks; |
307 | static int cli_log_inited; | |
308 | ||
309 | void | |
310 | cli_set_log_echo(cli *c, unsigned int mask, unsigned int size) | |
311 | { | |
312 | if (c->ring_buf) | |
313 | { | |
314 | mb_free(c->ring_buf); | |
315 | c->ring_buf = c->ring_end = c->ring_read = c->ring_write = NULL; | |
316 | rem_node(&c->n); | |
317 | } | |
318 | c->log_mask = mask; | |
319 | if (mask && size) | |
320 | { | |
321 | c->ring_buf = mb_alloc(c->pool, size); | |
322 | c->ring_end = c->ring_buf + size; | |
323 | c->ring_read = c->ring_write = c->ring_buf; | |
324 | add_tail(&cli_log_hooks, &c->n); | |
325 | c->log_threshold = size / 8; | |
326 | } | |
327 | c->ring_overflow = 0; | |
328 | } | |
329 | ||
330 | void | |
331 | cli_echo(unsigned int class, byte *msg) | |
332 | { | |
333 | unsigned len, free, i, l; | |
334 | cli *c; | |
335 | byte *m; | |
336 | ||
337 | if (!cli_log_inited || EMPTY_LIST(cli_log_hooks)) | |
338 | return; | |
339 | len = strlen(msg) + 1; | |
340 | WALK_LIST(c, cli_log_hooks) | |
341 | { | |
342 | if (!(c->log_mask & (1 << class))) | |
343 | continue; | |
344 | if (c->ring_read <= c->ring_write) | |
345 | free = (c->ring_end - c->ring_buf) - (c->ring_write - c->ring_read + 1); | |
346 | else | |
347 | free = c->ring_read - c->ring_write - 1; | |
348 | if (len > free || | |
349 | free < c->log_threshold && class < (unsigned) L_INFO[0]) | |
350 | { | |
351 | c->ring_overflow++; | |
352 | continue; | |
353 | } | |
354 | if (c->ring_read == c->ring_write) | |
355 | ev_schedule(c->event); | |
356 | m = msg; | |
357 | l = len; | |
358 | while (l) | |
359 | { | |
360 | if (c->ring_read <= c->ring_write) | |
361 | i = c->ring_end - c->ring_write; | |
362 | else | |
363 | i = c->ring_read - c->ring_write; | |
364 | if (i > l) | |
365 | i = l; | |
366 | memcpy(c->ring_write, m, i); | |
367 | m += i; | |
368 | l -= i; | |
369 | c->ring_write += i; | |
370 | if (c->ring_write == c->ring_end) | |
371 | c->ring_write = c->ring_buf; | |
372 | } | |
373 | } | |
374 | } | |
375 | ||
7d3aab1c MM |
376 | void |
377 | cli_free(cli *c) | |
378 | { | |
34350a52 | 379 | cli_set_log_echo(c, 0, 0); |
ffb59d24 MM |
380 | if (c->cleanup) |
381 | c->cleanup(c); | |
7d3aab1c MM |
382 | rfree(c->pool); |
383 | } | |
384 | ||
3d675cdb MM |
385 | /** |
386 | * cli_init - initialize the CLI module | |
387 | * | |
388 | * This function is called during BIRD startup to initialize | |
389 | * the internal data structures of the CLI module. | |
390 | */ | |
7d3aab1c MM |
391 | void |
392 | cli_init(void) | |
393 | { | |
394 | cli_pool = rp_new(&root_pool, "CLI"); | |
34350a52 MM |
395 | init_list(&cli_log_hooks); |
396 | cli_log_inited = 1; | |
7d3aab1c | 397 | } |