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

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

Fix bdsh completion crash

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