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