]> git.ipfire.org Git - thirdparty/lldpd.git/blob - src/client/lldpcli.c
client: add show interfaces command (#240)
[thirdparty/lldpd.git] / src / client / lldpcli.c
1 /* -*- mode: c; c-file-style: "openbsd" -*- */
2 /*
3 * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <time.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/socket.h>
28 #include <sys/un.h>
29 #include <arpa/inet.h>
30 #include <libgen.h>
31 #include <dirent.h>
32 #include <signal.h>
33 #include <sys/queue.h>
34
35 #include "client.h"
36
37 #ifdef HAVE___PROGNAME
38 extern const char *__progname;
39 #else
40 # define __progname "lldpcli"
41 #endif
42
43 /* Global for completion */
44 static struct cmd_node *root = NULL;
45 const char *ctlname = NULL;
46
47 static int
48 is_lldpctl(const char *name)
49 {
50 static int last_result = -1;
51 if (last_result == -1 && name) {
52 char *basec = strdup(name);
53 if (!basec) return 0;
54 char *bname = basename(basec);
55 last_result = (!strcmp(bname, "lldpctl"));
56 free(basec);
57 }
58 return (last_result == -1)?0:last_result;
59 }
60
61 static void
62 usage()
63 {
64 fprintf(stderr, "Usage: %s [OPTIONS ...] [COMMAND ...]\n", __progname);
65 fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
66
67 fprintf(stderr, "\n");
68
69 fprintf(stderr, "-d Enable more debugging information.\n");
70 fprintf(stderr, "-u socket Specify the Unix-domain socket used for communication with lldpd(8).\n");
71 fprintf(stderr, "-f format Choose output format (plain, keyvalue, json"
72 #if defined USE_XML
73 ", xml"
74 #endif
75 ").\n");
76 if (!is_lldpctl(NULL))
77 fprintf(stderr, "-c conf Read the provided configuration file.\n");
78
79 fprintf(stderr, "\n");
80
81 fprintf(stderr, "see manual page lldpcli(8) for more information\n");
82 exit(1);
83 }
84
85 static int
86 is_privileged()
87 {
88 /* Check we can access the control socket with read/write
89 * privileges. The `access()` function uses the real UID and real GID,
90 * therefore we don't have to mangle with our identity. */
91 return (ctlname && access(ctlname, R_OK|W_OK) == 0);
92 }
93
94 static char*
95 prompt()
96 {
97 #define CESC "\033"
98 int privileged = is_privileged();
99 if (isatty(STDIN_FILENO)) {
100 if (privileged)
101 return "[lldpcli] # ";
102 return "[lldpcli] $ ";
103 }
104 return "";
105 }
106
107 static int must_exit = 0;
108 /**
109 * Exit the interpreter.
110 */
111 static int
112 cmd_exit(struct lldpctl_conn_t *conn, struct writer *w,
113 struct cmd_env *env, void *arg)
114 {
115 log_info("lldpctl", "quit lldpcli");
116 must_exit = 1;
117 return 1;
118 }
119
120 /**
121 * Send an "update" request.
122 */
123 static int
124 cmd_update(struct lldpctl_conn_t *conn, struct writer *w,
125 struct cmd_env *env, void *arg)
126 {
127 log_info("lldpctl", "ask for global update");
128
129 lldpctl_atom_t *config = lldpctl_get_configuration(conn);
130 if (config == NULL) {
131 log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
132 lldpctl_last_strerror(conn));
133 return 0;
134 }
135 if (lldpctl_atom_set_int(config,
136 lldpctl_k_config_tx_interval, -1) == NULL) {
137 log_warnx("lldpctl", "unable to ask lldpd for immediate retransmission. %s",
138 lldpctl_last_strerror(conn));
139 lldpctl_atom_dec_ref(config);
140 return 0;
141 }
142 log_info("lldpctl", "immediate retransmission requested successfully");
143 lldpctl_atom_dec_ref(config);
144 return 1;
145 }
146
147 /**
148 * Pause or resume execution of lldpd.
149 *
150 * @param conn The connection to lldpd.
151 * @param pause 1 if we want to pause lldpd, 0 otherwise
152 * @return 1 on success, 0 on error
153 */
154 static int
155 cmd_pause_resume(lldpctl_conn_t *conn, int pause)
156 {
157 lldpctl_atom_t *config = lldpctl_get_configuration(conn);
158 if (config == NULL) {
159 log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
160 lldpctl_last_strerror(conn));
161 return 0;
162 }
163 if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) {
164 log_debug("lldpctl", "lldpd is already %s",
165 pause?"paused":"resumed");
166 lldpctl_atom_dec_ref(config);
167 return 1;
168 }
169 if (lldpctl_atom_set_int(config,
170 lldpctl_k_config_paused, pause) == NULL) {
171 log_warnx("lldpctl", "unable to ask lldpd to %s operations. %s",
172 pause?"pause":"resume",
173 lldpctl_last_strerror(conn));
174 lldpctl_atom_dec_ref(config);
175 return 0;
176 }
177 log_info("lldpctl", "lldpd should %s operations",
178 pause?"pause":"resume");
179 lldpctl_atom_dec_ref(config);
180 return 1;
181 }
182 static int
183 cmd_pause(struct lldpctl_conn_t *conn, struct writer *w,
184 struct cmd_env *env, void *arg) {
185 (void)w; (void)env;
186 return cmd_pause_resume(conn, 1);
187 }
188 static int
189 cmd_resume(struct lldpctl_conn_t *conn, struct writer *w,
190 struct cmd_env *env, void *arg) {
191 (void)w; (void)env;
192 return cmd_pause_resume(conn, 0);
193 }
194
195
196 #ifdef HAVE_LIBREADLINE
197 static int
198 _cmd_complete(int all)
199 {
200 char **argv = NULL;
201 int argc = 0;
202 int rc = 1;
203 size_t len = strlen(rl_line_buffer);
204 char *line = malloc(len + 2);
205 if (!line) return -1;
206 strlcpy(line, rl_line_buffer, len + 2);
207 line[rl_point] = 2; /* empty character, will force a word */
208 line[rl_point+1] = 0;
209
210 if (tokenize_line(line, &argc, &argv) != 0)
211 goto end;
212
213 char *compl = commands_complete(root, argc, (const char **)argv, all, is_privileged());
214 if (compl && strlen(argv[argc-1]) < strlen(compl)) {
215 if (rl_insert_text(compl + strlen(argv[argc-1])) < 0) {
216 free(compl);
217 goto end;
218 }
219 free(compl);
220 rc = 0;
221 goto end;
222 }
223 /* No completion or several completion available. */
224 free(compl);
225 fprintf(stderr, "\n");
226 rl_forced_update_display();
227 rc = 0;
228 end:
229 free(line);
230 tokenize_free(argc, argv);
231 return rc;
232 }
233
234 static int
235 cmd_complete(int count, int ch)
236 {
237 return _cmd_complete(0);
238 }
239
240 static int
241 cmd_help(int count, int ch)
242 {
243 return _cmd_complete(1);
244 }
245 #else
246 static char*
247 readline(const char *p)
248 {
249 static char line[2048];
250 fprintf(stderr, "%s", p);
251 fflush(stderr);
252 if (fgets(line, sizeof(line) - 2, stdin) == NULL)
253 return NULL;
254 return strdup(line);
255 }
256 #endif
257
258 /**
259 * Execute a tokenized command and display its output.
260 *
261 * @param conn The connection to lldpd.
262 * @param fmt Output format.
263 * @param argc Number of arguments.
264 * @param argv Array of arguments.
265 * @return 0 if an error occurred, 1 otherwise
266 */
267 static int
268 cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv)
269 {
270 /* Init output formatter */
271 struct writer *w;
272
273 if (strcmp(fmt, "plain") == 0) w = txt_init(stdout);
274 else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout);
275 else if (strcmp(fmt, "json") == 0) w = json_init(stdout, 1);
276 else if (strcmp(fmt, "json0") == 0) w = json_init(stdout, 0);
277 #ifdef USE_XML
278 else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout);
279 #endif
280 else {
281 log_warnx("lldpctl", "unknown output format \"%s\"", fmt);
282 w = txt_init(stdout);
283 }
284
285 /* Execute command */
286 int rc = commands_execute(conn, w,
287 root, argc, argv, is_privileged());
288 if (rc != 0) {
289 log_info("lldpctl", "an error occurred while executing last command");
290 w->finish(w);
291 return 0;
292 }
293 w->finish(w);
294 return 1;
295 }
296
297 /**
298 * Execute a command line and display its output.
299 *
300 * @param conn The connection to lldpd.
301 * @param fmt Output format.
302 * @param line Line to execute.
303 * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise.
304 */
305 static int
306 parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
307 {
308 int cargc = 0; char **cargv = NULL;
309 int n;
310 log_debug("lldpctl", "tokenize command line");
311 n = tokenize_line(line, &cargc, &cargv);
312 switch (n) {
313 case -1:
314 log_warnx("lldpctl", "internal error while tokenizing");
315 return -1;
316 case 1:
317 log_warnx("lldpctl", "unmatched quotes");
318 return -1;
319 }
320 if (cargc != 0)
321 n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
322 tokenize_free(cargc, cargv);
323 return (cargc == 0)?0:
324 (n == 0)?-1:
325 1;
326 }
327
328 static struct cmd_node*
329 register_commands()
330 {
331 root = commands_root();
332 register_commands_show(root);
333 register_commands_watch(root);
334 commands_privileged(commands_new(
335 commands_new(root, "update", "Update information and send LLDPU on all ports",
336 NULL, NULL, NULL),
337 NEWLINE, "Update information and send LLDPU on all ports",
338 NULL, cmd_update, NULL));
339 register_commands_configure(root);
340 commands_hidden(commands_new(root, "complete", "Get possible completions from a given command",
341 NULL, cmd_store_env_and_pop, "complete"));
342 commands_new(root, "help", "Get help on a possible command",
343 NULL, cmd_store_env_and_pop, "help");
344 commands_new(
345 commands_new(root, "pause", "Pause lldpd operations", NULL, NULL, NULL),
346 NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL);
347 commands_new(
348 commands_new(root, "resume", "Resume lldpd operations", NULL, NULL, NULL),
349 NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL);
350 commands_new(
351 commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
352 NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
353 return root;
354 }
355
356 struct input {
357 TAILQ_ENTRY(input) next;
358 char *name;
359 };
360 TAILQ_HEAD(inputs, input);
361 static int
362 filter(const struct dirent *dir)
363 {
364 if (strlen(dir->d_name) < 5) return 0;
365 if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0;
366 return 1;
367 }
368
369 /**
370 * Append a new input file/directory to the list of inputs.
371 *
372 * @param arg Directory or file name to add.
373 * @param inputs List of inputs
374 * @param acceptdir 1 if we accept a directory, 0 otherwise
375 */
376 static void
377 input_append(const char *arg, struct inputs *inputs, int acceptdir, int warn)
378 {
379 struct stat statbuf;
380 if (stat(arg, &statbuf) == -1) {
381 if (warn) {
382 log_warn("lldpctl", "cannot find configuration file/directory %s",
383 arg);
384 } else {
385 log_debug("lldpctl", "cannot find configuration file/directory %s",
386 arg);
387 }
388 return;
389 }
390
391 if (!S_ISDIR(statbuf.st_mode)) {
392 struct input *input = malloc(sizeof(struct input));
393 if (!input) {
394 log_warn("lldpctl", "not enough memory to process %s",
395 arg);
396 return;
397 }
398 log_debug("lldpctl", "input: %s", arg);
399 input->name = strdup(arg);
400 TAILQ_INSERT_TAIL(inputs, input, next);
401 return;
402 }
403 if (!acceptdir) {
404 log_debug("lldpctl", "skip directory %s",
405 arg);
406 return;
407 }
408
409 struct dirent **namelist = NULL;
410 int n = scandir(arg, &namelist, filter, alphasort);
411 if (n < 0) {
412 log_warnx("lldpctl", "unable to read directory %s",
413 arg);
414 return;
415 }
416 for (int i=0; i < n; i++) {
417 char *fullname;
418 if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) {
419 input_append(fullname, inputs, 0, 1);
420 free(fullname);
421 }
422 free(namelist[i]);
423 }
424 free(namelist);
425 }
426
427 int
428 main(int argc, char *argv[])
429 {
430 int ch, debug = 0, use_syslog = 0, rc = EXIT_FAILURE;
431 const char *fmt = "plain";
432 lldpctl_conn_t *conn = NULL;
433 const char *options = is_lldpctl(argv[0])?"hdvf:u:":"hdsvf:c:C:u:";
434
435 int gotinputs = 0, version = 0;
436 struct inputs inputs;
437 TAILQ_INIT(&inputs);
438
439 ctlname = lldpctl_get_default_transport();
440
441 signal(SIGHUP, SIG_IGN);
442
443 /* Get and parse command line options */
444 optind = 1;
445 while ((ch = getopt(argc, argv, options)) != -1) {
446 switch (ch) {
447 case 'd':
448 if (use_syslog)
449 use_syslog = 0;
450 else
451 debug++;
452 break;
453 case 's':
454 if (debug == 0)
455 use_syslog = 1;
456 else
457 debug--;
458 break;
459 case 'h':
460 usage();
461 break;
462 case 'u':
463 ctlname = optarg;
464 break;
465 case 'v':
466 version++;
467 break;
468 case 'f':
469 fmt = optarg;
470 break;
471 case 'C':
472 case 'c':
473 if (!gotinputs) {
474 log_init(use_syslog, debug, __progname);
475 lldpctl_log_level(debug + 1);
476 gotinputs = 1;
477 }
478 input_append(optarg, &inputs, 1, ch == 'c');
479 break;
480 default:
481 usage();
482 }
483 }
484
485 if (version) {
486 version_display(stdout, "lldpcli", version > 1);
487 exit(0);
488 }
489
490 if (!gotinputs) {
491 log_init(use_syslog, debug, __progname);
492 lldpctl_log_level(debug + 1);
493 }
494
495 /* Disable SIGPIPE */
496 signal(SIGPIPE, SIG_IGN);
497
498 /* Register commands */
499 root = register_commands();
500
501 /* Make a connection */
502 log_debug("lldpctl", "connect to lldpd");
503 conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
504 if (conn == NULL) goto end;
505
506 /* Process file inputs */
507 while (gotinputs && !TAILQ_EMPTY(&inputs)) {
508 /* coverity[use_after_free]
509 TAILQ_REMOVE does the right thing */
510 struct input *first = TAILQ_FIRST(&inputs);
511 log_debug("lldpctl", "process: %s", first->name);
512 FILE *file = fopen(first->name, "r");
513 if (file) {
514 size_t n;
515 ssize_t len;
516 char *line;
517 while (line = NULL, len = 0, (len = getline(&line, &n, file)) > 0) {
518 if (line[len - 1] == '\n') {
519 line[len - 1] = '\0';
520 parse_and_exec(conn, fmt, line);
521 }
522 free(line);
523 }
524 free(line);
525 fclose(file);
526 } else {
527 log_warn("lldpctl", "unable to open %s",
528 first->name);
529 }
530 TAILQ_REMOVE(&inputs, first, next);
531 free(first->name);
532 free(first);
533 }
534
535 /* Process additional arguments. First if we are lldpctl (interfaces) */
536 if (is_lldpctl(NULL)) {
537 char *line = NULL;
538 for (int i = optind; i < argc; i++) {
539 char *prev = line;
540 if (asprintf(&line, "%s%s%s",
541 prev?prev:"show neigh ports ", argv[i],
542 (i == argc - 1)?" details":",") == -1) {
543 log_warnx("lldpctl", "not enough memory to build list of interfaces");
544 free(prev);
545 goto end;
546 }
547 free(prev);
548 }
549 if (line == NULL && (line = strdup("show neigh details")) == NULL) {
550 log_warnx("lldpctl", "not enough memory to build command line");
551 goto end;
552 }
553 log_debug("lldpctl", "execute %s", line);
554 if (parse_and_exec(conn, fmt, line) != -1)
555 rc = EXIT_SUCCESS;
556 free(line);
557 goto end;
558 }
559
560 /* Then, if we are regular lldpcli (command line) */
561 if (optind < argc) {
562 const char **cargv;
563 int cargc;
564 cargv = &((const char **)argv)[optind];
565 cargc = argc - optind;
566 if (cmd_exec(conn, fmt, cargc, cargv) == 1)
567 rc = EXIT_SUCCESS;
568 goto end;
569 }
570
571 if (gotinputs) {
572 rc = EXIT_SUCCESS;
573 goto end;
574 }
575
576 /* Interactive session */
577 #ifdef HAVE_LIBREADLINE
578 rl_bind_key('?', cmd_help);
579 rl_bind_key('\t', cmd_complete);
580 #endif
581 char *line = NULL;
582 do {
583 if ((line = readline(prompt()))) {
584 int n = parse_and_exec(conn, fmt, line);
585 if (n != 0) {
586 #ifdef HAVE_READLINE_HISTORY
587 add_history(line);
588 #endif
589 }
590 free(line);
591 }
592 } while (!must_exit && line != NULL);
593 rc = EXIT_SUCCESS;
594
595 end:
596 while (!TAILQ_EMPTY(&inputs)) {
597 /* coverity[use_after_free]
598 TAILQ_REMOVE does the right thing */
599 struct input *first = TAILQ_FIRST(&inputs);
600 TAILQ_REMOVE(&inputs, first, next);
601 free(first->name);
602 free(first);
603 }
604 if (conn) lldpctl_release(conn);
605 if (root) commands_free(root);
606 return rc;
607 }