source: mainline/uspace/app/bdsh/compl.c@ b781cc49

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since b781cc49 was b781cc49, checked in by GitHub <noreply@…>, 6 years ago

Merge pull request #135 from matthieuriolo/bdsh_alias

Implements alias/unalias commands in bdsh.

  • Property mode set to 100644
File size: 10.9 KB
RevLine 
[9be9c4d]1/*
2 * Copyright (c) 2011 Jiri Svoboda
[e14a103]3 * Copyright (c) 2011 Martin Sucha
[9be9c4d]4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
[3e6a98c5]30#include <stdbool.h>
[9be9c4d]31#include <dirent.h>
32#include <errno.h>
33#include <macros.h>
34#include <stdlib.h>
[23a0368]35#include <vfs/vfs.h>
[1d6dd2a]36#include <str.h>
[ac2caecb]37#include <adt/odict.h>
[9be9c4d]38
[ac2caecb]39#include "scli.h"
[9be9c4d]40#include "cmds/cmds.h"
41#include "compl.h"
42#include "exec.h"
[e14a103]43#include "tok.h"
[1c481ee]44#include "util.h"
[9be9c4d]45
[b7fd2a0]46static errno_t compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state);
47static errno_t compl_get_next(void *state, char **compl);
[9be9c4d]48static void compl_fini(void *state);
49
50/** Bdsh implementation of completion ops. */
51tinput_compl_ops_t compl_ops = {
52 .init = compl_init,
53 .get_next = compl_get_next,
54 .fini = compl_fini
55};
56
57/** Completion state object.
58 *
59 * The state object contains 'iterators' for modules, builtins and
60 * executables in directories.
61 */
62typedef struct {
63 /** String prefix which we are trying to complete. */
64 char *prefix;
65 /** Length of string prefix (number of characters) */
66 size_t prefix_len;
67
[ac2caecb]68 /* Pointer to the current alias */
69 odlink_t *alias_link;
70
[9be9c4d]71 /** Pointer inside list of modules */
72 module_t *module;
73 /** Pointer inside list of builtins */
74 builtin_t *builtin;
75
76 /** Pointer inside list of directories */
[33b8d024]77 const char *const *path;
[3e5c48c9]78 /** If not @c NULL, should be freed in the end. */
[33b8d024]79 char **path_list;
[9be9c4d]80 /** Current open directory */
81 DIR *dir;
82
83 char *last_compl;
84
85 /**
86 * @c true if we are completing a command, @c false if we are
87 * completing an argument
88 */
89 bool is_command;
90} compl_t;
91
92/** Init completion.
93 *
94 * Set up iterators in completion object, based on current token.
95 */
[b7fd2a0]96static errno_t compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state)
[9be9c4d]97{
98 compl_t *cs = NULL;
99 char *stext = NULL;
100 char *prefix = NULL;
101 char *dirname = NULL;
[b7fd2a0]102 errno_t retval;
[a35b458]103
[8a6ba94]104 token_t *tokens = calloc(WORD_MAX, sizeof(token_t));
105 if (tokens == NULL) {
106 retval = ENOMEM;
107 goto error;
108 }
[a35b458]109
[5935c079]110 size_t pref_size;
111 char *rpath_sep;
112 static const char *dirlist_arg[] = { ".", NULL };
113 tokenizer_t tok;
114 ssize_t current_token;
[e14a103]115 size_t tokens_length;
[a35b458]116
[9be9c4d]117 cs = calloc(1, sizeof(compl_t));
118 if (!cs) {
119 retval = ENOMEM;
120 goto error;
121 }
[a35b458]122
[9be9c4d]123 /* Convert text buffer to string */
[e14a103]124 stext = wstr_to_astr(text);
[9be9c4d]125 if (stext == NULL) {
126 retval = ENOMEM;
127 goto error;
128 }
[a35b458]129
[e14a103]130 /* Tokenize the input string */
131 retval = tok_init(&tok, stext, tokens, WORD_MAX);
132 if (retval != EOK) {
133 goto error;
134 }
[a35b458]135
[e14a103]136 retval = tok_tokenize(&tok, &tokens_length);
137 if (retval != EOK) {
138 goto error;
139 }
[a35b458]140
[e14a103]141 /* Find the current token */
[5935c079]142 for (current_token = 0; current_token < (ssize_t) tokens_length;
[f737c1d5]143 current_token++) {
[e14a103]144 token_t *t = &tokens[current_token];
145 size_t end = t->char_start + t->char_length;
[a35b458]146
[5935c079]147 /*
148 * Check if the caret lies inside the token or immediately
[e14a103]149 * after it
150 */
151 if (t->char_start <= pos && pos <= end) {
152 break;
153 }
154 }
[a35b458]155
[5935c079]156 if (tokens_length == 0)
157 current_token = -1;
[a35b458]158
[5935c079]159 if ((current_token >= 0) && (tokens[current_token].type != TOKTYPE_SPACE))
[e14a103]160 *cstart = tokens[current_token].char_start;
[5935c079]161 else
[e14a103]162 *cstart = pos;
[a35b458]163
[5935c079]164 /*
165 * Extract the prefix being completed
[e14a103]166 * XXX: handle strings, etc.
167 */
[9be9c4d]168 pref_size = str_lsize(stext, pos - *cstart);
169 prefix = malloc(pref_size + 1);
170 if (prefix == NULL) {
171 retval = ENOMEM;
172 goto error;
173 }
[f737c1d5]174 prefix[pref_size] = 0;
[9be9c4d]175
[f737c1d5]176 if (current_token >= 0) {
177 str_ncpy(prefix, pref_size + 1, stext +
178 tokens[current_token].byte_start, pref_size);
179 }
[9be9c4d]180
181 /*
182 * Determine if the token being completed is a command or argument.
183 * We look at the previous token. If there is none or it is a pipe
184 * ('|'), it is a command, otherwise it is an argument.
185 */
186
187 /* Skip any whitespace before current token */
[5935c079]188 ssize_t prev_token = current_token - 1;
189 if ((prev_token >= 0) && (tokens[prev_token].type == TOKTYPE_SPACE))
[e14a103]190 prev_token--;
[a35b458]191
[9be9c4d]192 /*
193 * It is a command if it is the first token or if it immediately
194 * follows a pipe token.
195 */
[5935c079]196 if ((prev_token < 0) || (tokens[prev_token].type == TOKTYPE_SPACE))
[9be9c4d]197 cs->is_command = true;
198 else
199 cs->is_command = false;
200
201 rpath_sep = str_rchr(prefix, '/');
202 if (rpath_sep != NULL) {
203 /* Extract path. For path beginning with '/' keep the '/'. */
204 dirname = str_ndup(prefix, max(1, rpath_sep - prefix));
205 if (dirname == NULL) {
206 retval = ENOMEM;
207 goto error;
208 }
209
210 /* Extract name prefix */
211 cs->prefix = str_dup(rpath_sep + 1);
212 if (cs->prefix == NULL) {
213 retval = ENOMEM;
214 goto error;
215 }
216 *cstart += rpath_sep + 1 - prefix;
217
[3e5c48c9]218 cs->path_list = malloc(sizeof(char *) * 2);
219 if (cs->path_list == NULL) {
220 retval = ENOMEM;
221 goto error;
222 }
[1c481ee]223
224 if (!is_path(prefix) && cs->is_command) {
225 cs->path_list[0] = malloc(sizeof(char) * PATH_MAX);
[587867a]226 if (cs->path_list[0] == NULL) {
227 retval = ENOMEM;
228 goto error;
229 }
230
[1c481ee]231 int ret = snprintf(cs->path_list[0], PATH_MAX, "%s/%s", search_dir[0], dirname);
232 if (ret < 0 || ret >= PATH_MAX) {
233 retval = ENOMEM;
234 goto error;
235 }
236 } else {
237 cs->path_list[0] = dirname;
238 }
239
[587867a]240 free(prefix);
[3e5c48c9]241 cs->path_list[1] = NULL;
[7c3fb9b]242 /*
243 * The second const ensures that we can't assign a const
244 * string to the non-const array.
245 */
[33b8d024]246 cs->path = (const char *const *) cs->path_list;
[9be9c4d]247
248 } else if (cs->is_command) {
249 /* Command without path */
[ac2caecb]250 cs->alias_link = odict_first(&alias_dict);
[9be9c4d]251 cs->module = modules;
252 cs->builtin = builtins;
253 cs->prefix = prefix;
254 cs->path = &search_dir[0];
255 } else {
256 /* Argument without path */
257 cs->prefix = prefix;
258 cs->path = &dirlist_arg[0];
259 }
260
261 cs->prefix_len = str_length(cs->prefix);
[a35b458]262
[e14a103]263 tok_fini(&tok);
[9be9c4d]264
265 *state = cs;
266 return EOK;
267
268error:
269 /* Error cleanup */
[a35b458]270
[e14a103]271 tok_fini(&tok);
[9be9c4d]272
[3e5c48c9]273 if (cs != NULL && cs->path_list != NULL) {
274 size_t i = 0;
275 while (cs->path_list[i] != NULL) {
276 free(cs->path_list[i]);
277 ++i;
278 }
279 free(cs->path_list);
280 }
281
[10ef47ba]282 if ((cs != NULL) && (cs->prefix != NULL))
[9be9c4d]283 free(cs->prefix);
[a35b458]284
[9be9c4d]285 if (dirname != NULL)
286 free(dirname);
[a35b458]287
[9be9c4d]288 if (prefix != NULL)
289 free(prefix);
[a35b458]290
[9be9c4d]291 if (stext != NULL)
292 free(stext);
[a35b458]293
[9be9c4d]294 if (cs != NULL)
295 free(cs);
[a35b458]296
[8a6ba94]297 if (tokens != NULL)
298 free(tokens);
[9be9c4d]299
300 return retval;
301}
302
303/** Determine if completion matches the required prefix.
304 *
305 * Required prefix is stored in @a cs->prefix.
306 *
307 * @param cs Completion state object
308 * @param compl Completion string
309 * @return @c true when @a compl matches, @c false otherwise
310 */
311static bool compl_match_prefix(compl_t *cs, const char *compl)
312{
313 return str_lcmp(compl, cs->prefix, cs->prefix_len) == 0;
314}
315
316/** Get next match. */
[b7fd2a0]317static errno_t compl_get_next(void *state, char **compl)
[9be9c4d]318{
319 compl_t *cs = (compl_t *) state;
320 struct dirent *dent;
321
322 *compl = NULL;
323
324 if (cs->last_compl != NULL) {
325 free(cs->last_compl);
326 cs->last_compl = NULL;
327 }
328
[ac2caecb]329 /* Alias */
330 if (cs->alias_link != NULL) {
331 while (*compl == NULL && cs->alias_link != NULL) {
332 alias_t *data = odict_get_instance(cs->alias_link, alias_t, odict);
333 if (compl_match_prefix(cs, data->name)) {
334 asprintf(compl, "%s ", data->name);
335 cs->last_compl = *compl;
336 if (*compl == NULL)
337 return ENOMEM;
338 }
339 cs->alias_link = odict_next(cs->alias_link, &alias_dict);
340 }
341 }
342
[9be9c4d]343 /* Modules */
344 if (cs->module != NULL) {
345 while (*compl == NULL && cs->module->name != NULL) {
[ac2caecb]346 /* prevents multiple listing of an overriden cmd */
[9be9c4d]347 if (compl_match_prefix(cs, cs->module->name)) {
[ac2caecb]348 odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)cs->module->name, NULL);
349 if (alias_link == NULL) {
350 asprintf(compl, "%s ", cs->module->name);
351 cs->last_compl = *compl;
352 if (*compl == NULL)
353 return ENOMEM;
354 }
[9be9c4d]355 }
356 cs->module++;
357 }
358 }
359
360 /* Builtins */
361 if (cs->builtin != NULL) {
362 while (*compl == NULL && cs->builtin->name != NULL) {
363 if (compl_match_prefix(cs, cs->builtin->name)) {
[ac2caecb]364 /* prevents multiple listing of an overriden cmd */
365 odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)cs->module->name, NULL);
366 if (alias_link == NULL) {
367 asprintf(compl, "%s ", cs->builtin->name);
368 cs->last_compl = *compl;
369 if (*compl == NULL)
370 return ENOMEM;
371 }
[9be9c4d]372 }
373 cs->builtin++;
374 }
375 }
376
377 /* Files and directories. We scan entries from a set of directories. */
378 if (cs->path != NULL) {
379 while (*compl == NULL) {
380 /* Open next directory */
381 while (cs->dir == NULL) {
382 if (*cs->path == NULL)
383 break;
384
385 cs->dir = opendir(*cs->path);
[41ff85b]386
387 /* Skip directories that we fail to open. */
388 if (cs->dir == NULL)
389 cs->path++;
[9be9c4d]390 }
391
392 /* If it was the last one, we are done */
[3e5c48c9]393 if (cs->dir == NULL)
[9be9c4d]394 break;
395
396 /* Read next directory entry */
397 dent = readdir(cs->dir);
398 if (dent == NULL) {
399 /* Error. Close directory, go to next one */
400 closedir(cs->dir);
401 cs->dir = NULL;
402 cs->path++;
403 continue;
404 }
405
406 if (compl_match_prefix(cs, dent->d_name)) {
407 /* Construct pathname */
408 char *ent_path;
409 asprintf(&ent_path, "%s/%s", *cs->path, dent->d_name);
[39330200]410 vfs_stat_t ent_stat;
[23a0368]411 if (vfs_stat_path(ent_path, &ent_stat) != EOK) {
[9be9c4d]412 /* Error */
413 free(ent_path);
414 continue;
415 }
416
417 free(ent_path);
418
[ac2caecb]419 /* prevents multiple listing of an overriden cmd */
420 if (cs->is_command && !ent_stat.is_directory) {
421 odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)dent->d_name, NULL);
422 if (alias_link != NULL) {
423 continue;
424 }
425 }
[ab936440]426
[a91c555f]427 asprintf(compl, "%s%c", dent->d_name,
428 ent_stat.is_directory ? '/' : ' ');
429 cs->last_compl = *compl;
430 if (*compl == NULL)
431 return ENOMEM;
[9be9c4d]432 }
433 }
434 }
435
436 if (*compl == NULL)
437 return ENOENT;
438
439 return EOK;
440}
441
442/** Finish completion operation. */
443static void compl_fini(void *state)
444{
445 compl_t *cs = (compl_t *) state;
446
[3e5c48c9]447 if (cs->path_list != NULL) {
448 size_t i = 0;
449 while (cs->path_list[i] != NULL) {
450 free(cs->path_list[i]);
451 ++i;
452 }
453 free(cs->path_list);
454 }
455
[9be9c4d]456 if (cs->last_compl != NULL)
457 free(cs->last_compl);
458 if (cs->dir != NULL)
459 closedir(cs->dir);
460
461 free(cs->prefix);
462 free(cs);
463}
Note: See TracBrowser for help on using the repository browser.