]> git.ipfire.org Git - thirdparty/lldpd.git/blame - src/client/lldpcli.c
client: built-in JSON support
[thirdparty/lldpd.git] / src / client / lldpcli.c
CommitLineData
4b292b55
VB
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
23e7fa38 18
4b292b55
VB
19#include <stdio.h>
20#include <stdlib.h>
21#include <unistd.h>
22#include <time.h>
23#include <errno.h>
4e5f34c5 24#include <string.h>
4b292b55 25#include <sys/types.h>
6402fd2c 26#include <sys/stat.h>
4b292b55
VB
27#include <sys/socket.h>
28#include <sys/un.h>
29#include <arpa/inet.h>
fe80711e 30#include <libgen.h>
6402fd2c 31#include <dirent.h>
003620d3 32#include <signal.h>
6402fd2c 33#include <sys/queue.h>
4b292b55 34
4b292b55
VB
35#include "client.h"
36
4b292b55
VB
37#ifdef HAVE___PROGNAME
38extern const char *__progname;
39#else
fe80711e 40# define __progname "lldpcli"
4b292b55
VB
41#endif
42
9a775667
VB
43/* Global for completion */
44static struct cmd_node *root = NULL;
fe8f9650 45const char *ctlname = NULL;
4b292b55 46
6402fd2c
VB
47static int
48is_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
4b292b55 61static void
9a775667 62usage()
4b292b55 63{
9a775667 64 fprintf(stderr, "Usage: %s [OPTIONS ...] [COMMAND ...]\n", __progname);
4b292b55
VB
65 fprintf(stderr, "Version: %s\n", PACKAGE_STRING);
66
67 fprintf(stderr, "\n");
68
69 fprintf(stderr, "-d Enable more debugging information.\n");
baaa96d1 70 fprintf(stderr, "-u socket Specify the Unix-domain socket used for communication with lldpd(8).\n");
06987a24 71 fprintf(stderr, "-f format Choose output format (plain, keyvalue, json"
3fa29406
VB
72#if defined USE_XML
73 ", xml"
3fa29406
VB
74#endif
75 ").\n");
6402fd2c 76 if (!is_lldpctl(NULL))
baaa96d1 77 fprintf(stderr, "-c conf Read the provided configuration file.\n");
4b292b55
VB
78
79 fprintf(stderr, "\n");
80
fe80711e 81 fprintf(stderr, "see manual page lldpcli(8) for more information\n");
4b292b55
VB
82 exit(1);
83}
84
9a775667
VB
85static int
86is_privileged()
87{
fe8f9650
VB
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);
9a775667
VB
92}
93
94static char*
23e7fa38 95prompt()
9a775667 96{
23e7fa38 97#define CESC "\033"
9a775667 98 int privileged = is_privileged();
23e7fa38
VB
99 if (isatty(STDIN_FILENO)) {
100 if (privileged)
101 return "[lldpcli] # ";
102 return "[lldpcli] $ ";
103 }
104 return "";
9a775667
VB
105}
106
107static int must_exit = 0;
108/**
109 * Exit the interpreter.
110 */
111static int
112cmd_exit(struct lldpctl_conn_t *conn, struct writer *w,
113 struct cmd_env *env, void *arg)
4e90a9e0 114{
fe80711e 115 log_info("lldpctl", "quit lldpcli");
9a775667
VB
116 must_exit = 1;
117 return 1;
118}
119
120/**
121 * Send an "update" request.
122 */
123static int
124cmd_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;
4e90a9e0 134 }
9a775667
VB
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;
4e90a9e0 141 }
44fb3587 142 log_info("lldpctl", "immediate retransmission requested successfully");
9a775667
VB
143 lldpctl_atom_dec_ref(config);
144 return 1;
145}
146
e4ff3ed5
VB
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 */
154static int
155cmd_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) {
4f670a1e 164 log_debug("lldpctl", "lldpd is already %s",
e4ff3ed5
VB
165 pause?"paused":"resumed");
166 lldpctl_atom_dec_ref(config);
4f670a1e 167 return 1;
e4ff3ed5
VB
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}
182static int
183cmd_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}
188static int
189cmd_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
35f6f4fb 196#ifdef HAVE_LIBREADLINE
23e7fa38
VB
197static int
198_cmd_complete(int all)
9a775667 199{
23e7fa38
VB
200 char **argv = NULL;
201 int argc = 0;
202 int rc = 1;
608b5669
VB
203 size_t len = strlen(rl_line_buffer);
204 char *line = malloc(len + 2);
23e7fa38 205 if (!line) return -1;
608b5669 206 strlcpy(line, rl_line_buffer, len + 2);
23e7fa38
VB
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)
9a775667
VB
211 goto end;
212
e13945c0 213 char *compl = commands_complete(root, argc, (const char **)argv, all, is_privileged());
07f2c567
VB
214 if (compl && strlen(argv[argc-1]) < strlen(compl)) {
215 if (rl_insert_text(compl + strlen(argv[argc-1])) < 0) {
9a775667
VB
216 free(compl);
217 goto end;
218 }
219 free(compl);
23e7fa38 220 rc = 0;
9a775667
VB
221 goto end;
222 }
23e7fa38 223 /* No completion or several completion available. */
af35cabd 224 free(compl);
35f6f4fb
VB
225 fprintf(stderr, "\n");
226 rl_forced_update_display();
23e7fa38 227 rc = 0;
9a775667 228end:
23e7fa38
VB
229 free(line);
230 tokenize_free(argc, argv);
9a775667
VB
231 return rc;
232}
233
23e7fa38
VB
234static int
235cmd_complete(int count, int ch)
9a775667 236{
23e7fa38 237 return _cmd_complete(0);
9a775667
VB
238}
239
23e7fa38
VB
240static int
241cmd_help(int count, int ch)
9a775667 242{
23e7fa38 243 return _cmd_complete(1);
9a775667 244}
35f6f4fb
VB
245#else
246static char*
02987888 247readline(const char *p)
35f6f4fb
VB
248{
249 static char line[2048];
02987888 250 fprintf(stderr, "%s", p);
35f6f4fb
VB
251 fflush(stderr);
252 if (fgets(line, sizeof(line) - 2, stdin) == NULL)
253 return NULL;
21f243c7 254 return strdup(line);
35f6f4fb
VB
255}
256#endif
9a775667 257
6402fd2c
VB
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 */
267static int
268cmd_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);
06987a24 275 else if (strcmp(fmt, "json") == 0) w = json_init(stdout);
6402fd2c
VB
276#ifdef USE_XML
277 else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout);
6402fd2c 278#endif
a7426708
VB
279 else {
280 log_warnx("lldpctl", "unknown output format \"%s\"", fmt);
281 w = txt_init(stdout);
282 }
6402fd2c
VB
283
284 /* Execute command */
285 int rc = commands_execute(conn, w,
e13945c0 286 root, argc, argv, is_privileged());
6402fd2c
VB
287 if (rc != 0) {
288 log_info("lldpctl", "an error occurred while executing last command");
289 w->finish(w);
290 return 0;
291 }
292 w->finish(w);
293 return 1;
294}
295
296/**
297 * Execute a command line and display its output.
298 *
299 * @param conn The connection to lldpd.
300 * @param fmt Output format.
301 * @param line Line to execute.
302 * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise.
303 */
304static int
305parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
306{
307 int cargc = 0; char **cargv = NULL;
308 int n;
309 log_debug("lldpctl", "tokenize command line");
310 n = tokenize_line(line, &cargc, &cargv);
311 switch (n) {
312 case -1:
313 log_warnx("lldpctl", "internal error while tokenizing");
314 return -1;
315 case 1:
316 log_warnx("lldpctl", "unmatched quotes");
317 return -1;
318 }
ddd75166
VB
319 if (cargc != 0)
320 n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
6402fd2c 321 tokenize_free(cargc, cargv);
ddd75166
VB
322 return (cargc == 0)?0:
323 (n == 0)?-1:
324 1;
6402fd2c
VB
325}
326
9a775667
VB
327static struct cmd_node*
328register_commands()
329{
330 root = commands_root();
331 register_commands_show(root);
332 register_commands_watch(root);
e13945c0
VB
333 commands_privileged(commands_new(
334 commands_new(root, "update", "Update information and send LLDPU on all ports",
335 NULL, NULL, NULL),
336 NEWLINE, "Update information and send LLDPU on all ports",
337 NULL, cmd_update, NULL));
338 register_commands_configure(root);
314f382a
VB
339 commands_hidden(commands_new(root, "complete", "Get possible completions from a given command",
340 NULL, cmd_store_env_and_pop, "complete"));
25272118
VB
341 commands_new(root, "help", "Get help on a possible command",
342 NULL, cmd_store_env_and_pop, "help");
e4ff3ed5
VB
343 commands_new(
344 commands_new(root, "pause", "Pause lldpd operations", NULL, NULL, NULL),
345 NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL);
346 commands_new(
347 commands_new(root, "resume", "Resume lldpd operations", NULL, NULL, NULL),
348 NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL);
9a775667
VB
349 commands_new(
350 commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
351 NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
352 return root;
4e90a9e0
VB
353}
354
6402fd2c
VB
355struct input {
356 TAILQ_ENTRY(input) next;
357 char *name;
358};
359TAILQ_HEAD(inputs, input);
fe80711e 360static int
6402fd2c 361filter(const struct dirent *dir)
fe80711e 362{
6402fd2c
VB
363 if (strlen(dir->d_name) < 5) return 0;
364 if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0;
365 return 1;
366}
367
368/**
369 * Append a new input file/directory to the list of inputs.
370 *
371 * @param arg Directory or file name to add.
372 * @param inputs List of inputs
373 * @param acceptdir 1 if we accept a directory, 0 otherwise
374 */
375static void
e9447d45 376input_append(const char *arg, struct inputs *inputs, int acceptdir, int warn)
6402fd2c
VB
377{
378 struct stat statbuf;
379 if (stat(arg, &statbuf) == -1) {
e9447d45
VB
380 if (warn) {
381 log_warn("lldpctl", "cannot find configuration file/directory %s",
382 arg);
383 } else {
384 log_debug("lldpctl", "cannot find configuration file/directory %s",
385 arg);
386 }
6402fd2c 387 return;
fe80711e 388 }
6402fd2c
VB
389
390 if (!S_ISDIR(statbuf.st_mode)) {
391 struct input *input = malloc(sizeof(struct input));
392 if (!input) {
393 log_warn("lldpctl", "not enough memory to process %s",
394 arg);
395 return;
396 }
397 log_debug("lldpctl", "input: %s", arg);
398 input->name = strdup(arg);
399 TAILQ_INSERT_TAIL(inputs, input, next);
400 return;
401 }
402 if (!acceptdir) {
403 log_debug("lldpctl", "skip directory %s",
404 arg);
405 return;
406 }
407
408 struct dirent **namelist = NULL;
409 int n = scandir(arg, &namelist, filter, alphasort);
410 if (n < 0) {
411 log_warnx("lldpctl", "unable to read directory %s",
412 arg);
413 return;
414 }
415 for (int i=0; i < n; i++) {
416 char *fullname;
417 if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) {
e9447d45 418 input_append(fullname, inputs, 0, 1);
6402fd2c
VB
419 free(fullname);
420 }
421 free(namelist[i]);
422 }
423 free(namelist);
fe80711e
VB
424}
425
4b292b55
VB
426int
427main(int argc, char *argv[])
428{
9856f279 429 int ch, debug = 0, use_syslog = 0, rc = EXIT_FAILURE;
6402fd2c 430 const char *fmt = "plain";
048355f3 431 lldpctl_conn_t *conn = NULL;
e9447d45 432 const char *options = is_lldpctl(argv[0])?"hdvf:u:":"hdsvf:c:C:u:";
9a775667 433
efa6a7a3 434 int gotinputs = 0, version = 0;
6402fd2c
VB
435 struct inputs inputs;
436 TAILQ_INIT(&inputs);
437
fe8f9650
VB
438 ctlname = lldpctl_get_default_transport();
439
003620d3
ST
440 signal(SIGHUP, SIG_IGN);
441
4b292b55 442 /* Get and parse command line options */
6402fd2c
VB
443 optind = 1;
444 while ((ch = getopt(argc, argv, options)) != -1) {
4b292b55 445 switch (ch) {
9856f279
VB
446 case 'd':
447 if (use_syslog)
448 use_syslog = 0;
449 else
450 debug++;
451 break;
452 case 's':
453 if (debug == 0)
454 use_syslog = 1;
455 else
456 debug--;
457 break;
4b292b55
VB
458 case 'h':
459 usage();
460 break;
0262adbb
ZM
461 case 'u':
462 ctlname = optarg;
463 break;
4b292b55 464 case 'v':
efa6a7a3 465 version++;
4b292b55 466 break;
4b292b55
VB
467 case 'f':
468 fmt = optarg;
469 break;
e9447d45 470 case 'C':
6402fd2c 471 case 'c':
2b00cbf9 472 if (!gotinputs) {
9856f279
VB
473 log_init(use_syslog, debug, __progname);
474 lldpctl_log_level(debug + 1);
2b00cbf9
VB
475 gotinputs = 1;
476 }
e9447d45 477 input_append(optarg, &inputs, 1, ch == 'c');
6402fd2c 478 break;
4b292b55
VB
479 default:
480 usage();
481 }
482 }
483
efa6a7a3
VB
484 if (version) {
485 version_display(stdout, "lldpcli", version > 1);
486 exit(0);
487 }
488
2b00cbf9 489 if (!gotinputs) {
9856f279
VB
490 log_init(use_syslog, debug, __progname);
491 lldpctl_log_level(debug + 1);
2b00cbf9
VB
492 }
493
2c4dda06
VB
494 /* Disable SIGPIPE */
495 signal(SIGPIPE, SIG_IGN);
496
9a775667
VB
497 /* Register commands */
498 root = register_commands();
499
6402fd2c
VB
500 /* Make a connection */
501 log_debug("lldpctl", "connect to lldpd");
0262adbb 502 conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
6402fd2c
VB
503 if (conn == NULL) goto end;
504
505 /* Process file inputs */
506 while (gotinputs && !TAILQ_EMPTY(&inputs)) {
87dfd175
VB
507 /* coverity[use_after_free]
508 TAILQ_REMOVE does the right thing */
6402fd2c
VB
509 struct input *first = TAILQ_FIRST(&inputs);
510 log_debug("lldpctl", "process: %s", first->name);
511 FILE *file = fopen(first->name, "r");
512 if (file) {
af828c47
VB
513 size_t n;
514 ssize_t len;
6402fd2c 515 char *line;
af828c47 516 while (line = NULL, len = 0, (len = getline(&line, &n, file)) > 0) {
6402fd2c
VB
517 if (line[len - 1] == '\n') {
518 line[len - 1] = '\0';
3ea0bccf 519 parse_and_exec(conn, fmt, line);
6402fd2c 520 }
5920dbf7 521 free(line);
6402fd2c 522 }
af828c47 523 free(line);
6402fd2c
VB
524 fclose(file);
525 } else {
526 log_warn("lldpctl", "unable to open %s",
527 first->name);
528 }
529 TAILQ_REMOVE(&inputs, first, next);
530 free(first->name);
531 free(first);
532 }
533
534 /* Process additional arguments. First if we are lldpctl (interfaces) */
535 if (is_lldpctl(NULL)) {
536 char *line = NULL;
fe80711e 537 for (int i = optind; i < argc; i++) {
6402fd2c
VB
538 char *prev = line;
539 if (asprintf(&line, "%s%s%s",
540 prev?prev:"show neigh ports ", argv[i],
541 (i == argc - 1)?" details":",") == -1) {
fe80711e 542 log_warnx("lldpctl", "not enough memory to build list of interfaces");
6402fd2c 543 free(prev);
fe80711e
VB
544 goto end;
545 }
546 free(prev);
547 }
6402fd2c
VB
548 if (line == NULL && (line = strdup("show neigh details")) == NULL) {
549 log_warnx("lldpctl", "not enough memory to build command line");
550 goto end;
551 }
552 log_debug("lldpctl", "execute %s", line);
553 if (parse_and_exec(conn, fmt, line) != -1)
554 rc = EXIT_SUCCESS;
555 free(line);
556 goto end;
4e90a9e0
VB
557 }
558
6402fd2c
VB
559 /* Then, if we are regular lldpcli (command line) */
560 if (optind < argc) {
561 const char **cargv;
562 int cargc;
563 cargv = &((const char **)argv)[optind];
564 cargc = argc - optind;
6835f334 565 if (cmd_exec(conn, fmt, cargc, cargv) == 1)
6402fd2c
VB
566 rc = EXIT_SUCCESS;
567 goto end;
568 }
9a775667 569
cdcf7dc5
VB
570 if (gotinputs) {
571 rc = EXIT_SUCCESS;
572 goto end;
573 }
9a775667 574
6402fd2c
VB
575 /* Interactive session */
576#ifdef HAVE_LIBREADLINE
577 rl_bind_key('?', cmd_help);
578 rl_bind_key('\t', cmd_complete);
8b7150e4 579#endif
21f243c7 580 char *line = NULL;
6402fd2c
VB
581 do {
582 if ((line = readline(prompt()))) {
583 int n = parse_and_exec(conn, fmt, line);
21f243c7 584 if (n != 0) {
6402fd2c 585#ifdef HAVE_READLINE_HISTORY
21f243c7 586 add_history(line);
4e90a9e0 587#endif
21f243c7
VB
588 }
589 free(line);
fe80711e 590 }
4a22e2c1 591 } while (!must_exit && line != NULL);
9a775667 592 rc = EXIT_SUCCESS;
6402fd2c 593
9a775667 594end:
6402fd2c 595 while (!TAILQ_EMPTY(&inputs)) {
87dfd175
VB
596 /* coverity[use_after_free]
597 TAILQ_REMOVE does the right thing */
6402fd2c
VB
598 struct input *first = TAILQ_FIRST(&inputs);
599 TAILQ_REMOVE(&inputs, first, next);
600 free(first->name);
601 free(first);
602 }
9a775667 603 if (conn) lldpctl_release(conn);
9a775667
VB
604 if (root) commands_free(root);
605 return rc;
4b292b55 606}