]>
Commit | Line | Data |
---|---|---|
ed608150 MM |
1 | /* |
2 | * BIRD Client | |
3 | * | |
7deffd84 | 4 | * (c) 1999--2004 Martin Mares <mj@ucw.cz> |
ed608150 MM |
5 | * |
6 | * Can be freely distributed and used under the terms of the GNU GPL. | |
7 | */ | |
8 | ||
9fac310d | 9 | #include <stdio.h> |
9fac310d | 10 | #include <stdlib.h> |
c51f132d | 11 | #include <fcntl.h> |
9fac310d | 12 | #include <unistd.h> |
7deffd84 | 13 | #include <termios.h> |
c51f132d MM |
14 | #include <errno.h> |
15 | #include <sys/socket.h> | |
16 | #include <sys/un.h> | |
c51f132d | 17 | #include <sys/types.h> |
7211be1c MM |
18 | #include <readline/readline.h> |
19 | #include <readline/history.h> | |
f0333f44 | 20 | #include <curses.h> |
9fac310d | 21 | |
ed608150 | 22 | #include "nest/bird.h" |
9fac310d | 23 | #include "lib/resource.h" |
221135d6 | 24 | #include "lib/string.h" |
ed608150 | 25 | #include "client/client.h" |
4daf03e5 | 26 | #include "sysdep/unix/unix.h" |
ed608150 | 27 | |
e0a45fb4 | 28 | static char *opt_list = "s:vr"; |
c51f132d | 29 | static int verbose; |
e0a45fb4 OZ |
30 | static char *init_cmd; |
31 | static int once; | |
c51f132d | 32 | |
d69e5ff2 | 33 | static char *server_path = PATH_CONTROL_SOCKET; |
c51f132d | 34 | static int server_fd; |
c51f132d MM |
35 | static byte server_read_buf[4096]; |
36 | static byte *server_read_pos = server_read_buf; | |
37 | ||
f0333f44 OZ |
38 | #define STATE_PROMPT 0 |
39 | #define STATE_CMD_SERVER 1 | |
40 | #define STATE_CMD_USER 2 | |
41 | ||
c51f132d | 42 | static int input_initialized; |
c51f132d | 43 | static int input_hidden_end; |
f0333f44 OZ |
44 | static int cstate = STATE_CMD_SERVER; |
45 | static int nstate = STATE_CMD_SERVER; | |
46 | ||
47 | static int num_lines, skip_input, interactive; | |
c51f132d MM |
48 | |
49 | /*** Parsing of arguments ***/ | |
f50b9e48 | 50 | |
9fac310d MM |
51 | static void |
52 | usage(void) | |
53 | { | |
e0a45fb4 | 54 | fprintf(stderr, "Usage: birdc [-s <control-socket>] [-v] [-r]\n"); |
9fac310d MM |
55 | exit(1); |
56 | } | |
57 | ||
58 | static void | |
59 | parse_args(int argc, char **argv) | |
60 | { | |
61 | int c; | |
62 | ||
63 | while ((c = getopt(argc, argv, opt_list)) >= 0) | |
64 | switch (c) | |
65 | { | |
c51f132d MM |
66 | case 's': |
67 | server_path = optarg; | |
68 | break; | |
69 | case 'v': | |
70 | verbose++; | |
71 | break; | |
e0a45fb4 OZ |
72 | case 'r': |
73 | init_cmd = "restrict"; | |
74 | break; | |
9fac310d MM |
75 | default: |
76 | usage(); | |
77 | } | |
e0a45fb4 OZ |
78 | |
79 | /* If some arguments are not options, we take it as commands */ | |
9fac310d | 80 | if (optind < argc) |
e0a45fb4 OZ |
81 | { |
82 | char *tmp; | |
83 | int i; | |
84 | int len = 0; | |
85 | ||
86 | if (init_cmd) | |
87 | usage(); | |
88 | ||
89 | for (i = optind; i < argc; i++) | |
90 | len += strlen(argv[i]) + 1; | |
91 | ||
92 | tmp = init_cmd = malloc(len); | |
93 | for (i = optind; i < argc; i++) | |
94 | { | |
95 | strcpy(tmp, argv[i]); | |
96 | tmp += strlen(tmp); | |
97 | *tmp++ = ' '; | |
98 | } | |
99 | ||
100 | once = 1; | |
101 | } | |
9fac310d | 102 | } |
f50b9e48 | 103 | |
c51f132d MM |
104 | /*** Input ***/ |
105 | ||
106 | static void server_send(char *); | |
7211be1c | 107 | |
fae0396e MM |
108 | /* HACK: libreadline internals we need to access */ |
109 | extern int _rl_vis_botlin; | |
110 | extern void _rl_move_vert(int); | |
111 | extern Function *rl_last_func; | |
112 | ||
2983460b MM |
113 | static int |
114 | handle_internal_command(char *cmd) | |
115 | { | |
116 | if (!strncmp(cmd, "exit", 4) || !strncmp(cmd, "quit", 4)) | |
117 | { | |
118 | cleanup(); | |
119 | exit(0); | |
120 | } | |
121 | if (!strncmp(cmd, "help", 4)) | |
122 | { | |
123 | puts("Press `?' for context sensitive help."); | |
124 | return 1; | |
125 | } | |
126 | return 0; | |
127 | } | |
128 | ||
f0333f44 OZ |
129 | void |
130 | submit_server_command(char *cmd) | |
131 | { | |
132 | server_send(cmd); | |
133 | nstate = STATE_CMD_SERVER; | |
134 | num_lines = 2; | |
135 | } | |
136 | ||
137 | ||
c51f132d MM |
138 | static void |
139 | got_line(char *cmd_buffer) | |
140 | { | |
e69e4ed9 MM |
141 | char *cmd; |
142 | ||
7211be1c | 143 | if (!cmd_buffer) |
c51f132d MM |
144 | { |
145 | cleanup(); | |
146 | exit(0); | |
147 | } | |
7211be1c | 148 | if (cmd_buffer[0]) |
c51f132d | 149 | { |
e69e4ed9 MM |
150 | cmd = cmd_expand(cmd_buffer); |
151 | if (cmd) | |
152 | { | |
153 | add_history(cmd); | |
f0333f44 | 154 | |
2983460b | 155 | if (!handle_internal_command(cmd)) |
f0333f44 OZ |
156 | submit_server_command(cmd); |
157 | ||
e69e4ed9 MM |
158 | free(cmd); |
159 | } | |
971b2310 MM |
160 | else |
161 | add_history(cmd_buffer); | |
c51f132d MM |
162 | } |
163 | free(cmd_buffer); | |
164 | } | |
165 | ||
fae0396e MM |
166 | void |
167 | input_start_list(void) /* Leave the currently edited line and make space for listing */ | |
168 | { | |
169 | _rl_move_vert(_rl_vis_botlin); | |
bd62eeca | 170 | #ifdef HAVE_RL_CRLF |
59b96d7b | 171 | rl_crlf(); |
bd62eeca | 172 | #endif |
fae0396e MM |
173 | } |
174 | ||
175 | void | |
176 | input_stop_list(void) /* Reprint the currently edited line after listing */ | |
177 | { | |
178 | rl_on_new_line(); | |
179 | rl_redisplay(); | |
180 | } | |
181 | ||
0223d4ff | 182 | static int |
d7390312 | 183 | input_complete(int arg UNUSED, int key UNUSED) |
0223d4ff | 184 | { |
fae0396e MM |
185 | static int complete_flag; |
186 | char buf[256]; | |
187 | ||
188 | if (rl_last_func != input_complete) | |
189 | complete_flag = 0; | |
190 | switch (cmd_complete(rl_line_buffer, rl_point, buf, complete_flag)) | |
191 | { | |
192 | case 0: | |
193 | complete_flag = 1; | |
194 | break; | |
195 | case 1: | |
196 | rl_insert_text(buf); | |
197 | break; | |
198 | default: | |
199 | complete_flag = 1; | |
bd62eeca | 200 | #ifdef HAVE_RL_DING |
59b96d7b | 201 | rl_ding(); |
bd62eeca | 202 | #endif |
fae0396e | 203 | } |
0223d4ff MM |
204 | return 0; |
205 | } | |
206 | ||
207 | static int | |
d7390312 | 208 | input_help(int arg, int key UNUSED) |
0223d4ff | 209 | { |
cf186034 | 210 | int i, in_string, in_bracket; |
0223d4ff | 211 | |
fae0396e | 212 | if (arg != 1) |
0223d4ff | 213 | return rl_insert(arg, '?'); |
80ac7dc1 | 214 | |
cf186034 | 215 | in_string = in_bracket = 0; |
80ac7dc1 | 216 | for (i = 0; i < rl_point; i++) |
0223d4ff | 217 | { |
cf186034 | 218 | |
80ac7dc1 OZ |
219 | if (rl_line_buffer[i] == '"') |
220 | in_string = ! in_string; | |
cf186034 OZ |
221 | else if (! in_string) |
222 | { | |
223 | if (rl_line_buffer[i] == '[') | |
224 | in_bracket++; | |
225 | else if (rl_line_buffer[i] == ']') | |
226 | in_bracket--; | |
227 | } | |
0223d4ff | 228 | } |
80ac7dc1 OZ |
229 | |
230 | /* `?' inside string or path -> insert */ | |
cf186034 | 231 | if (in_string || in_bracket) |
80ac7dc1 OZ |
232 | return rl_insert(1, '?'); |
233 | ||
fae0396e MM |
234 | rl_begin_undo_group(); /* HACK: We want to display `?' at point position */ |
235 | rl_insert_text("?"); | |
0223d4ff | 236 | rl_redisplay(); |
fae0396e MM |
237 | rl_end_undo_group(); |
238 | input_start_list(); | |
239 | cmd_help(rl_line_buffer, rl_point); | |
240 | rl_undo_command(1, 0); | |
241 | input_stop_list(); | |
0223d4ff MM |
242 | return 0; |
243 | } | |
244 | ||
c51f132d MM |
245 | static void |
246 | input_init(void) | |
247 | { | |
248 | rl_readline_name = "birdc"; | |
0223d4ff MM |
249 | rl_add_defun("bird-complete", input_complete, '\t'); |
250 | rl_add_defun("bird-help", input_help, '?'); | |
c51f132d MM |
251 | rl_callback_handler_install("bird> ", got_line); |
252 | input_initialized = 1; | |
661ec5db OZ |
253 | // readline library does strange things when stdin is nonblocking. |
254 | // if (fcntl(0, F_SETFL, O_NONBLOCK) < 0) | |
255 | // die("fcntl: %m"); | |
c51f132d MM |
256 | } |
257 | ||
258 | static void | |
259 | input_hide(void) | |
260 | { | |
f0333f44 OZ |
261 | input_hidden_end = rl_end; |
262 | rl_end = 0; | |
263 | rl_expand_prompt(""); | |
264 | rl_redisplay(); | |
c51f132d MM |
265 | } |
266 | ||
267 | static void | |
268 | input_reveal(void) | |
269 | { | |
f0333f44 OZ |
270 | /* need this, otherwise some lib seems to eat pending output when |
271 | the prompt is displayed */ | |
272 | fflush(stdout); | |
273 | tcdrain(fileno(stdout)); | |
274 | ||
c51f132d MM |
275 | rl_end = input_hidden_end; |
276 | rl_expand_prompt("bird> "); | |
277 | rl_forced_update_display(); | |
c51f132d MM |
278 | } |
279 | ||
280 | void | |
281 | cleanup(void) | |
282 | { | |
283 | if (input_initialized) | |
284 | { | |
285 | input_initialized = 0; | |
286 | input_hide(); | |
287 | rl_callback_handler_remove(); | |
288 | } | |
289 | } | |
290 | ||
f0333f44 OZ |
291 | void |
292 | update_state(void) | |
293 | { | |
294 | if (nstate == cstate) | |
295 | return; | |
296 | ||
e0a45fb4 OZ |
297 | if (init_cmd) |
298 | { | |
299 | /* First transition - client received hello from BIRD | |
300 | and there is waiting initial command */ | |
301 | submit_server_command(init_cmd); | |
302 | init_cmd = NULL; | |
303 | return; | |
304 | } | |
305 | ||
306 | if (!init_cmd && once) | |
307 | { | |
308 | /* Initial command is finished and we want to exit */ | |
309 | cleanup(); | |
310 | exit(0); | |
311 | } | |
312 | ||
f0333f44 OZ |
313 | if (nstate == STATE_PROMPT) |
314 | if (input_initialized) | |
315 | input_reveal(); | |
316 | else | |
317 | input_init(); | |
318 | ||
319 | if (nstate != STATE_PROMPT) | |
320 | input_hide(); | |
321 | ||
322 | cstate = nstate; | |
323 | } | |
324 | ||
325 | void | |
326 | more(void) | |
327 | { | |
328 | printf("--More--\015"); | |
329 | fflush(stdout); | |
330 | ||
331 | redo: | |
332 | switch (getchar()) | |
333 | { | |
334 | case 32: | |
335 | num_lines = 2; | |
336 | break; | |
337 | case 13: | |
338 | num_lines--; | |
339 | break; | |
340 | case 'q': | |
341 | skip_input = 1; | |
342 | break; | |
343 | default: | |
344 | goto redo; | |
345 | } | |
346 | ||
347 | printf(" \015"); | |
348 | fflush(stdout); | |
349 | } | |
350 | ||
351 | ||
c51f132d MM |
352 | /*** Communication with server ***/ |
353 | ||
354 | static void | |
355 | server_connect(void) | |
356 | { | |
357 | struct sockaddr_un sa; | |
358 | ||
359 | server_fd = socket(AF_UNIX, SOCK_STREAM, 0); | |
360 | if (server_fd < 0) | |
361 | die("Cannot create socket: %m"); | |
68fa95cf OZ |
362 | |
363 | if (strlen(server_path) >= sizeof(sa.sun_path)) | |
364 | die("server_connect: path too long"); | |
365 | ||
c51f132d MM |
366 | bzero(&sa, sizeof(sa)); |
367 | sa.sun_family = AF_UNIX; | |
97c6fa02 | 368 | strcpy(sa.sun_path, server_path); |
0b3bf4b1 | 369 | if (connect(server_fd, (struct sockaddr *) &sa, SUN_LEN(&sa)) < 0) |
c51f132d MM |
370 | die("Unable to connect to server control socket (%s): %m", server_path); |
371 | if (fcntl(server_fd, F_SETFL, O_NONBLOCK) < 0) | |
372 | die("fcntl: %m"); | |
373 | } | |
374 | ||
375 | static void | |
376 | server_got_reply(char *x) | |
377 | { | |
378 | int code; | |
9c46ad8e | 379 | int len = 0; |
c51f132d | 380 | |
c51f132d | 381 | if (*x == '+') /* Async reply */ |
9c46ad8e | 382 | skip_input || (len = printf(">>> %s\n", x+1)); |
c51f132d | 383 | else if (x[0] == ' ') /* Continuation */ |
9c46ad8e | 384 | skip_input || (len = printf("%s%s\n", verbose ? " " : "", x+1)); |
c51f132d MM |
385 | else if (strlen(x) > 4 && |
386 | sscanf(x, "%d", &code) == 1 && code >= 0 && code < 10000 && | |
387 | (x[4] == ' ' || x[4] == '-')) | |
388 | { | |
389 | if (code) | |
9c46ad8e | 390 | skip_input || (len = printf("%s\n", verbose ? x : x+5)); |
c51f132d | 391 | if (x[4] == ' ') |
f0333f44 OZ |
392 | { |
393 | nstate = STATE_PROMPT; | |
394 | skip_input = 0; | |
395 | return; | |
396 | } | |
c51f132d MM |
397 | } |
398 | else | |
9c46ad8e | 399 | skip_input || (len = printf("??? <%s>\n", x)); |
f0333f44 OZ |
400 | |
401 | if (skip_input) | |
402 | return; | |
403 | ||
9c46ad8e OZ |
404 | if (interactive && input_initialized && (len > 0)) |
405 | { | |
406 | int lns = LINES ? LINES : 25; | |
407 | int cls = COLS ? COLS : 80; | |
408 | num_lines += (len + cls - 1) / cls; /* Divide and round up */ | |
409 | if ((num_lines >= lns) && (cstate == STATE_CMD_SERVER)) | |
410 | more(); | |
411 | } | |
c51f132d MM |
412 | } |
413 | ||
414 | static void | |
415 | server_read(void) | |
416 | { | |
417 | int c; | |
418 | byte *start, *p; | |
419 | ||
e0011590 | 420 | redo: |
c51f132d MM |
421 | c = read(server_fd, server_read_pos, server_read_buf + sizeof(server_read_buf) - server_read_pos); |
422 | if (!c) | |
423 | die("Connection closed by server."); | |
424 | if (c < 0) | |
e0011590 OZ |
425 | { |
426 | if (errno == EINTR) | |
427 | goto redo; | |
428 | else | |
429 | die("Server read error: %m"); | |
430 | } | |
431 | ||
c51f132d MM |
432 | start = server_read_buf; |
433 | p = server_read_pos; | |
434 | server_read_pos += c; | |
435 | while (p < server_read_pos) | |
436 | if (*p++ == '\n') | |
437 | { | |
438 | p[-1] = 0; | |
439 | server_got_reply(start); | |
440 | start = p; | |
441 | } | |
442 | if (start != server_read_buf) | |
443 | { | |
444 | int l = server_read_pos - start; | |
445 | memmove(server_read_buf, start, l); | |
446 | server_read_pos = server_read_buf + l; | |
447 | } | |
448 | else if (server_read_pos == server_read_buf + sizeof(server_read_buf)) | |
449 | { | |
450 | strcpy(server_read_buf, "?<too-long>"); | |
451 | server_read_pos = server_read_buf + 11; | |
452 | } | |
453 | } | |
454 | ||
455 | static fd_set select_fds; | |
456 | ||
457 | static void | |
f0333f44 | 458 | select_loop(void) |
c51f132d | 459 | { |
e0011590 | 460 | int rv; |
f0333f44 | 461 | while (1) |
c51f132d | 462 | { |
9e85a5e6 | 463 | FD_ZERO(&select_fds); |
f0333f44 OZ |
464 | |
465 | if (cstate != STATE_CMD_USER) | |
466 | FD_SET(server_fd, &select_fds); | |
467 | if (cstate != STATE_CMD_SERVER) | |
c51f132d | 468 | FD_SET(0, &select_fds); |
e0011590 OZ |
469 | |
470 | rv = select(server_fd+1, &select_fds, NULL, NULL, NULL); | |
471 | if (rv < 0) | |
472 | { | |
473 | if (errno == EINTR) | |
474 | continue; | |
475 | else | |
476 | die("select: %m"); | |
477 | } | |
478 | ||
c51f132d MM |
479 | if (FD_ISSET(server_fd, &select_fds)) |
480 | { | |
481 | server_read(); | |
f0333f44 | 482 | update_state(); |
c51f132d | 483 | } |
f0333f44 | 484 | |
c51f132d | 485 | if (FD_ISSET(0, &select_fds)) |
f0333f44 OZ |
486 | { |
487 | rl_callback_read_char(); | |
488 | update_state(); | |
489 | } | |
c51f132d | 490 | } |
c51f132d MM |
491 | } |
492 | ||
e0011590 OZ |
493 | static void |
494 | wait_for_write(int fd) | |
495 | { | |
496 | while (1) | |
497 | { | |
498 | int rv; | |
499 | fd_set set; | |
500 | FD_ZERO(&set); | |
501 | FD_SET(fd, &set); | |
502 | ||
503 | rv = select(fd+1, NULL, &set, NULL, NULL); | |
504 | if (rv < 0) | |
505 | { | |
506 | if (errno == EINTR) | |
507 | continue; | |
508 | else | |
509 | die("select: %m"); | |
510 | } | |
511 | ||
512 | if (FD_ISSET(server_fd, &set)) | |
513 | return; | |
514 | } | |
515 | } | |
516 | ||
c51f132d MM |
517 | static void |
518 | server_send(char *cmd) | |
519 | { | |
520 | int l = strlen(cmd); | |
521 | byte *z = alloca(l + 1); | |
522 | ||
523 | memcpy(z, cmd, l); | |
524 | z[l++] = '\n'; | |
525 | while (l) | |
526 | { | |
527 | int cnt = write(server_fd, z, l); | |
e0011590 | 528 | |
c51f132d MM |
529 | if (cnt < 0) |
530 | { | |
e0011590 OZ |
531 | if (errno == EAGAIN) |
532 | wait_for_write(server_fd); | |
533 | else if (errno == EINTR) | |
534 | continue; | |
c51f132d MM |
535 | else |
536 | die("Server write error: %m"); | |
537 | } | |
538 | else | |
539 | { | |
540 | l -= cnt; | |
541 | z += cnt; | |
542 | } | |
543 | } | |
7211be1c MM |
544 | } |
545 | ||
ed608150 MM |
546 | int |
547 | main(int argc, char **argv) | |
548 | { | |
9fac310d MM |
549 | #ifdef HAVE_LIBDMALLOC |
550 | if (!getenv("DMALLOC_OPTIONS")) | |
551 | dmalloc_debug(0x2f03d00); | |
552 | #endif | |
553 | ||
f0333f44 | 554 | interactive = isatty(0); |
9fac310d | 555 | parse_args(argc, argv); |
0223d4ff | 556 | cmd_build_tree(); |
c51f132d | 557 | server_connect(); |
f0333f44 | 558 | select_loop(); |
c51f132d | 559 | return 0; |
ed608150 | 560 | } |