2009-02-03 Felix Zielcke <fzielcke@z-51.de>
[grub2/phcoder/solaris.git] / normal / completion.c
blobfb38f28663c7759b4d1465318b91fa8bc8a53022
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 Free Software Foundation, Inc.
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.
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.
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/>.
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>
29 /* The current word. */
30 static char *current_word;
32 /* The matched string. */
33 static char *match;
35 /* The count of candidates. */
36 static int num_found;
38 /* The string to be appended. */
39 static const char *suffix;
41 /* The callback function to print items. */
42 static void (*print_func) (const char *, grub_completion_type_t, int);
44 /* The state the command line is in. */
45 static grub_parser_state_t cmdline_state;
48 /* Add a string to the list of possible completions. COMPLETION is the
49 string that should be added. EXTRA will be appended if COMPLETION
50 matches uniquely. The type TYPE specifies what kind of data is added. */
51 static int
52 add_completion (const char *completion, const char *extra,
53 grub_completion_type_t type)
55 if (grub_strncmp (current_word, completion, grub_strlen (current_word)) == 0)
57 num_found++;
59 switch (num_found)
61 case 1:
62 match = grub_strdup (completion);
63 if (! match)
64 return 1;
65 suffix = extra;
66 break;
68 case 2:
69 if (print_func)
70 print_func (match, type, 0);
72 /* Fall through. */
74 default:
76 char *s = match;
77 const char *t = completion;
79 if (print_func)
80 print_func (completion, type, num_found - 1);
82 /* Detect the matched portion. */
83 while (*s && *t && *s == *t)
85 s++;
86 t++;
89 *s = '\0';
91 break;
95 return 0;
98 static int
99 iterate_partition (grub_disk_t disk, const grub_partition_t p)
101 const char *disk_name = disk->name;
102 char *partition_name = grub_partition_get_name (p);
103 char *name;
104 int ret;
106 if (! partition_name)
107 return 1;
109 name = grub_malloc (grub_strlen (disk_name) + 1
110 + grub_strlen (partition_name) + 1);
111 if (! name)
113 grub_free (partition_name);
114 return 1;
117 grub_sprintf (name, "%s,%s", disk_name, partition_name);
118 grub_free (partition_name);
120 ret = add_completion (name, ")", GRUB_COMPLETION_TYPE_PARTITION);
121 grub_free (name);
122 return ret;
125 static int
126 iterate_dir (const char *filename, int dir)
128 if (! dir)
130 const char *prefix;
131 if (cmdline_state == GRUB_PARSER_STATE_DQUOTE)
132 prefix = "\" ";
133 else if (cmdline_state == GRUB_PARSER_STATE_QUOTE)
134 prefix = "\' ";
135 else
136 prefix = " ";
138 if (add_completion (filename, prefix, GRUB_COMPLETION_TYPE_FILE))
139 return 1;
141 else if (grub_strcmp (filename, ".") && grub_strcmp (filename, ".."))
143 char fname[grub_strlen (filename) + 2];
145 grub_sprintf (fname, "%s/", filename);
146 if (add_completion (fname, "", GRUB_COMPLETION_TYPE_FILE))
147 return 1;
150 return 0;
153 static int
154 iterate_dev (const char *devname)
156 grub_device_t dev;
158 /* Complete the partition part. */
159 dev = grub_device_open (devname);
161 if (dev)
163 if (dev->disk && dev->disk->has_partitions)
165 if (add_completion (devname, ",", GRUB_COMPLETION_TYPE_DEVICE))
166 return 1;
168 else
170 if (add_completion (devname, ")", GRUB_COMPLETION_TYPE_DEVICE))
171 return 1;
175 grub_errno = GRUB_ERR_NONE;
176 return 0;
179 static int
180 iterate_command (grub_command_t cmd)
182 if (grub_command_find (cmd->name))
184 if (cmd->flags & GRUB_COMMAND_FLAG_CMDLINE)
186 if (add_completion (cmd->name, " ", GRUB_COMPLETION_TYPE_COMMAND))
187 return 1;
191 return 0;
194 /* Complete a device. */
195 static int
196 complete_device (void)
198 /* Check if this is a device or a partition. */
199 char *p = grub_strchr (++current_word, ',');
200 grub_device_t dev;
202 if (! p)
204 /* Complete the disk part. */
205 if (grub_disk_dev_iterate (iterate_dev))
206 return 1;
208 else
210 /* Complete the partition part. */
211 *p = '\0';
212 dev = grub_device_open (current_word);
213 *p = ',';
214 grub_errno = GRUB_ERR_NONE;
216 if (dev)
218 if (dev->disk && dev->disk->has_partitions)
220 if (grub_partition_iterate (dev->disk, iterate_partition))
222 grub_device_close (dev);
223 return 1;
227 grub_device_close (dev);
229 else
230 return 1;
233 return 0;
236 /* Complete a file. */
237 static int
238 complete_file (void)
240 char *device;
241 char *dir;
242 char *last_dir;
243 grub_fs_t fs;
244 grub_device_t dev;
245 int ret = 0;
247 device = grub_file_get_device_name (current_word);
248 if (grub_errno != GRUB_ERR_NONE)
249 return 1;
251 dev = grub_device_open (device);
252 if (! dev)
254 ret = 1;
255 goto fail;
258 fs = grub_fs_probe (dev);
259 if (! fs)
261 ret = 1;
262 goto fail;
265 dir = grub_strchr (current_word, '/');
266 last_dir = grub_strrchr (current_word, '/');
267 if (dir)
269 char *dirfile;
271 current_word = last_dir + 1;
273 dir = grub_strdup (dir);
274 if (! dir)
276 ret = 1;
277 goto fail;
280 /* Cut away the filename part. */
281 dirfile = grub_strrchr (dir, '/');
282 dirfile[1] = '\0';
284 /* Iterate the directory. */
285 (fs->dir) (dev, dir, iterate_dir);
287 grub_free (dir);
289 if (grub_errno)
291 ret = 1;
292 goto fail;
295 else
297 current_word += grub_strlen (current_word);
298 match = grub_strdup ("/");
299 if (! match)
301 ret = 1;
302 goto fail;
305 suffix = "";
306 num_found = 1;
309 fail:
310 if (dev)
311 grub_device_close (dev);
312 grub_free (device);
313 return ret;
316 /* Complete an argument. */
317 static int
318 complete_arguments (char *command)
320 grub_command_t cmd;
321 const struct grub_arg_option *option;
322 char shortarg[] = "- ";
324 cmd = grub_command_find (command);
326 if (!cmd || !cmd->options)
327 return 0;
329 if (add_completion ("-u", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
330 return 1;
332 /* Add the short arguments. */
333 for (option = cmd->options; option->doc; option++)
335 if (! option->shortarg)
336 continue;
338 shortarg[1] = option->shortarg;
339 if (add_completion (shortarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
340 return 1;
344 /* First add the built-in arguments. */
345 if (add_completion ("--help", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
346 return 1;
347 if (add_completion ("--usage", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
348 return 1;
350 /* Add the long arguments. */
351 for (option = cmd->options; option->doc; option++)
353 char *longarg;
354 if (!option->longarg)
355 continue;
357 longarg = grub_malloc (grub_strlen (option->longarg));
358 grub_sprintf (longarg, "--%s", option->longarg);
360 if (add_completion (longarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
362 grub_free (longarg);
363 return 1;
365 grub_free (longarg);
368 return 0;
372 static grub_parser_state_t
373 get_state (const char *cmdline)
375 grub_parser_state_t state = GRUB_PARSER_STATE_TEXT;
376 char use;
378 while (*cmdline)
379 state = grub_parser_cmdline_state (state, *(cmdline++), &use);
380 return state;
384 /* Try to complete the string in BUF. Return the characters that
385 should be added to the string. This command outputs the possible
386 completions by calling HOOK, in that case set RESTORE to 1 so the
387 caller can restore the prompt. */
388 char *
389 grub_normal_do_completion (char *buf, int *restore,
390 void (*hook) (const char *, grub_completion_type_t, int))
392 int argc;
393 char **argv;
395 /* Initialize variables. */
396 match = 0;
397 num_found = 0;
398 suffix = "";
399 print_func = hook;
401 *restore = 1;
403 if (grub_parser_split_cmdline (buf, 0, &argc, &argv))
404 return 0;
406 current_word = argv[argc];
408 /* Determine the state the command line is in, depending on the
409 state, it can be determined how to complete. */
410 cmdline_state = get_state (buf);
412 if (argc == 0)
414 /* Complete a command. */
415 if (grub_iterate_commands (iterate_command))
416 goto fail;
418 else if (*current_word == '-')
420 if (complete_arguments (buf))
421 goto fail;
423 else if (*current_word == '(' && ! grub_strchr (current_word, ')'))
425 /* Complete a device. */
426 if (complete_device ())
427 goto fail;
429 else
431 /* Complete a file. */
432 if (complete_file ())
433 goto fail;
436 /* If more than one match is found those matches will be printed and
437 the prompt should be restored. */
438 if (num_found > 1)
439 *restore = 1;
440 else
441 *restore = 0;
443 /* Return the part that matches. */
444 if (match)
446 char *ret;
447 char *escstr;
448 char *newstr;
449 int current_len;
450 int match_len;
451 int spaces = 0;
453 current_len = grub_strlen (current_word);
454 match_len = grub_strlen (match);
456 /* Count the number of spaces that have to be escaped. XXX:
457 More than just spaces have to be escaped. */
458 for (escstr = match + current_len; *escstr; escstr++)
459 if (*escstr == ' ')
460 spaces++;
462 ret = grub_malloc (match_len - current_len + grub_strlen (suffix) + spaces + 1);
463 newstr = ret;
464 for (escstr = match + current_len; *escstr; escstr++)
466 if (*escstr == ' ' && cmdline_state != GRUB_PARSER_STATE_QUOTE
467 && cmdline_state != GRUB_PARSER_STATE_QUOTE)
468 *(newstr++) = '\\';
469 *(newstr++) = *escstr;
471 *newstr = '\0';
473 if (num_found == 1)
474 grub_strcat (ret, suffix);
476 if (*ret == '\0')
478 grub_free (ret);
479 goto fail;
482 grub_free (argv[0]);
483 grub_free (match);
484 return ret;
487 fail:
488 grub_free (argv[0]);
489 grub_free (match);
490 grub_errno = GRUB_ERR_NONE;
492 return 0;