]> git.ipfire.org Git - thirdparty/lldpd.git/blame - src/client/lldpcli.c
lldpcli: reformat a bit bash completion to be more readable
[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");
4b292b55 71 fprintf(stderr, "-f format Choose output format (plain, keyvalue or xml).\n");
6402fd2c 72 if (!is_lldpctl(NULL))
baaa96d1 73 fprintf(stderr, "-c conf Read the provided configuration file.\n");
4b292b55
VB
74
75 fprintf(stderr, "\n");
76
fe80711e 77 fprintf(stderr, "see manual page lldpcli(8) for more information\n");
4b292b55
VB
78 exit(1);
79}
80
9a775667
VB
81static int
82is_privileged()
83{
fe8f9650
VB
84 /* Check we can access the control socket with read/write
85 * privileges. The `access()` function uses the real UID and real GID,
86 * therefore we don't have to mangle with our identity. */
87 return (ctlname && access(ctlname, R_OK|W_OK) == 0);
9a775667
VB
88}
89
90static char*
23e7fa38 91prompt()
9a775667 92{
23e7fa38 93#define CESC "\033"
9a775667 94 int privileged = is_privileged();
23e7fa38
VB
95 if (isatty(STDIN_FILENO)) {
96 if (privileged)
97 return "[lldpcli] # ";
98 return "[lldpcli] $ ";
99 }
100 return "";
9a775667
VB
101}
102
103static int must_exit = 0;
104/**
105 * Exit the interpreter.
106 */
107static int
108cmd_exit(struct lldpctl_conn_t *conn, struct writer *w,
109 struct cmd_env *env, void *arg)
4e90a9e0 110{
fe80711e 111 log_info("lldpctl", "quit lldpcli");
9a775667
VB
112 must_exit = 1;
113 return 1;
114}
115
116/**
117 * Send an "update" request.
118 */
119static int
120cmd_update(struct lldpctl_conn_t *conn, struct writer *w,
121 struct cmd_env *env, void *arg)
122{
123 log_info("lldpctl", "ask for global update");
124
125 lldpctl_atom_t *config = lldpctl_get_configuration(conn);
126 if (config == NULL) {
127 log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
128 lldpctl_last_strerror(conn));
129 return 0;
4e90a9e0 130 }
9a775667
VB
131 if (lldpctl_atom_set_int(config,
132 lldpctl_k_config_tx_interval, -1) == NULL) {
133 log_warnx("lldpctl", "unable to ask lldpd for immediate retransmission. %s",
134 lldpctl_last_strerror(conn));
135 lldpctl_atom_dec_ref(config);
136 return 0;
4e90a9e0 137 }
9a775667
VB
138 log_info("lldpctl", "immediate retransmission requested successfuly");
139 lldpctl_atom_dec_ref(config);
140 return 1;
141}
142
e4ff3ed5
VB
143/**
144 * Pause or resume execution of lldpd.
145 *
146 * @param conn The connection to lldpd.
147 * @param pause 1 if we want to pause lldpd, 0 otherwise
148 * @return 1 on success, 0 on error
149 */
150static int
151cmd_pause_resume(lldpctl_conn_t *conn, int pause)
152{
153 lldpctl_atom_t *config = lldpctl_get_configuration(conn);
154 if (config == NULL) {
155 log_warnx("lldpctl", "unable to get configuration from lldpd. %s",
156 lldpctl_last_strerror(conn));
157 return 0;
158 }
159 if (lldpctl_atom_get_int(config, lldpctl_k_config_paused) == pause) {
4f670a1e 160 log_debug("lldpctl", "lldpd is already %s",
e4ff3ed5
VB
161 pause?"paused":"resumed");
162 lldpctl_atom_dec_ref(config);
4f670a1e 163 return 1;
e4ff3ed5
VB
164 }
165 if (lldpctl_atom_set_int(config,
166 lldpctl_k_config_paused, pause) == NULL) {
167 log_warnx("lldpctl", "unable to ask lldpd to %s operations. %s",
168 pause?"pause":"resume",
169 lldpctl_last_strerror(conn));
170 lldpctl_atom_dec_ref(config);
171 return 0;
172 }
173 log_info("lldpctl", "lldpd should %s operations",
174 pause?"pause":"resume");
175 lldpctl_atom_dec_ref(config);
176 return 1;
177}
178static int
179cmd_pause(struct lldpctl_conn_t *conn, struct writer *w,
180 struct cmd_env *env, void *arg) {
181 (void)w; (void)env;
182 return cmd_pause_resume(conn, 1);
183}
184static int
185cmd_resume(struct lldpctl_conn_t *conn, struct writer *w,
186 struct cmd_env *env, void *arg) {
187 (void)w; (void)env;
188 return cmd_pause_resume(conn, 0);
189}
190
191
35f6f4fb 192#ifdef HAVE_LIBREADLINE
23e7fa38
VB
193static int
194_cmd_complete(int all)
9a775667 195{
23e7fa38
VB
196 char **argv = NULL;
197 int argc = 0;
198 int rc = 1;
608b5669
VB
199 size_t len = strlen(rl_line_buffer);
200 char *line = malloc(len + 2);
23e7fa38 201 if (!line) return -1;
608b5669 202 strlcpy(line, rl_line_buffer, len + 2);
23e7fa38
VB
203 line[rl_point] = 2; /* empty character, will force a word */
204 line[rl_point+1] = 0;
205
206 if (tokenize_line(line, &argc, &argv) != 0)
9a775667
VB
207 goto end;
208
23e7fa38 209 char *compl = commands_complete(root, argc, (const char **)argv, all);
07f2c567
VB
210 if (compl && strlen(argv[argc-1]) < strlen(compl)) {
211 if (rl_insert_text(compl + strlen(argv[argc-1])) < 0) {
9a775667
VB
212 free(compl);
213 goto end;
214 }
215 free(compl);
23e7fa38 216 rc = 0;
9a775667
VB
217 goto end;
218 }
23e7fa38 219 /* No completion or several completion available. */
af35cabd 220 free(compl);
35f6f4fb
VB
221 fprintf(stderr, "\n");
222 rl_forced_update_display();
23e7fa38 223 rc = 0;
9a775667 224end:
23e7fa38
VB
225 free(line);
226 tokenize_free(argc, argv);
9a775667
VB
227 return rc;
228}
229
23e7fa38
VB
230static int
231cmd_complete(int count, int ch)
9a775667 232{
23e7fa38 233 return _cmd_complete(0);
9a775667
VB
234}
235
23e7fa38
VB
236static int
237cmd_help(int count, int ch)
9a775667 238{
23e7fa38 239 return _cmd_complete(1);
9a775667 240}
35f6f4fb
VB
241#else
242static char*
02987888 243readline(const char *p)
35f6f4fb
VB
244{
245 static char line[2048];
02987888 246 fprintf(stderr, "%s", p);
35f6f4fb
VB
247 fflush(stderr);
248 if (fgets(line, sizeof(line) - 2, stdin) == NULL)
249 return NULL;
250 return line;
251}
252#endif
9a775667 253
6402fd2c
VB
254/**
255 * Execute a tokenized command and display its output.
256 *
257 * @param conn The connection to lldpd.
258 * @param fmt Output format.
259 * @param argc Number of arguments.
260 * @param argv Array of arguments.
261 * @return 0 if an error occurred, 1 otherwise
262 */
263static int
264cmd_exec(lldpctl_conn_t *conn, const char *fmt, int argc, const char **argv)
265{
266 /* Init output formatter */
267 struct writer *w;
268
269 if (strcmp(fmt, "plain") == 0) w = txt_init(stdout);
270 else if (strcmp(fmt, "keyvalue") == 0) w = kv_init(stdout);
271#ifdef USE_XML
272 else if (strcmp(fmt, "xml") == 0) w = xml_init(stdout);
273#endif
274#ifdef USE_JSON
275 else if (strcmp(fmt, "json") == 0) w = json_init(stdout);
276#endif
277 else w = txt_init(stdout);
278
279 /* Execute command */
280 int rc = commands_execute(conn, w,
281 root, argc, argv);
282 if (rc != 0) {
283 log_info("lldpctl", "an error occurred while executing last command");
284 w->finish(w);
285 return 0;
286 }
287 w->finish(w);
288 return 1;
289}
290
291/**
292 * Execute a command line and display its output.
293 *
294 * @param conn The connection to lldpd.
295 * @param fmt Output format.
296 * @param line Line to execute.
297 * @return -1 if an error occurred, 0 if nothing was executed. 1 otherwise.
298 */
299static int
300parse_and_exec(lldpctl_conn_t *conn, const char *fmt, const char *line)
301{
302 int cargc = 0; char **cargv = NULL;
303 int n;
304 log_debug("lldpctl", "tokenize command line");
305 n = tokenize_line(line, &cargc, &cargv);
306 switch (n) {
307 case -1:
308 log_warnx("lldpctl", "internal error while tokenizing");
309 return -1;
310 case 1:
311 log_warnx("lldpctl", "unmatched quotes");
312 return -1;
313 }
ddd75166
VB
314 if (cargc != 0)
315 n = cmd_exec(conn, fmt, cargc, (const char **)cargv);
6402fd2c 316 tokenize_free(cargc, cargv);
ddd75166
VB
317 return (cargc == 0)?0:
318 (n == 0)?-1:
319 1;
6402fd2c
VB
320}
321
9a775667
VB
322static struct cmd_node*
323register_commands()
324{
325 root = commands_root();
326 register_commands_show(root);
327 register_commands_watch(root);
328 if (is_privileged()) {
329 commands_new(
330 commands_new(root, "update", "Update information and send LLDPU on all ports",
331 NULL, NULL, NULL),
332 NEWLINE, "Update information and send LLDPU on all ports",
333 NULL, cmd_update, NULL);
334 register_commands_configure(root);
335 }
25272118
VB
336 commands_new(root, "help", "Get help on a possible command",
337 NULL, cmd_store_env_and_pop, "help");
e4ff3ed5
VB
338 commands_new(
339 commands_new(root, "pause", "Pause lldpd operations", NULL, NULL, NULL),
340 NEWLINE, "Pause lldpd operations", NULL, cmd_pause, NULL);
341 commands_new(
342 commands_new(root, "resume", "Resume lldpd operations", NULL, NULL, NULL),
343 NEWLINE, "Resume lldpd operations", NULL, cmd_resume, NULL);
9a775667
VB
344 commands_new(
345 commands_new(root, "exit", "Exit interpreter", NULL, NULL, NULL),
346 NEWLINE, "Exit interpreter", NULL, cmd_exit, NULL);
347 return root;
4e90a9e0
VB
348}
349
6402fd2c
VB
350struct input {
351 TAILQ_ENTRY(input) next;
352 char *name;
353};
354TAILQ_HEAD(inputs, input);
fe80711e 355static int
6402fd2c 356filter(const struct dirent *dir)
fe80711e 357{
6402fd2c
VB
358 if (strlen(dir->d_name) < 5) return 0;
359 if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".conf")) return 0;
360 return 1;
361}
362
363/**
364 * Append a new input file/directory to the list of inputs.
365 *
366 * @param arg Directory or file name to add.
367 * @param inputs List of inputs
368 * @param acceptdir 1 if we accept a directory, 0 otherwise
369 */
370static void
371input_append(const char *arg, struct inputs *inputs, int acceptdir)
372{
373 struct stat statbuf;
374 if (stat(arg, &statbuf) == -1) {
375 log_info("lldpctl", "cannot find configuration file/directory %s",
376 arg);
377 return;
fe80711e 378 }
6402fd2c
VB
379
380 if (!S_ISDIR(statbuf.st_mode)) {
381 struct input *input = malloc(sizeof(struct input));
382 if (!input) {
383 log_warn("lldpctl", "not enough memory to process %s",
384 arg);
385 return;
386 }
387 log_debug("lldpctl", "input: %s", arg);
388 input->name = strdup(arg);
389 TAILQ_INSERT_TAIL(inputs, input, next);
390 return;
391 }
392 if (!acceptdir) {
393 log_debug("lldpctl", "skip directory %s",
394 arg);
395 return;
396 }
397
398 struct dirent **namelist = NULL;
399 int n = scandir(arg, &namelist, filter, alphasort);
400 if (n < 0) {
401 log_warnx("lldpctl", "unable to read directory %s",
402 arg);
403 return;
404 }
405 for (int i=0; i < n; i++) {
406 char *fullname;
407 if (asprintf(&fullname, "%s/%s", arg, namelist[i]->d_name) != -1) {
408 input_append(fullname, inputs, 0);
409 free(fullname);
410 }
411 free(namelist[i]);
412 }
413 free(namelist);
fe80711e
VB
414}
415
4b292b55
VB
416int
417main(int argc, char *argv[])
418{
9a775667 419 int ch, debug = 1, rc = EXIT_FAILURE;
6402fd2c 420 const char *fmt = "plain";
048355f3 421 lldpctl_conn_t *conn = NULL;
f7242beb 422 const char *options = is_lldpctl(argv[0])?"hdvf:":"hdsvf:c:u:";
9a775667 423
6402fd2c
VB
424 int gotinputs = 0;
425 struct inputs inputs;
426 TAILQ_INIT(&inputs);
427
fe8f9650
VB
428 ctlname = lldpctl_get_default_transport();
429
003620d3
ST
430 signal(SIGHUP, SIG_IGN);
431
6402fd2c
VB
432 /* Initialize logging */
433 while ((ch = getopt(argc, argv, options)) != -1) {
434 switch (ch) {
435 case 'd': debug++; break;
51534ef3 436 case 's': debug--; break;
6402fd2c
VB
437 }
438 }
439 log_init(debug, __progname);
4b292b55
VB
440
441 /* Get and parse command line options */
6402fd2c
VB
442 optind = 1;
443 while ((ch = getopt(argc, argv, options)) != -1) {
4b292b55 444 switch (ch) {
6402fd2c 445 case 'd': break;
51534ef3 446 case 's': break;
4b292b55
VB
447 case 'h':
448 usage();
449 break;
0262adbb
ZM
450 case 'u':
451 ctlname = optarg;
452 break;
4b292b55
VB
453 case 'v':
454 fprintf(stdout, "%s\n", PACKAGE_VERSION);
455 exit(0);
456 break;
4b292b55
VB
457 case 'f':
458 fmt = optarg;
459 break;
6402fd2c
VB
460 case 'c':
461 gotinputs = 1;
462 input_append(optarg, &inputs, 1);
463 break;
4b292b55
VB
464 default:
465 usage();
466 }
467 }
468
9a775667
VB
469 /* Register commands */
470 root = register_commands();
471
6402fd2c
VB
472 /* Make a connection */
473 log_debug("lldpctl", "connect to lldpd");
0262adbb 474 conn = lldpctl_new_name(ctlname, NULL, NULL, NULL);
6402fd2c
VB
475 if (conn == NULL) goto end;
476
477 /* Process file inputs */
478 while (gotinputs && !TAILQ_EMPTY(&inputs)) {
87dfd175
VB
479 /* coverity[use_after_free]
480 TAILQ_REMOVE does the right thing */
6402fd2c
VB
481 struct input *first = TAILQ_FIRST(&inputs);
482 log_debug("lldpctl", "process: %s", first->name);
483 FILE *file = fopen(first->name, "r");
484 if (file) {
485 size_t len;
486 char *line;
487 while ((line = fgetln(file, &len))) {
5920dbf7 488 line = strndup(line, len);
6402fd2c
VB
489 if (line[len - 1] == '\n') {
490 line[len - 1] = '\0';
491 parse_and_exec(conn, fmt, line);
492 }
5920dbf7 493 free(line);
6402fd2c
VB
494 }
495 fclose(file);
496 } else {
497 log_warn("lldpctl", "unable to open %s",
498 first->name);
499 }
500 TAILQ_REMOVE(&inputs, first, next);
501 free(first->name);
502 free(first);
503 }
504
505 /* Process additional arguments. First if we are lldpctl (interfaces) */
506 if (is_lldpctl(NULL)) {
507 char *line = NULL;
fe80711e 508 for (int i = optind; i < argc; i++) {
6402fd2c
VB
509 char *prev = line;
510 if (asprintf(&line, "%s%s%s",
511 prev?prev:"show neigh ports ", argv[i],
512 (i == argc - 1)?" details":",") == -1) {
fe80711e 513 log_warnx("lldpctl", "not enough memory to build list of interfaces");
6402fd2c 514 free(prev);
fe80711e
VB
515 goto end;
516 }
517 free(prev);
518 }
6402fd2c
VB
519 if (line == NULL && (line = strdup("show neigh details")) == NULL) {
520 log_warnx("lldpctl", "not enough memory to build command line");
521 goto end;
522 }
523 log_debug("lldpctl", "execute %s", line);
524 if (parse_and_exec(conn, fmt, line) != -1)
525 rc = EXIT_SUCCESS;
526 free(line);
527 goto end;
4e90a9e0
VB
528 }
529
6402fd2c
VB
530 /* Then, if we are regular lldpcli (command line) */
531 if (optind < argc) {
532 const char **cargv;
533 int cargc;
534 cargv = &((const char **)argv)[optind];
535 cargc = argc - optind;
536 if (cmd_exec(conn, fmt, cargc, cargv) != -1)
537 rc = EXIT_SUCCESS;
538 goto end;
539 }
9a775667 540
cdcf7dc5
VB
541 if (gotinputs) {
542 rc = EXIT_SUCCESS;
543 goto end;
544 }
9a775667 545
6402fd2c
VB
546 /* Interactive session */
547#ifdef HAVE_LIBREADLINE
548 rl_bind_key('?', cmd_help);
549 rl_bind_key('\t', cmd_complete);
8b7150e4 550#endif
6402fd2c
VB
551 const char *line;
552 do {
553 if ((line = readline(prompt()))) {
554 int n = parse_and_exec(conn, fmt, line);
555 (void)n;
556#ifdef HAVE_READLINE_HISTORY
557 if (n != 0) add_history(line);
4e90a9e0 558#endif
fe80711e 559 }
4a22e2c1 560 } while (!must_exit && line != NULL);
9a775667 561 rc = EXIT_SUCCESS;
6402fd2c 562
9a775667 563end:
6402fd2c 564 while (!TAILQ_EMPTY(&inputs)) {
87dfd175
VB
565 /* coverity[use_after_free]
566 TAILQ_REMOVE does the right thing */
6402fd2c
VB
567 struct input *first = TAILQ_FIRST(&inputs);
568 TAILQ_REMOVE(&inputs, first, next);
569 free(first->name);
570 free(first);
571 }
9a775667 572 if (conn) lldpctl_release(conn);
9a775667
VB
573 if (root) commands_free(root);
574 return rc;
4b292b55 575}