]> git.proxmox.com Git - grub2.git/blob - grub-core/normal/completion.c
pull-in emu-lite branch
[grub2.git] / grub-core / normal / completion.c
1 /* completion.c - complete a command, a disk, a partition or a file */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc.
5 *
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <grub/normal.h>
21 #include <grub/misc.h>
22 #include <grub/err.h>
23 #include <grub/mm.h>
24 #include <grub/partition.h>
25 #include <grub/disk.h>
26 #include <grub/file.h>
27 #include <grub/parser.h>
28 #include <grub/extcmd.h>
29
30 /* The current word. */
31 static char *current_word;
32
33 /* The matched string. */
34 static char *match;
35
36 /* The count of candidates. */
37 static int num_found;
38
39 /* The string to be appended. */
40 static const char *suffix;
41
42 /* The callback function to print items. */
43 static void (*print_func) (const char *, grub_completion_type_t, int);
44
45 /* The state the command line is in. */
46 static grub_parser_state_t cmdline_state;
47 \f
48
49 /* Add a string to the list of possible completions. COMPLETION is the
50 string that should be added. EXTRA will be appended if COMPLETION
51 matches uniquely. The type TYPE specifies what kind of data is added. */
52 static int
53 add_completion (const char *completion, const char *extra,
54 grub_completion_type_t type)
55 {
56 if (grub_strncmp (current_word, completion, grub_strlen (current_word)) == 0)
57 {
58 num_found++;
59
60 switch (num_found)
61 {
62 case 1:
63 match = grub_strdup (completion);
64 if (! match)
65 return 1;
66 suffix = extra;
67 break;
68
69 case 2:
70 if (print_func)
71 print_func (match, type, 0);
72
73 /* Fall through. */
74
75 default:
76 {
77 char *s = match;
78 const char *t = completion;
79
80 if (print_func)
81 print_func (completion, type, num_found - 1);
82
83 /* Detect the matched portion. */
84 while (*s && *t && *s == *t)
85 {
86 s++;
87 t++;
88 }
89
90 *s = '\0';
91 }
92 break;
93 }
94 }
95
96 return 0;
97 }
98
99 static int
100 iterate_partition (grub_disk_t disk, const grub_partition_t p)
101 {
102 const char *disk_name = disk->name;
103 char *partition_name = grub_partition_get_name (p);
104 char *name;
105 int ret;
106
107 if (! partition_name)
108 return 1;
109
110 name = grub_xasprintf ("%s,%s", disk_name, partition_name);
111 grub_free (partition_name);
112
113 if (! name)
114 return 1;
115
116 ret = add_completion (name, ")", GRUB_COMPLETION_TYPE_PARTITION);
117 grub_free (name);
118 return ret;
119 }
120
121 static int
122 iterate_dir (const char *filename, const struct grub_dirhook_info *info)
123 {
124 if (! info->dir)
125 {
126 const char *prefix;
127 if (cmdline_state == GRUB_PARSER_STATE_DQUOTE)
128 prefix = "\" ";
129 else if (cmdline_state == GRUB_PARSER_STATE_QUOTE)
130 prefix = "\' ";
131 else
132 prefix = " ";
133
134 if (add_completion (filename, prefix, GRUB_COMPLETION_TYPE_FILE))
135 return 1;
136 }
137 else if (grub_strcmp (filename, ".") && grub_strcmp (filename, ".."))
138 {
139 char *fname;
140
141 fname = grub_xasprintf ("%s/", filename);
142 if (add_completion (fname, "", GRUB_COMPLETION_TYPE_FILE))
143 {
144 grub_free (fname);
145 return 1;
146 }
147 grub_free (fname);
148 }
149
150 return 0;
151 }
152
153 static int
154 iterate_dev (const char *devname)
155 {
156 grub_device_t dev;
157
158 /* Complete the partition part. */
159 dev = grub_device_open (devname);
160
161 if (dev)
162 {
163 if (dev->disk && dev->disk->has_partitions)
164 {
165 if (add_completion (devname, ",", GRUB_COMPLETION_TYPE_DEVICE))
166 return 1;
167 }
168 else
169 {
170 if (add_completion (devname, ")", GRUB_COMPLETION_TYPE_DEVICE))
171 return 1;
172 }
173 }
174
175 grub_errno = GRUB_ERR_NONE;
176 return 0;
177 }
178
179 static int
180 iterate_command (grub_command_t cmd)
181 {
182 if (cmd->prio & GRUB_PRIO_LIST_FLAG_ACTIVE)
183 {
184 if (cmd->flags & GRUB_COMMAND_FLAG_CMDLINE)
185 {
186 if (add_completion (cmd->name, " ", GRUB_COMPLETION_TYPE_COMMAND))
187 return 1;
188 }
189 }
190
191 return 0;
192 }
193
194 /* Complete a device. */
195 static int
196 complete_device (void)
197 {
198 /* Check if this is a device or a partition. */
199 char *p = grub_strchr (++current_word, ',');
200 grub_device_t dev;
201
202 if (! p)
203 {
204 /* Complete the disk part. */
205 if (grub_disk_dev_iterate (iterate_dev))
206 return 1;
207 }
208 else
209 {
210 /* Complete the partition part. */
211 *p = '\0';
212 dev = grub_device_open (current_word);
213 *p = ',';
214 grub_errno = GRUB_ERR_NONE;
215
216 if (dev)
217 {
218 if (dev->disk && dev->disk->has_partitions)
219 {
220 if (grub_partition_iterate (dev->disk, iterate_partition))
221 {
222 grub_device_close (dev);
223 return 1;
224 }
225 }
226
227 grub_device_close (dev);
228 }
229 else
230 return 1;
231 }
232
233 return 0;
234 }
235
236 /* Complete a file. */
237 static int
238 complete_file (void)
239 {
240 char *device;
241 char *dir;
242 char *last_dir;
243 grub_fs_t fs;
244 grub_device_t dev;
245 int ret = 0;
246
247 device = grub_file_get_device_name (current_word);
248 if (grub_errno != GRUB_ERR_NONE)
249 return 1;
250
251 dev = grub_device_open (device);
252 if (! dev)
253 {
254 ret = 1;
255 goto fail;
256 }
257
258 fs = grub_fs_probe (dev);
259 if (! fs)
260 {
261 ret = 1;
262 goto fail;
263 }
264
265 dir = grub_strchr (current_word, '/');
266 last_dir = grub_strrchr (current_word, '/');
267 if (dir)
268 {
269 char *dirfile;
270
271 current_word = last_dir + 1;
272
273 dir = grub_strdup (dir);
274 if (! dir)
275 {
276 ret = 1;
277 goto fail;
278 }
279
280 /* Cut away the filename part. */
281 dirfile = grub_strrchr (dir, '/');
282 dirfile[1] = '\0';
283
284 /* Iterate the directory. */
285 (fs->dir) (dev, dir, iterate_dir);
286
287 grub_free (dir);
288
289 if (grub_errno)
290 {
291 ret = 1;
292 goto fail;
293 }
294 }
295 else
296 {
297 current_word += grub_strlen (current_word);
298 match = grub_strdup ("/");
299 if (! match)
300 {
301 ret = 1;
302 goto fail;
303 }
304
305 suffix = "";
306 num_found = 1;
307 }
308
309 fail:
310 if (dev)
311 grub_device_close (dev);
312 grub_free (device);
313 return ret;
314 }
315
316 /* Complete an argument. */
317 static int
318 complete_arguments (char *command)
319 {
320 grub_command_t cmd;
321 grub_extcmd_t ext;
322 const struct grub_arg_option *option;
323 char shortarg[] = "- ";
324
325 cmd = grub_command_find (command);
326
327 if (!cmd || !(cmd->flags & GRUB_COMMAND_FLAG_EXTCMD))
328 return 0;
329
330 ext = cmd->data;
331 if (!ext->options)
332 return 0;
333
334 if (add_completion ("-u", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
335 return 1;
336
337 /* Add the short arguments. */
338 for (option = ext->options; option->doc; option++)
339 {
340 if (! option->shortarg)
341 continue;
342
343 shortarg[1] = option->shortarg;
344 if (add_completion (shortarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
345 return 1;
346
347 }
348
349 /* First add the built-in arguments. */
350 if (add_completion ("--help", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
351 return 1;
352 if (add_completion ("--usage", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
353 return 1;
354
355 /* Add the long arguments. */
356 for (option = ext->options; option->doc; option++)
357 {
358 char *longarg;
359 if (!option->longarg)
360 continue;
361
362 longarg = grub_xasprintf ("--%s", option->longarg);
363 if (!longarg)
364 return 1;
365
366 if (add_completion (longarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
367 {
368 grub_free (longarg);
369 return 1;
370 }
371 grub_free (longarg);
372 }
373
374 return 0;
375 }
376
377
378 static grub_parser_state_t
379 get_state (const char *cmdline)
380 {
381 grub_parser_state_t state = GRUB_PARSER_STATE_TEXT;
382 char use;
383
384 while (*cmdline)
385 state = grub_parser_cmdline_state (state, *(cmdline++), &use);
386 return state;
387 }
388
389
390 /* Try to complete the string in BUF. Return the characters that
391 should be added to the string. This command outputs the possible
392 completions by calling HOOK, in that case set RESTORE to 1 so the
393 caller can restore the prompt. */
394 char *
395 grub_normal_do_completion (char *buf, int *restore,
396 void (*hook) (const char *, grub_completion_type_t, int))
397 {
398 int argc;
399 char **argv;
400
401 /* Initialize variables. */
402 match = 0;
403 num_found = 0;
404 suffix = "";
405 print_func = hook;
406
407 *restore = 1;
408
409 if (grub_parser_split_cmdline (buf, 0, &argc, &argv))
410 return 0;
411
412 if (argc == 0)
413 current_word = "";
414 else
415 current_word = argv[argc - 1];
416
417 /* Determine the state the command line is in, depending on the
418 state, it can be determined how to complete. */
419 cmdline_state = get_state (buf);
420
421 if (argc == 1 || argc == 0)
422 {
423 /* Complete a command. */
424 if (grub_command_iterate (iterate_command))
425 goto fail;
426 }
427 else if (*current_word == '-')
428 {
429 if (complete_arguments (buf))
430 goto fail;
431 }
432 else if (*current_word == '(' && ! grub_strchr (current_word, ')'))
433 {
434 /* Complete a device. */
435 if (complete_device ())
436 goto fail;
437 }
438 else
439 {
440 /* Complete a file. */
441 if (complete_file ())
442 goto fail;
443 }
444
445 /* If more than one match is found those matches will be printed and
446 the prompt should be restored. */
447 if (num_found > 1)
448 *restore = 1;
449 else
450 *restore = 0;
451
452 /* Return the part that matches. */
453 if (match)
454 {
455 char *ret;
456 char *escstr;
457 char *newstr;
458 int current_len;
459 int match_len;
460 int spaces = 0;
461
462 current_len = grub_strlen (current_word);
463 match_len = grub_strlen (match);
464
465 /* Count the number of spaces that have to be escaped. XXX:
466 More than just spaces have to be escaped. */
467 for (escstr = match + current_len; *escstr; escstr++)
468 if (*escstr == ' ')
469 spaces++;
470
471 ret = grub_malloc (match_len - current_len + grub_strlen (suffix) + spaces + 1);
472 newstr = ret;
473 for (escstr = match + current_len; *escstr; escstr++)
474 {
475 if (*escstr == ' ' && cmdline_state != GRUB_PARSER_STATE_QUOTE
476 && cmdline_state != GRUB_PARSER_STATE_QUOTE)
477 *(newstr++) = '\\';
478 *(newstr++) = *escstr;
479 }
480 *newstr = '\0';
481
482 if (num_found == 1)
483 grub_strcat (ret, suffix);
484
485 if (*ret == '\0')
486 {
487 grub_free (ret);
488 goto fail;
489 }
490
491 if (argc != 0)
492 grub_free (argv[0]);
493 grub_free (match);
494 return ret;
495 }
496
497 fail:
498 if (argc != 0)
499 grub_free (argv[0]);
500 grub_free (match);
501 grub_errno = GRUB_ERR_NONE;
502
503 return 0;
504 }