1 /* -*- mode: c; c-file-style: "openbsd" -*- */
3 * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
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.
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.
25 #include <sys/types.h>
27 #include <sys/socket.h>
29 #include <arpa/inet.h>
33 #include <sys/queue.h>
37 #ifdef HAVE___PROGNAME
38 extern const char *__progname
;
40 # define __progname "lldpcli"
43 /* Global for completion */
44 static struct cmd_node
*root
= NULL
;
45 const char *ctlname
= NULL
;
48 is_lldpctl(const char *name
)
50 static int last_result
= -1;
51 if (last_result
== -1 && name
) {
52 char *basec
= strdup(name
);
54 char *bname
= basename(basec
);
55 last_result
= (!strcmp(bname
, "lldpctl"));
58 return (last_result
== -1)?0:last_result
;
64 fprintf(stderr
, "Usage: %s [OPTIONS ...] [COMMAND ...]\n", __progname
);
65 fprintf(stderr
, "Version: %s\n", PACKAGE_STRING
);
67 fprintf(stderr
, "\n");
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"
76 if (!is_lldpctl(NULL
))
77 fprintf(stderr
, "-c conf Read the provided configuration file.\n");
79 fprintf(stderr
, "\n");
81 fprintf(stderr
, "see manual page lldpcli(8) for more information\n");
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);
98 int privileged
= is_privileged();
99 if (isatty(STDIN_FILENO
)) {
101 return "[lldpcli] # ";
102 return "[lldpcli] $ ";
107 static int must_exit
= 0;
109 * Exit the interpreter.
112 cmd_exit(struct lldpctl_conn_t
*conn
, struct writer
*w
,
113 struct cmd_env
*env
, void *arg
)
115 log_info("lldpctl", "quit lldpcli");
121 * Send an "update" request.
124 cmd_update(struct lldpctl_conn_t
*conn
, struct writer
*w
,
125 struct cmd_env
*env
, void *arg
)
127 log_info("lldpctl", "ask for global update");
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
));
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
);
142 log_info("lldpctl", "immediate retransmission requested successfully");
143 lldpctl_atom_dec_ref(config
);
148 * Pause or resume execution of lldpd.
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
155 cmd_pause_resume(lldpctl_conn_t
*conn
, int pause
)
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
));
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
);
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
);
177 log_info("lldpctl", "lldpd should %s operations",
178 pause
?"pause":"resume");
179 lldpctl_atom_dec_ref(config
);
183 cmd_pause(struct lldpctl_conn_t
*conn
, struct writer
*w
,
184 struct cmd_env
*env
, void *arg
) {
186 return cmd_pause_resume(conn
, 1);
189 cmd_resume(struct lldpctl_conn_t
*conn
, struct writer
*w
,
190 struct cmd_env
*env
, void *arg
) {
192 return cmd_pause_resume(conn
, 0);
196 #ifdef HAVE_LIBREADLINE
198 _cmd_complete(int all
)
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;
210 if (tokenize_line(line
, &argc
, &argv
) != 0)
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) {
223 /* No completion or several completion available. */
225 fprintf(stderr
, "\n");
226 rl_forced_update_display();
230 tokenize_free(argc
, argv
);
235 cmd_complete(int count
, int ch
)
237 return _cmd_complete(0);
241 cmd_help(int count
, int ch
)
243 return _cmd_complete(1);
247 readline(const char *p
)
249 static char line
[2048];
250 fprintf(stderr
, "%s", p
);
252 if (fgets(line
, sizeof(line
) - 2, stdin
) == NULL
)
259 * Execute a tokenized command and display its output.
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
268 cmd_exec(lldpctl_conn_t
*conn
, const char *fmt
, int argc
, const char **argv
)
270 /* Init output formatter */
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);
278 else if (strcmp(fmt
, "xml") == 0) w
= xml_init(stdout
);
281 log_warnx("lldpctl", "unknown output format \"%s\"", fmt
);
282 w
= txt_init(stdout
);
285 /* Execute command */
286 int rc
= commands_execute(conn
, w
,
287 root
, argc
, argv
, is_privileged());
289 log_info("lldpctl", "an error occurred while executing last command");
298 * Execute a command line and display its output.
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.
306 parse_and_exec(lldpctl_conn_t
*conn
, const char *fmt
, const char *line
)
308 int cargc
= 0; char **cargv
= NULL
;
310 log_debug("lldpctl", "tokenize command line");
311 n
= tokenize_line(line
, &cargc
, &cargv
);
314 log_warnx("lldpctl", "internal error while tokenizing");
317 log_warnx("lldpctl", "unmatched quotes");
321 n
= cmd_exec(conn
, fmt
, cargc
, (const char **)cargv
);
322 tokenize_free(cargc
, cargv
);
323 return (cargc
== 0)?0:
328 static struct cmd_node
*
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",
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");
345 commands_new(root
, "pause", "Pause lldpd operations", NULL
, NULL
, NULL
),
346 NEWLINE
, "Pause lldpd operations", NULL
, cmd_pause
, NULL
);
348 commands_new(root
, "resume", "Resume lldpd operations", NULL
, NULL
, NULL
),
349 NEWLINE
, "Resume lldpd operations", NULL
, cmd_resume
, NULL
);
351 commands_new(root
, "exit", "Exit interpreter", NULL
, NULL
, NULL
),
352 NEWLINE
, "Exit interpreter", NULL
, cmd_exit
, NULL
);
357 TAILQ_ENTRY(input
) next
;
360 TAILQ_HEAD(inputs
, input
);
362 filter(const struct dirent
*dir
)
364 if (strlen(dir
->d_name
) < 5) return 0;
365 if (strcmp(dir
->d_name
+ strlen(dir
->d_name
) - 5, ".conf")) return 0;
370 * Append a new input file/directory to the list of inputs.
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
377 input_append(const char *arg
, struct inputs
*inputs
, int acceptdir
, int warn
)
380 if (stat(arg
, &statbuf
) == -1) {
382 log_warn("lldpctl", "cannot find configuration file/directory %s",
385 log_debug("lldpctl", "cannot find configuration file/directory %s",
391 if (!S_ISDIR(statbuf
.st_mode
)) {
392 struct input
*input
= malloc(sizeof(struct input
));
394 log_warn("lldpctl", "not enough memory to process %s",
398 log_debug("lldpctl", "input: %s", arg
);
399 input
->name
= strdup(arg
);
400 TAILQ_INSERT_TAIL(inputs
, input
, next
);
404 log_debug("lldpctl", "skip directory %s",
409 struct dirent
**namelist
= NULL
;
410 int n
= scandir(arg
, &namelist
, filter
, alphasort
);
412 log_warnx("lldpctl", "unable to read directory %s",
416 for (int i
=0; i
< n
; i
++) {
418 if (asprintf(&fullname
, "%s/%s", arg
, namelist
[i
]->d_name
) != -1) {
419 input_append(fullname
, inputs
, 0, 1);
428 main(int argc
, char *argv
[])
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:";
435 int gotinputs
= 0, version
= 0;
436 struct inputs inputs
;
439 ctlname
= lldpctl_get_default_transport();
441 signal(SIGHUP
, SIG_IGN
);
443 /* Get and parse command line options */
445 while ((ch
= getopt(argc
, argv
, options
)) != -1) {
474 log_init(use_syslog
, debug
, __progname
);
475 lldpctl_log_level(debug
+ 1);
478 input_append(optarg
, &inputs
, 1, ch
== 'c');
486 version_display(stdout
, "lldpcli", version
> 1);
491 log_init(use_syslog
, debug
, __progname
);
492 lldpctl_log_level(debug
+ 1);
495 /* Disable SIGPIPE */
496 signal(SIGPIPE
, SIG_IGN
);
498 /* Register commands */
499 root
= register_commands();
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
;
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");
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
);
527 log_warn("lldpctl", "unable to open %s",
530 TAILQ_REMOVE(&inputs
, first
, next
);
535 /* Process additional arguments. First if we are lldpctl (interfaces) */
536 if (is_lldpctl(NULL
)) {
538 for (int i
= optind
; i
< argc
; i
++) {
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");
549 if (line
== NULL
&& (line
= strdup("show neigh details")) == NULL
) {
550 log_warnx("lldpctl", "not enough memory to build command line");
553 log_debug("lldpctl", "execute %s", line
);
554 if (parse_and_exec(conn
, fmt
, line
) != -1)
560 /* Then, if we are regular lldpcli (command line) */
564 cargv
= &((const char **)argv
)[optind
];
565 cargc
= argc
- optind
;
566 if (cmd_exec(conn
, fmt
, cargc
, cargv
) == 1)
576 /* Interactive session */
577 #ifdef HAVE_LIBREADLINE
578 rl_bind_key('?', cmd_help
);
579 rl_bind_key('\t', cmd_complete
);
583 if ((line
= readline(prompt()))) {
584 int n
= parse_and_exec(conn
, fmt
, line
);
586 #ifdef HAVE_READLINE_HISTORY
592 } while (!must_exit
&& line
!= NULL
);
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
);
604 if (conn
) lldpctl_release(conn
);
605 if (root
) commands_free(root
);