]>
Commit | Line | Data |
---|---|---|
9a775667 VB |
1 | /* -*- mode: c; c-file-style: "openbsd" -*- */ |
2 | /* | |
3 | * Copyright (c) 2012 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 | #include "client.h" | |
19 | #include <string.h> | |
20 | #include <sys/queue.h> | |
0a46b330 | 21 | #include <sys/types.h> |
c38c53d0 VB |
22 | #include <sys/stat.h> |
23 | #include <fcntl.h> | |
0a46b330 VB |
24 | #include <sys/socket.h> |
25 | #include <sys/un.h> | |
c38c53d0 | 26 | #include <libgen.h> |
9a775667 VB |
27 | |
28 | /** | |
29 | * An element of the environment (a key and a value). | |
30 | */ | |
31 | struct cmd_env_el { | |
32 | TAILQ_ENTRY(cmd_env_el) next; /**< Next environment element */ | |
33 | const char *key; /**< Key for this element */ | |
34 | const char *value; /**< Value for this element */ | |
35 | }; | |
36 | ||
37 | /** | |
38 | * A stack element. | |
39 | */ | |
40 | struct cmd_env_stack { | |
41 | TAILQ_ENTRY(cmd_env_stack) next; /**< Next element, down the stack */ | |
42 | struct cmd_node *el; /**< Stored element */ | |
43 | }; | |
44 | ||
45 | /** | |
46 | * Structure representing an environment for the current command. | |
47 | * | |
48 | * An environment is a list of values stored for use for the function executing | |
49 | * as well as the current command, the current position in the command and a | |
50 | * stack for cmd_node | |
51 | */ | |
52 | struct cmd_env { | |
53 | TAILQ_HEAD(, cmd_env_el) elements; /**< List of environment variables */ | |
54 | TAILQ_HEAD(, cmd_env_stack) stack; /**< Stack */ | |
8b549648 VB |
55 | int argc; /**< Number of argument in the command */ |
56 | int argp; /**< Current argument */ | |
57 | const char **argv; /**< Arguments */ | |
9a775667 VB |
58 | }; |
59 | ||
60 | /** | |
61 | * Structure representing a command node. | |
62 | * | |
63 | * Such a node contains a token accepted to enter the node (or @c NULL if there | |
64 | * is no token needed), a documentation string to present the user, a function | |
65 | * to validate the user input (or @c NULL if no function is needed) and a | |
66 | * function to execute when entering the node. Because we can enter a node just | |
67 | * by completing, the execution part should have no other effect than modifying | |
68 | * the environment, with the exception of execution on @c NEWLINE (which cannot | |
69 | * happen when completing). | |
70 | */ | |
71 | struct cmd_node { | |
72 | TAILQ_ENTRY(cmd_node) next; /**< Next sibling */ | |
73 | ||
8b549648 VB |
74 | const char *token; /**< Token to enter this cnode */ |
75 | const char *doc; /**< Documentation string */ | |
76 | int privileged; /**< Privileged command? */ | |
77 | int lock; /**< Lock required for execution? */ | |
78 | int hidden; /**< Hidden command? */ | |
9a775667 VB |
79 | |
80 | /** | |
81 | * Function validating entry in this node. Can be @c NULL. | |
82 | */ | |
f540397c | 83 | int (*validate)(struct cmd_env *, const void *); |
9a775667 VB |
84 | /** |
85 | * Function to execute when entering this node. May be @c NULL. | |
86 | * | |
87 | * This function can alter the environment | |
88 | */ | |
8b549648 | 89 | int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *, |
f540397c GS |
90 | const void *); |
91 | const void *arg; /**< Magic argument for the previous two functions */ | |
9a775667 VB |
92 | |
93 | /* List of possible subentries */ | |
94 | TAILQ_HEAD(, cmd_node) subentries; /* List of subnodes */ | |
95 | }; | |
96 | ||
97 | /** | |
98 | * Create a root node. | |
99 | * | |
100 | * @return the root node. | |
101 | */ | |
8b549648 | 102 | struct cmd_node * |
9a775667 VB |
103 | commands_root(void) |
104 | { | |
61979752 VB |
105 | struct cmd_node *new = calloc(1, sizeof(struct cmd_node)); |
106 | if (new == NULL) fatalx("lldpctl", "out of memory"); | |
107 | TAILQ_INIT(&new->subentries); | |
108 | return new; | |
9a775667 VB |
109 | } |
110 | ||
111 | /** | |
e13945c0 VB |
112 | * Make a node accessible only to privileged users. |
113 | * | |
314f382a | 114 | * @param node node to change privileges |
e13945c0 VB |
115 | * @return the modified node |
116 | * | |
117 | * The node is modified. It is returned to ease chaining. | |
118 | */ | |
8b549648 | 119 | struct cmd_node * |
e13945c0 VB |
120 | commands_privileged(struct cmd_node *node) |
121 | { | |
122 | if (node) node->privileged = 1; | |
123 | return node; | |
124 | } | |
125 | ||
0a46b330 VB |
126 | /** |
127 | * Make a node accessible only with a lock. | |
128 | * | |
129 | * @param node node to use lock to execute | |
130 | * @return the modified node | |
131 | * | |
132 | * The node is modified. It is returned to ease chaining. | |
133 | */ | |
8b549648 | 134 | struct cmd_node * |
0a46b330 VB |
135 | commands_lock(struct cmd_node *node) |
136 | { | |
137 | if (node) node->lock = 1; | |
138 | return node; | |
139 | } | |
140 | ||
314f382a VB |
141 | /** |
142 | * Hide a node from help or completion. | |
143 | * | |
144 | * @param node node to hide | |
145 | * @return the modified node | |
146 | * | |
147 | * The node is modified. It is returned to ease chaining. | |
148 | */ | |
8b549648 | 149 | struct cmd_node * |
314f382a VB |
150 | commands_hidden(struct cmd_node *node) |
151 | { | |
152 | if (node) node->hidden = 1; | |
153 | return node; | |
154 | } | |
155 | ||
e13945c0 VB |
156 | /** |
157 | * Create a new node acessible by any user. | |
9a775667 VB |
158 | * |
159 | * @param root The node we want to attach this node. | |
160 | * @param token Token to enter this node. Or @c NULL if no token is needed. | |
161 | * @param doc Documentation for this node. | |
162 | * @param validate Function that should return 1 if we can enter the node. | |
163 | * @param execute Function that should return 1 on successful execution of this node. | |
164 | * @param arg Magic argument for precedent functions. | |
165 | * @return the newly created node | |
166 | */ | |
8b549648 VB |
167 | struct cmd_node * |
168 | commands_new(struct cmd_node *root, const char *token, const char *doc, | |
f540397c GS |
169 | int (*validate)(struct cmd_env *, const void *), |
170 | int (*execute)(struct lldpctl_conn_t *, struct writer *, struct cmd_env *, | |
171 | const void *), | |
172 | const void *arg) | |
9a775667 VB |
173 | { |
174 | struct cmd_node *new = calloc(1, sizeof(struct cmd_node)); | |
8b549648 | 175 | if (new == NULL) fatalx("lldpctl", "out of memory"); |
9a775667 VB |
176 | new->token = token; |
177 | new->doc = doc; | |
178 | new->validate = validate; | |
179 | new->execute = execute; | |
180 | new->arg = arg; | |
181 | TAILQ_INIT(&new->subentries); | |
61979752 | 182 | TAILQ_INSERT_TAIL(&root->subentries, new, next); |
9a775667 VB |
183 | return new; |
184 | } | |
185 | ||
186 | /** | |
187 | * Free a command tree. | |
188 | * | |
189 | * @param root The node we want to free. | |
190 | */ | |
191 | void | |
192 | commands_free(struct cmd_node *root) | |
193 | { | |
194 | struct cmd_node *subcmd, *subcmd_next; | |
195 | for (subcmd = TAILQ_FIRST(&root->subentries); subcmd != NULL; | |
196 | subcmd = subcmd_next) { | |
197 | subcmd_next = TAILQ_NEXT(subcmd, next); | |
198 | TAILQ_REMOVE(&root->subentries, subcmd, next); | |
199 | commands_free(subcmd); | |
200 | } | |
201 | free(root); | |
202 | } | |
203 | ||
204 | /** | |
205 | * Return the current argument in the environment. This can be @c NEWLINE or | |
206 | * @c NULL. | |
207 | * | |
208 | * @param env The environment. | |
209 | * @return current argument. | |
210 | */ | |
8b549648 | 211 | const char * |
9a775667 VB |
212 | cmdenv_arg(struct cmd_env *env) |
213 | { | |
8b549648 VB |
214 | if (env->argp < env->argc) return env->argv[env->argp]; |
215 | if (env->argp == env->argc) return NEWLINE; | |
9a775667 VB |
216 | return NULL; |
217 | } | |
218 | ||
219 | /** | |
220 | * Get a value from the environment. | |
221 | * | |
222 | * @param env The environment. | |
223 | * @param key The key for the requested value. | |
224 | * @return @c NULL if not found or the requested value otherwise. If no value is | |
225 | * associated, return the key. | |
226 | */ | |
8b549648 | 227 | const char * |
9a775667 VB |
228 | cmdenv_get(struct cmd_env *env, const char *key) |
229 | { | |
230 | struct cmd_env_el *el; | |
8b549648 VB |
231 | TAILQ_FOREACH (el, &env->elements, next) |
232 | if (!strcmp(el->key, key)) return el->value ? el->value : el->key; | |
9a775667 VB |
233 | return NULL; |
234 | } | |
235 | ||
236 | /** | |
237 | * Put a value in the environment. | |
238 | * | |
239 | * @param env The environment. | |
240 | * @param key The key for the value. | |
241 | * @param value The value. | |
242 | * @return 0 on success, -1 otherwise. | |
243 | */ | |
244 | int | |
8b549648 | 245 | cmdenv_put(struct cmd_env *env, const char *key, const char *value) |
9a775667 VB |
246 | { |
247 | struct cmd_env_el *el = malloc(sizeof(struct cmd_env_el)); | |
248 | if (el == NULL) { | |
8b549648 VB |
249 | log_warn("lldpctl", |
250 | "unable to allocate memory for new environment variable"); | |
9a775667 VB |
251 | return -1; |
252 | } | |
253 | el->key = key; | |
254 | el->value = value; | |
255 | TAILQ_INSERT_TAIL(&env->elements, el, next); | |
256 | return 0; | |
257 | } | |
258 | ||
259 | /** | |
260 | * Pop some node from the execution stack. | |
261 | * | |
262 | * This allows to resume parsing on a previous state. Useful to call after | |
263 | * parsing optional arguments. | |
264 | * | |
265 | * @param env The environment. | |
266 | * @param n How many element we want to pop. | |
267 | * @return 0 on success, -1 otherwise. | |
268 | */ | |
269 | int | |
270 | cmdenv_pop(struct cmd_env *env, int n) | |
271 | { | |
272 | while (n-- > 0) { | |
273 | if (TAILQ_EMPTY(&env->stack)) { | |
274 | log_warnx("lldpctl", "environment stack is empty"); | |
275 | return -1; | |
276 | } | |
277 | struct cmd_env_stack *first = TAILQ_FIRST(&env->stack); | |
8b549648 | 278 | TAILQ_REMOVE(&env->stack, first, next); |
9a775667 VB |
279 | free(first); |
280 | } | |
281 | return 0; | |
282 | } | |
283 | ||
284 | /** | |
285 | * Push some node on the execution stack. | |
286 | * | |
287 | * @param env The environment. | |
288 | * @param node The node to push. | |
289 | * @return 0 on success, -1 on error. | |
290 | */ | |
291 | static int | |
292 | cmdenv_push(struct cmd_env *env, struct cmd_node *node) | |
293 | { | |
294 | struct cmd_env_stack *el = malloc(sizeof(struct cmd_env_stack)); | |
295 | if (el == NULL) { | |
296 | log_warn("lldpctl", "not enough memory to allocate a stack element"); | |
297 | return -1; | |
298 | } | |
299 | el->el = node; | |
300 | TAILQ_INSERT_HEAD(&env->stack, el, next); | |
301 | return 0; | |
302 | } | |
303 | ||
304 | /** | |
305 | * Return the top of the stack, without poping it. | |
306 | * | |
307 | * @param env The environment. | |
308 | * @return the top element or @c NULL is the stack is empty. | |
309 | */ | |
8b549648 | 310 | static struct cmd_node * |
9a775667 VB |
311 | cmdenv_top(struct cmd_env *env) |
312 | { | |
313 | if (TAILQ_EMPTY(&env->stack)) return NULL; | |
314 | return TAILQ_FIRST(&env->stack)->el; | |
315 | } | |
316 | ||
317 | /** | |
318 | * Free execution environment. | |
319 | * | |
320 | * @param env The environment. | |
321 | */ | |
322 | static void | |
323 | cmdenv_free(struct cmd_env *env) | |
324 | { | |
8b549648 VB |
325 | while (!TAILQ_EMPTY(&env->stack)) |
326 | cmdenv_pop(env, 1); | |
9a775667 VB |
327 | |
328 | struct cmd_env_el *first; | |
329 | while (!TAILQ_EMPTY(&env->elements)) { | |
330 | first = TAILQ_FIRST(&env->elements); | |
331 | TAILQ_REMOVE(&env->elements, first, next); | |
332 | free(first); | |
333 | } | |
334 | } | |
335 | ||
336 | struct candidate_word { | |
337 | TAILQ_ENTRY(candidate_word) next; | |
338 | const char *word; | |
339 | const char *doc; | |
314f382a | 340 | int hidden; |
9a775667 VB |
341 | }; |
342 | ||
343 | /** | |
344 | * Execute or complete a command from the given node. | |
345 | * | |
346 | * @param conn Connection to lldpd. | |
347 | * @param w Writer for output. | |
348 | * @param root Root node we want to start from. | |
349 | * @param argc Number of arguments. | |
350 | * @param argv Array of arguments. | |
9a775667 | 351 | * @param word Completed word. Or NULL when no completion is required. |
8b549648 VB |
352 | * @param all When completing, display possible completions even if only one choice |
353 | * is possible. | |
e13945c0 | 354 | * @param priv Is the current user privileged? |
9a775667 VB |
355 | * @return 0 on success, -1 otherwise. |
356 | */ | |
357 | static int | |
8b549648 VB |
358 | _commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root, |
359 | int argc, const char **argv, char **word, int all, int priv) | |
9a775667 | 360 | { |
23e7fa38 | 361 | int n, rc = 0, completion = (word != NULL); |
8b549648 VB |
362 | int help = 0; /* Are we asking for help? */ |
363 | int complete = 0; /* Are we asking for possible completions? */ | |
364 | int needlock = 0; /* Do we need a lock? */ | |
365 | struct cmd_env env = { .elements = TAILQ_HEAD_INITIALIZER(env.elements), | |
9a775667 | 366 | .stack = TAILQ_HEAD_INITIALIZER(env.stack), |
23e7fa38 | 367 | .argc = argc, |
9a775667 | 368 | .argv = argv, |
8b549648 | 369 | .argp = 0 }; |
fc8164d8 | 370 | static int lockfd = -1; |
8b549648 | 371 | static char *lockname = NULL; /* Name of lockfile */ |
9a775667 VB |
372 | cmdenv_push(&env, root); |
373 | if (!completion) | |
374 | for (n = 0; n < argc; n++) | |
375 | log_debug("lldpctl", "argument %02d: `%s`", n, argv[n]); | |
376 | if (completion) *word = NULL; | |
377 | ||
8b549648 VB |
378 | #define CAN_EXECUTE(candidate) \ |
379 | ((!candidate->privileged || priv || complete) && \ | |
380 | (!candidate->validate || candidate->validate(&env, candidate->arg) == 1)) | |
e13945c0 | 381 | |
9a775667 VB |
382 | /* When completion is in progress, we use the same algorithm than for |
383 | * execution until we reach the cursor position. */ | |
384 | struct cmd_node *current = NULL; | |
385 | while ((current = cmdenv_top(&env))) { | |
314f382a | 386 | if (!completion) { |
25272118 | 387 | help = !!cmdenv_get(&env, "help"); /* Are we asking for help? */ |
314f382a VB |
388 | complete = !!cmdenv_get(&env, "complete"); /* Or completion? */ |
389 | } | |
25272118 | 390 | |
9a775667 | 391 | struct cmd_node *candidate, *best = NULL; |
8b549648 VB |
392 | const char *token = (env.argp < env.argc) ? env.argv[env.argp] : |
393 | (env.argp == env.argc && !help && !complete) ? NEWLINE : | |
394 | NULL; | |
395 | if (token == NULL || (completion && env.argp == env.argc - 1)) goto end; | |
9a775667 | 396 | if (!completion) |
8b549648 VB |
397 | log_debug("lldpctl", "process argument %02d: `%s`", env.argp, |
398 | token); | |
399 | TAILQ_FOREACH (candidate, ¤t->subentries, next) { | |
9a775667 | 400 | if (candidate->token && |
e13945c0 VB |
401 | !strncmp(candidate->token, token, strlen(token)) && |
402 | CAN_EXECUTE(candidate)) { | |
403 | if (candidate->token && | |
404 | !strcmp(candidate->token, token)) { | |
405 | /* Exact match */ | |
406 | best = candidate; | |
0a46b330 | 407 | needlock = needlock || candidate->lock; |
e13945c0 VB |
408 | break; |
409 | } | |
8b549648 VB |
410 | if (!best) |
411 | best = candidate; | |
e13945c0 VB |
412 | else { |
413 | if (!completion) | |
8b549648 VB |
414 | log_warnx("lldpctl", |
415 | "ambiguous token: %s (%s or %s)", | |
416 | token, candidate->token, | |
417 | best->token); | |
e13945c0 VB |
418 | rc = -1; |
419 | goto end; | |
9a775667 VB |
420 | } |
421 | } | |
422 | } | |
423 | if (!best) { | |
424 | /* Take first that validate */ | |
8b549648 VB |
425 | TAILQ_FOREACH (candidate, ¤t->subentries, next) { |
426 | if (!candidate->token && CAN_EXECUTE(candidate)) { | |
9a775667 | 427 | best = candidate; |
0a46b330 | 428 | needlock = needlock || candidate->lock; |
9a775667 VB |
429 | break; |
430 | } | |
431 | } | |
432 | } | |
433 | if (!best && env.argp == env.argc) goto end; | |
434 | if (!best) { | |
435 | if (!completion) | |
8b549648 VB |
436 | log_warnx("lldpctl", |
437 | "unknown command from argument %d: `%s`", | |
9a775667 VB |
438 | env.argp + 1, token); |
439 | rc = -1; | |
440 | goto end; | |
441 | } | |
442 | ||
443 | /* Push and execute */ | |
444 | cmdenv_push(&env, best); | |
0a46b330 | 445 | if (best->execute) { |
0a46b330 | 446 | if (needlock) { |
fc8164d8 | 447 | if (lockfd == -1) { |
c38c53d0 | 448 | if (lockname == NULL && |
8b549648 VB |
449 | asprintf(&lockname, "%s.lock", ctlname) == |
450 | -1) { | |
c38c53d0 VB |
451 | log_warnx("lldpctl", |
452 | "not enough memory to build lock filename"); | |
fc8164d8 VB |
453 | rc = -1; |
454 | goto end; | |
455 | } | |
8b549648 VB |
456 | log_debug("lldpctl", "open %s for locking", |
457 | lockname); | |
a1c9d4be | 458 | if ((lockfd = open(lockname, O_RDWR)) == -1) { |
8b549648 VB |
459 | log_warn("lldpctl", |
460 | "cannot open lock %s", lockname); | |
fc8164d8 | 461 | rc = -1; |
fc8164d8 VB |
462 | goto end; |
463 | } | |
0a46b330 VB |
464 | } |
465 | if (lockf(lockfd, F_LOCK, 0) == -1) { | |
8b549648 VB |
466 | log_warn("lldpctl", "cannot get lock on %s", |
467 | lockname); | |
0a46b330 | 468 | rc = -1; |
8b549648 VB |
469 | close(lockfd); |
470 | lockfd = -1; | |
0a46b330 VB |
471 | goto end; |
472 | } | |
473 | } | |
fc8164d8 VB |
474 | rc = best->execute(conn, w, &env, best->arg) != 1 ? -1 : rc; |
475 | if (needlock && lockf(lockfd, F_ULOCK, 0) == -1) { | |
c38c53d0 | 476 | log_warn("lldpctl", "cannot unlock %s", lockname); |
8b549648 VB |
477 | close(lockfd); |
478 | lockfd = -1; | |
0a46b330 | 479 | } |
fc8164d8 | 480 | if (rc == -1) goto end; |
9a775667 VB |
481 | } |
482 | env.argp++; | |
483 | } | |
484 | end: | |
314f382a | 485 | if (!completion && !help && !complete) { |
9a775667 VB |
486 | if (rc == 0 && env.argp != env.argc + 1) { |
487 | log_warnx("lldpctl", "incomplete command"); | |
488 | rc = -1; | |
489 | } | |
314f382a | 490 | } else if (rc == 0 && (env.argp == env.argc - 1 || help || complete)) { |
9a775667 VB |
491 | /* We need to complete. Let's build the list of candidate words. */ |
492 | struct cmd_node *candidate = NULL; | |
314f382a | 493 | size_t maxl = 10; /* Max length of a word */ |
9a775667 VB |
494 | TAILQ_HEAD(, candidate_word) words; /* List of subnodes */ |
495 | TAILQ_INIT(&words); | |
496 | current = cmdenv_top(&env); | |
aa015c26 | 497 | if (!TAILQ_EMPTY(¤t->subentries)) { |
8b549648 | 498 | TAILQ_FOREACH (candidate, ¤t->subentries, next) { |
314f382a | 499 | if ((!candidate->token || help || complete || |
8b549648 VB |
500 | !strncmp(env.argv[env.argc - 1], |
501 | candidate->token, | |
502 | strlen(env.argv[env.argc - 1]))) && | |
e13945c0 | 503 | CAN_EXECUTE(candidate)) { |
aa015c26 VB |
504 | struct candidate_word *cword = |
505 | malloc(sizeof(struct candidate_word)); | |
506 | if (!cword) break; | |
507 | cword->word = candidate->token; | |
508 | cword->doc = candidate->doc; | |
314f382a | 509 | cword->hidden = candidate->hidden; |
aa015c26 VB |
510 | if (cword->word && strlen(cword->word) > maxl) |
511 | maxl = strlen(cword->word); | |
512 | TAILQ_INSERT_TAIL(&words, cword, next); | |
513 | } | |
9a775667 VB |
514 | } |
515 | } | |
516 | if (!TAILQ_EMPTY(&words)) { | |
517 | /* Search if there is a common prefix, then return it. */ | |
9a775667 VB |
518 | char prefix[maxl + 2]; /* Extra space may be added at the end */ |
519 | struct candidate_word *cword, *cword_next; | |
8b549648 | 520 | memset(prefix, 0, maxl + 2); |
314f382a VB |
521 | for (size_t n = 0; n < maxl; n++) { |
522 | int c = 1; /* Set to 0 to exit outer loop */ | |
8b549648 | 523 | TAILQ_FOREACH (cword, &words, next) { |
9a775667 | 524 | c = 0; |
314f382a | 525 | if (cword->hidden) continue; |
9a775667 VB |
526 | if (cword->word == NULL) break; |
527 | if (!strcmp(cword->word, NEWLINE)) break; | |
528 | if (strlen(cword->word) == n) break; | |
8b549648 VB |
529 | if (prefix[n] == '\0') |
530 | prefix[n] = cword->word[n]; | |
531 | else if (prefix[n] != cword->word[n]) | |
532 | break; | |
9a775667 VB |
533 | c = 1; |
534 | } | |
535 | if (c == 0) { | |
536 | prefix[n] = '\0'; | |
537 | break; | |
538 | } | |
539 | } | |
540 | /* If the prefix is complete, add a space, otherwise, | |
541 | * just return it as is. */ | |
314f382a | 542 | if (!all && !help && !complete && strcmp(prefix, NEWLINE) && |
23e7fa38 | 543 | strlen(prefix) > 0 && |
8b549648 VB |
544 | strlen(env.argv[env.argc - 1]) < strlen(prefix)) { |
545 | TAILQ_FOREACH (cword, &words, next) { | |
546 | if (cword->word && | |
547 | !strcmp(prefix, cword->word)) { | |
9a775667 VB |
548 | prefix[strlen(prefix)] = ' '; |
549 | break; | |
550 | } | |
551 | } | |
552 | *word = strdup(prefix); | |
553 | } else { | |
554 | /* No common prefix, print possible completions */ | |
314f382a VB |
555 | if (!complete) |
556 | fprintf(stderr, "\n-- \033[1;34m%s\033[0m\n", | |
557 | current->doc ? current->doc : "Help"); | |
8b549648 | 558 | TAILQ_FOREACH (cword, &words, next) { |
314f382a VB |
559 | if (cword->hidden) continue; |
560 | ||
9a775667 | 561 | char fmt[100]; |
314f382a VB |
562 | if (!complete) { |
563 | snprintf(fmt, sizeof(fmt), | |
8b549648 VB |
564 | "%s%%%ds%s %%s\n", "\033[1m", |
565 | (int)maxl, "\033[0m"); | |
314f382a VB |
566 | fprintf(stderr, fmt, |
567 | cword->word ? cword->word : "WORD", | |
8b549648 | 568 | cword->doc ? cword->doc : "..."); |
314f382a | 569 | } else { |
8b549648 VB |
570 | if (!cword->word || |
571 | !strcmp(cword->word, NEWLINE)) | |
314f382a VB |
572 | continue; |
573 | fprintf(stdout, "%s %s\n", | |
574 | cword->word ? cword->word : "WORD", | |
8b549648 | 575 | cword->doc ? cword->doc : "..."); |
314f382a | 576 | } |
9a775667 VB |
577 | } |
578 | } | |
579 | for (cword = TAILQ_FIRST(&words); cword != NULL; | |
580 | cword = cword_next) { | |
581 | cword_next = TAILQ_NEXT(cword, next); | |
582 | TAILQ_REMOVE(&words, cword, next); | |
583 | free(cword); | |
584 | } | |
585 | } | |
586 | } | |
587 | cmdenv_free(&env); | |
588 | return rc; | |
589 | } | |
590 | ||
591 | /** | |
592 | * Complete the given command. | |
593 | */ | |
594 | char * | |
8b549648 VB |
595 | commands_complete(struct cmd_node *root, int argc, const char **argv, int all, |
596 | int privileged) | |
9a775667 VB |
597 | { |
598 | char *word = NULL; | |
8b549648 VB |
599 | if (_commands_execute(NULL, NULL, root, argc, argv, &word, all, privileged) == |
600 | 0) | |
9a775667 VB |
601 | return word; |
602 | return NULL; | |
603 | } | |
604 | ||
605 | /** | |
606 | * Execute the given commands. | |
607 | */ | |
608 | int | |
8b549648 VB |
609 | commands_execute(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_node *root, |
610 | int argc, const char **argv, int privileged) | |
9a775667 | 611 | { |
e13945c0 | 612 | return _commands_execute(conn, w, root, argc, argv, NULL, 0, privileged); |
9a775667 VB |
613 | } |
614 | ||
9a775667 VB |
615 | /** |
616 | * Check if the environment does not contain the given key. | |
617 | * | |
618 | * @param env The environment. | |
9cac8fed VB |
619 | * @param key The key to search for. |
620 | * @return 1 if the environment does not contain the key. 0 otherwise. | |
9a775667 VB |
621 | */ |
622 | int | |
f540397c | 623 | cmd_check_no_env(struct cmd_env *env, const void *key) |
9a775667 | 624 | { |
8b549648 | 625 | return cmdenv_get(env, (const char *)key) == NULL; |
9a775667 VB |
626 | } |
627 | ||
628 | /** | |
629 | * Check if the environment does contain the given key. | |
630 | * | |
631 | * @param env The environment. | |
9cac8fed VB |
632 | * @param key The key to search for. Can be a comma separated list. |
633 | * @return 1 if the environment does contain the key. 0 otherwise. | |
9a775667 VB |
634 | */ |
635 | int | |
f540397c | 636 | cmd_check_env(struct cmd_env *env, const void *key) |
9a775667 VB |
637 | { |
638 | struct cmd_env_el *el; | |
639 | const char *list = key; | |
640 | int count = 0; | |
8b549648 VB |
641 | TAILQ_FOREACH (el, &env->elements, next) { |
642 | if (contains(list, el->key)) count++; | |
643 | } | |
644 | while ((list = strchr(list, ','))) { | |
645 | list++; | |
646 | count--; | |
9a775667 | 647 | } |
9a775667 VB |
648 | return (count == 1); |
649 | } | |
650 | ||
651 | /** | |
652 | * Store the given key in the environment. | |
653 | * | |
9cac8fed VB |
654 | * @param conn The connection. |
655 | * @param w The writer (not used). | |
9a775667 VB |
656 | * @param env The environment. |
657 | * @param key The key to store. | |
9cac8fed | 658 | * @return 1 if the key was stored |
9a775667 VB |
659 | */ |
660 | int | |
8b549648 | 661 | cmd_store_env(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, |
f540397c | 662 | const void *key) |
9a775667 VB |
663 | { |
664 | return cmdenv_put(env, key, NULL) != -1; | |
665 | } | |
666 | ||
667 | /** | |
668 | * Store the given key in the environment and pop one element from the stack. | |
669 | * | |
9cac8fed VB |
670 | * @param conn The connection. |
671 | * @param w The writer (not used). | |
9a775667 VB |
672 | * @param env The environment. |
673 | * @param key The key to store. | |
9cac8fed | 674 | * @return 1 if the key was stored |
9a775667 VB |
675 | */ |
676 | int | |
677 | cmd_store_env_and_pop(struct lldpctl_conn_t *conn, struct writer *w, | |
f540397c | 678 | struct cmd_env *env, const void *key) |
9a775667 | 679 | { |
8b549648 | 680 | return (cmd_store_env(conn, w, env, key) != -1 && cmdenv_pop(env, 1) != -1); |
9a775667 VB |
681 | } |
682 | ||
683 | /** | |
684 | * Store the given key with a value being the current keyword in the environment | |
685 | * and pop X elements from the stack. | |
686 | * | |
9cac8fed VB |
687 | * @param conn The connection. |
688 | * @param w The writer (not used). | |
9a775667 VB |
689 | * @param env The environment. |
690 | * @param key The key to store. | |
9cac8fed | 691 | * @return 1 if the key was stored |
9a775667 VB |
692 | */ |
693 | int | |
694 | cmd_store_env_value_and_pop(struct lldpctl_conn_t *conn, struct writer *w, | |
f540397c | 695 | struct cmd_env *env, const void *key) |
9a775667 | 696 | { |
8b549648 VB |
697 | return ( |
698 | cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 1) != -1); | |
9a775667 VB |
699 | } |
700 | int | |
701 | cmd_store_env_value_and_pop2(struct lldpctl_conn_t *conn, struct writer *w, | |
f540397c | 702 | struct cmd_env *env, const void *key) |
9a775667 | 703 | { |
8b549648 VB |
704 | return ( |
705 | cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 2) != -1); | |
9a775667 VB |
706 | } |
707 | int | |
8b549648 | 708 | cmd_store_env_value(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env, |
f540397c | 709 | const void *key) |
9a775667 VB |
710 | { |
711 | return (cmdenv_put(env, key, cmdenv_arg(env)) != -1); | |
712 | } | |
713 | int | |
714 | cmd_store_env_value_and_pop3(struct lldpctl_conn_t *conn, struct writer *w, | |
f540397c | 715 | struct cmd_env *env, const void *key) |
9a775667 | 716 | { |
8b549648 VB |
717 | return ( |
718 | cmdenv_put(env, key, cmdenv_arg(env)) != -1 && cmdenv_pop(env, 3) != -1); | |
9a775667 | 719 | } |
e15f96d2 | 720 | int |
8b549648 | 721 | cmd_store_something_env_value_and_pop2(const char *what, struct cmd_env *env, |
f540397c | 722 | const void *value) |
e15f96d2 | 723 | { |
8b549648 | 724 | return (cmdenv_put(env, what, value) != -1 && cmdenv_pop(env, 2) != -1); |
e15f96d2 | 725 | } |
e7331ce9 | 726 | int |
f540397c | 727 | cmd_store_something_env_value(const char *what, struct cmd_env *env, const void *value) |
e7331ce9 VB |
728 | { |
729 | return (cmdenv_put(env, what, value) != -1); | |
730 | } | |
9a775667 VB |
731 | |
732 | /** | |
733 | * Provide an iterator on all interfaces contained in "ports". | |
734 | * | |
735 | * @warning This function is not reentrant. It uses static variables to keep | |
736 | * track of ports that have already been provided. Moreover, to release all | |
737 | * resources, the iterator should be used until its end. | |
738 | * | |
739 | * @param conn The connection. | |
740 | * @param env The environment. | |
741 | * @return The next interface in the set of ports (or in all ports if no `ports` | |
742 | * variable is present in the environment) | |
743 | */ | |
8b549648 | 744 | lldpctl_atom_t * |
9a775667 VB |
745 | cmd_iterate_on_interfaces(struct lldpctl_conn_t *conn, struct cmd_env *env) |
746 | { | |
747 | static lldpctl_atom_iter_t *iter = NULL; | |
748 | static lldpctl_atom_t *iface_list = NULL; | |
749 | static lldpctl_atom_t *iface = NULL; | |
750 | const char *interfaces = cmdenv_get(env, "ports"); | |
751 | ||
752 | do { | |
753 | if (iter == NULL) { | |
754 | iface_list = lldpctl_get_interfaces(conn); | |
755 | if (!iface_list) { | |
8b549648 VB |
756 | log_warnx("lldpctl", |
757 | "not able to get the list of interfaces. %s", | |
9a775667 VB |
758 | lldpctl_last_strerror(conn)); |
759 | return NULL; | |
760 | } | |
761 | iter = lldpctl_atom_iter(iface_list); | |
762 | if (!iter) return NULL; | |
763 | } else { | |
764 | iter = lldpctl_atom_iter_next(iface_list, iter); | |
f3f1e6a2 VB |
765 | if (iface) { |
766 | lldpctl_atom_dec_ref(iface); | |
767 | iface = NULL; | |
768 | } | |
9a775667 VB |
769 | if (!iter) { |
770 | lldpctl_atom_dec_ref(iface_list); | |
771 | return NULL; | |
772 | } | |
773 | } | |
774 | ||
775 | iface = lldpctl_atom_iter_value(iface_list, iter); | |
8b549648 VB |
776 | } while (interfaces && |
777 | !contains(interfaces, | |
9a775667 VB |
778 | lldpctl_atom_get_str(iface, lldpctl_k_interface_name))); |
779 | ||
780 | return iface; | |
781 | } | |
e688f90f | 782 | |
9da663f7 VB |
783 | /** |
784 | * Provide an iterator on all ports contained in "ports", as well as the | |
785 | * default port. | |
786 | * | |
787 | * @warning This function is not reentrant. It uses static variables to keep | |
788 | * track of ports that have already been provided. Moreover, to release all | |
789 | * resources, the iterator should be used until its end. | |
790 | * | |
791 | * @param conn The connection. | |
792 | * @param env The environment. | |
793 | * @param name Name of the interface (for logging purpose) | |
794 | * @return The next interface in the set of ports (or in all ports if no `ports` | |
795 | * variable is present in the environment), including the default port | |
796 | * if no `ports` variable is present in the environment. | |
797 | */ | |
8b549648 VB |
798 | lldpctl_atom_t * |
799 | cmd_iterate_on_ports(struct lldpctl_conn_t *conn, struct cmd_env *env, | |
800 | const char **name) | |
9da663f7 VB |
801 | { |
802 | static int put_default = 0; | |
803 | static lldpctl_atom_t *last_port = NULL; | |
804 | const char *interfaces = cmdenv_get(env, "ports"); | |
805 | ||
806 | if (last_port) { | |
807 | lldpctl_atom_dec_ref(last_port); | |
808 | last_port = NULL; | |
809 | } | |
810 | if (!put_default) { | |
811 | lldpctl_atom_t *iface = cmd_iterate_on_interfaces(conn, env); | |
812 | if (iface) { | |
813 | *name = lldpctl_atom_get_str(iface, lldpctl_k_interface_name); | |
814 | last_port = lldpctl_get_port(iface); | |
815 | return last_port; | |
816 | } | |
04f8e4a3 | 817 | if (!interfaces) { |
9da663f7 VB |
818 | put_default = 1; |
819 | *name = "(default)"; | |
820 | last_port = lldpctl_get_default_port(conn); | |
821 | return last_port; | |
822 | } | |
823 | return NULL; | |
824 | } else { | |
825 | put_default = 0; | |
826 | return NULL; | |
827 | } | |
828 | } | |
829 | ||
e688f90f VB |
830 | /** |
831 | * Restrict the command to some ports. | |
832 | */ | |
833 | void | |
834 | cmd_restrict_ports(struct cmd_node *root) | |
835 | { | |
836 | /* Restrict to some ports. */ | |
8b549648 VB |
837 | commands_new(commands_new(root, "ports", "Restrict configuration to some ports", |
838 | cmd_check_no_env, NULL, "ports"), | |
839 | NULL, | |
840 | "Restrict configuration to the specified ports (comma-separated list)", | |
841 | NULL, cmd_store_env_value_and_pop2, "ports"); | |
e688f90f | 842 | } |
494264f0 ST |
843 | |
844 | /** | |
845 | * Restrict the command to specific protocol | |
846 | */ | |
847 | void | |
848 | cmd_restrict_protocol(struct cmd_node *root) | |
849 | { | |
850 | /* Restrict to some ports. */ | |
8b549648 VB |
851 | commands_new(commands_new(root, "protocol", "Restrict to specific protocol", |
852 | cmd_check_no_env, NULL, "protocol"), | |
853 | NULL, "Restrict display to the specified protocol", NULL, | |
854 | cmd_store_env_value_and_pop2, "protocol"); | |
494264f0 | 855 | } |