source: mainline/uspace/app/bdsh/compl.c@ 8a6ba94

lfn serial ticket/834-toolchain-update topic/fix-logger-deadlock topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 8a6ba94 was 8a6ba94, checked in by Martin Sucha <sucha14@…>, 15 years ago

Fix stack overflow also in completion code

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