source: mainline/uspace/app/bdsh/compl.c@ 1c635d6

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 1c635d6 was 10ef47ba, checked in by Martin Decky <martin@…>, 11 years ago

avoid double free of prefix
(detected by Coverity, CID 10457)

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