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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a78cdcd was 587867a, checked in by Matthieu Riolo <matthieu.riolo@…>, 6 years ago

improve error handling

The code did not test if malloc returned NULL.
This meant that the code potentially could break without
that the user was informed.

  • Property mode set to 100644
File size: 9.8 KB
Line 
1/*
2 * Copyright (c) 2011 Jiri Svoboda
3 * Copyright (c) 2011 Martin Sucha
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
30#include <stdbool.h>
31#include <dirent.h>
32#include <errno.h>
33#include <macros.h>
34#include <stdlib.h>
35#include <vfs/vfs.h>
36#include <str.h>
37
38#include "cmds/cmds.h"
39#include "compl.h"
40#include "exec.h"
41#include "tok.h"
42#include "util.h"
43
44static errno_t compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state);
45static errno_t compl_get_next(void *state, char **compl);
46static void compl_fini(void *state);
47
48/** Bdsh implementation of completion ops. */
49tinput_compl_ops_t compl_ops = {
50 .init = compl_init,
51 .get_next = compl_get_next,
52 .fini = compl_fini
53};
54
55/** Completion state object.
56 *
57 * The state object contains 'iterators' for modules, builtins and
58 * executables in directories.
59 */
60typedef struct {
61 /** String prefix which we are trying to complete. */
62 char *prefix;
63 /** Length of string prefix (number of characters) */
64 size_t prefix_len;
65
66 /** Pointer inside list of modules */
67 module_t *module;
68 /** Pointer inside list of builtins */
69 builtin_t *builtin;
70
71 /** Pointer inside list of directories */
72 const char *const *path;
73 /** If not @c NULL, should be freed in the end. */
74 char **path_list;
75 /** Current open directory */
76 DIR *dir;
77
78 char *last_compl;
79
80 /**
81 * @c true if we are completing a command, @c false if we are
82 * completing an argument
83 */
84 bool is_command;
85} compl_t;
86
87/** Init completion.
88 *
89 * Set up iterators in completion object, based on current token.
90 */
91static errno_t compl_init(wchar_t *text, size_t pos, size_t *cstart, void **state)
92{
93 compl_t *cs = NULL;
94 char *stext = NULL;
95 char *prefix = NULL;
96 char *dirname = NULL;
97 errno_t retval;
98
99 token_t *tokens = calloc(WORD_MAX, sizeof(token_t));
100 if (tokens == NULL) {
101 retval = ENOMEM;
102 goto error;
103 }
104
105 size_t pref_size;
106 char *rpath_sep;
107 static const char *dirlist_arg[] = { ".", NULL };
108 tokenizer_t tok;
109 ssize_t current_token;
110 size_t tokens_length;
111
112 cs = calloc(1, sizeof(compl_t));
113 if (!cs) {
114 retval = ENOMEM;
115 goto error;
116 }
117
118 /* Convert text buffer to string */
119 stext = wstr_to_astr(text);
120 if (stext == NULL) {
121 retval = ENOMEM;
122 goto error;
123 }
124
125 /* Tokenize the input string */
126 retval = tok_init(&tok, stext, tokens, WORD_MAX);
127 if (retval != EOK) {
128 goto error;
129 }
130
131 retval = tok_tokenize(&tok, &tokens_length);
132 if (retval != EOK) {
133 goto error;
134 }
135
136 /* Find the current token */
137 for (current_token = 0; current_token < (ssize_t) tokens_length;
138 current_token++) {
139 token_t *t = &tokens[current_token];
140 size_t end = t->char_start + t->char_length;
141
142 /*
143 * Check if the caret lies inside the token or immediately
144 * after it
145 */
146 if (t->char_start <= pos && pos <= end) {
147 break;
148 }
149 }
150
151 if (tokens_length == 0)
152 current_token = -1;
153
154 if ((current_token >= 0) && (tokens[current_token].type != TOKTYPE_SPACE))
155 *cstart = tokens[current_token].char_start;
156 else
157 *cstart = pos;
158
159 /*
160 * Extract the prefix being completed
161 * XXX: handle strings, etc.
162 */
163 pref_size = str_lsize(stext, pos - *cstart);
164 prefix = malloc(pref_size + 1);
165 if (prefix == NULL) {
166 retval = ENOMEM;
167 goto error;
168 }
169 prefix[pref_size] = 0;
170
171 if (current_token >= 0) {
172 str_ncpy(prefix, pref_size + 1, stext +
173 tokens[current_token].byte_start, pref_size);
174 }
175
176 /*
177 * Determine if the token being completed is a command or argument.
178 * We look at the previous token. If there is none or it is a pipe
179 * ('|'), it is a command, otherwise it is an argument.
180 */
181
182 /* Skip any whitespace before current token */
183 ssize_t prev_token = current_token - 1;
184 if ((prev_token >= 0) && (tokens[prev_token].type == TOKTYPE_SPACE))
185 prev_token--;
186
187 /*
188 * It is a command if it is the first token or if it immediately
189 * follows a pipe token.
190 */
191 if ((prev_token < 0) || (tokens[prev_token].type == TOKTYPE_SPACE))
192 cs->is_command = true;
193 else
194 cs->is_command = false;
195
196 rpath_sep = str_rchr(prefix, '/');
197 if (rpath_sep != NULL) {
198 /* Extract path. For path beginning with '/' keep the '/'. */
199 dirname = str_ndup(prefix, max(1, rpath_sep - prefix));
200 if (dirname == NULL) {
201 retval = ENOMEM;
202 goto error;
203 }
204
205 /* Extract name prefix */
206 cs->prefix = str_dup(rpath_sep + 1);
207 if (cs->prefix == NULL) {
208 retval = ENOMEM;
209 goto error;
210 }
211 *cstart += rpath_sep + 1 - prefix;
212
213 cs->path_list = malloc(sizeof(char *) * 2);
214 if (cs->path_list == NULL) {
215 retval = ENOMEM;
216 goto error;
217 }
218
219 if (!is_path(prefix) && cs->is_command) {
220 cs->path_list[0] = malloc(sizeof(char) * PATH_MAX);
221 if (cs->path_list[0] == NULL) {
222 retval = ENOMEM;
223 goto error;
224 }
225
226 int ret = snprintf(cs->path_list[0], PATH_MAX, "%s/%s", search_dir[0], dirname);
227 if (ret < 0 || ret >= PATH_MAX) {
228 retval = ENOMEM;
229 goto error;
230 }
231 } else {
232 cs->path_list[0] = dirname;
233 }
234
235 free(prefix);
236 cs->path_list[1] = NULL;
237 /*
238 * The second const ensures that we can't assign a const
239 * string to the non-const array.
240 */
241 cs->path = (const char *const *) cs->path_list;
242
243 } else if (cs->is_command) {
244 /* Command without path */
245 cs->module = modules;
246 cs->builtin = builtins;
247 cs->prefix = prefix;
248 cs->path = &search_dir[0];
249 } else {
250 /* Argument without path */
251 cs->prefix = prefix;
252 cs->path = &dirlist_arg[0];
253 }
254
255 cs->prefix_len = str_length(cs->prefix);
256
257 tok_fini(&tok);
258
259 *state = cs;
260 return EOK;
261
262error:
263 /* Error cleanup */
264
265 tok_fini(&tok);
266
267 if (cs != NULL && cs->path_list != NULL) {
268 size_t i = 0;
269 while (cs->path_list[i] != NULL) {
270 free(cs->path_list[i]);
271 ++i;
272 }
273 free(cs->path_list);
274 }
275
276 if ((cs != NULL) && (cs->prefix != NULL))
277 free(cs->prefix);
278
279 if (dirname != NULL)
280 free(dirname);
281
282 if (prefix != NULL)
283 free(prefix);
284
285 if (stext != NULL)
286 free(stext);
287
288 if (cs != NULL)
289 free(cs);
290
291 if (tokens != NULL)
292 free(tokens);
293
294 return retval;
295}
296
297/** Determine if completion matches the required prefix.
298 *
299 * Required prefix is stored in @a cs->prefix.
300 *
301 * @param cs Completion state object
302 * @param compl Completion string
303 * @return @c true when @a compl matches, @c false otherwise
304 */
305static bool compl_match_prefix(compl_t *cs, const char *compl)
306{
307 return str_lcmp(compl, cs->prefix, cs->prefix_len) == 0;
308}
309
310/** Get next match. */
311static errno_t compl_get_next(void *state, char **compl)
312{
313 compl_t *cs = (compl_t *) state;
314 struct dirent *dent;
315
316 *compl = NULL;
317
318 if (cs->last_compl != NULL) {
319 free(cs->last_compl);
320 cs->last_compl = NULL;
321 }
322
323 /* Modules */
324 if (cs->module != NULL) {
325 while (*compl == NULL && cs->module->name != NULL) {
326 if (compl_match_prefix(cs, cs->module->name)) {
327 asprintf(compl, "%s ", cs->module->name);
328 cs->last_compl = *compl;
329 if (*compl == NULL)
330 return ENOMEM;
331 }
332 cs->module++;
333 }
334 }
335
336 /* Builtins */
337 if (cs->builtin != NULL) {
338 while (*compl == NULL && cs->builtin->name != NULL) {
339 if (compl_match_prefix(cs, cs->builtin->name)) {
340 asprintf(compl, "%s ", cs->builtin->name);
341 cs->last_compl = *compl;
342 if (*compl == NULL)
343 return ENOMEM;
344 }
345 cs->builtin++;
346 }
347 }
348
349 /* Files and directories. We scan entries from a set of directories. */
350 if (cs->path != NULL) {
351 while (*compl == NULL) {
352 /* Open next directory */
353 while (cs->dir == NULL) {
354 if (*cs->path == NULL)
355 break;
356
357 cs->dir = opendir(*cs->path);
358
359 /* Skip directories that we fail to open. */
360 if (cs->dir == NULL)
361 cs->path++;
362 }
363
364 /* If it was the last one, we are done */
365 if (cs->dir == NULL)
366 break;
367
368 /* Read next directory entry */
369 dent = readdir(cs->dir);
370 if (dent == NULL) {
371 /* Error. Close directory, go to next one */
372 closedir(cs->dir);
373 cs->dir = NULL;
374 cs->path++;
375 continue;
376 }
377
378 if (compl_match_prefix(cs, dent->d_name)) {
379 /* Construct pathname */
380 char *ent_path;
381 asprintf(&ent_path, "%s/%s", *cs->path, dent->d_name);
382 vfs_stat_t ent_stat;
383 if (vfs_stat_path(ent_path, &ent_stat) != EOK) {
384 /* Error */
385 free(ent_path);
386 continue;
387 }
388
389 free(ent_path);
390
391 asprintf(compl, "%s%c", dent->d_name,
392 ent_stat.is_directory ? '/' : ' ');
393 cs->last_compl = *compl;
394 if (*compl == NULL)
395 return ENOMEM;
396 }
397 }
398 }
399
400 if (*compl == NULL)
401 return ENOENT;
402
403 return EOK;
404}
405
406/** Finish completion operation. */
407static void compl_fini(void *state)
408{
409 compl_t *cs = (compl_t *) state;
410
411 if (cs->path_list != NULL) {
412 size_t i = 0;
413 while (cs->path_list[i] != NULL) {
414 free(cs->path_list[i]);
415 ++i;
416 }
417 free(cs->path_list);
418 }
419
420 if (cs->last_compl != NULL)
421 free(cs->last_compl);
422 if (cs->dir != NULL)
423 closedir(cs->dir);
424
425 free(cs->prefix);
426 free(cs);
427}
Note: See TracBrowser for help on using the repository browser.