]> git.proxmox.com Git - grub2.git/blob - normal/completion.c
Remove unwantred commits
[grub2.git] / 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 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_malloc (grub_strlen (disk_name) + 1
111 + grub_strlen (partition_name) + 1);
112 if (! name)
113 {
114 grub_free (partition_name);
115 return 1;
116 }
117
118 grub_sprintf (name, "%s,%s", disk_name, partition_name);
119 grub_free (partition_name);
120
121 ret = add_completion (name, ")", GRUB_COMPLETION_TYPE_PARTITION);
122 grub_free (name);
123 return ret;
124 }
125
126 static int
127 iterate_dir (const char *filename, const struct grub_dirhook_info *info)
128 {
129 if (! info->dir)
130 {
131 const char *prefix;
132 if (cmdline_state == GRUB_PARSER_STATE_DQUOTE)
133 prefix = "\" ";
134 else if (cmdline_state == GRUB_PARSER_STATE_QUOTE)
135 prefix = "\' ";
136 else
137 prefix = " ";
138
139 if (add_completion (filename, prefix, GRUB_COMPLETION_TYPE_FILE))
140 return 1;
141 }
142 else if (grub_strcmp (filename, ".") && grub_strcmp (filename, ".."))
143 {
144 char fname[grub_strlen (filename) + 2];
145
146 grub_sprintf (fname, "%s/", filename);
147 if (add_completion (fname, "", GRUB_COMPLETION_TYPE_FILE))
148 return 1;
149 }
150
151 return 0;
152 }
153
154 static int
155 iterate_dev (const char *devname)
156 {
157 grub_device_t dev;
158
159 /* Complete the partition part. */
160 dev = grub_device_open (devname);
161
162 if (dev)
163 {
164 if (dev->disk && dev->disk->has_partitions)
165 {
166 if (add_completion (devname, ",", GRUB_COMPLETION_TYPE_DEVICE))
167 return 1;
168 }
169 else
170 {
171 if (add_completion (devname, ")", GRUB_COMPLETION_TYPE_DEVICE))
172 return 1;
173 }
174 }
175
176 grub_errno = GRUB_ERR_NONE;
177 return 0;
178 }
179
180 static int
181 iterate_command (grub_command_t cmd)
182 {
183 if (cmd->prio & GRUB_PRIO_LIST_FLAG_ACTIVE)
184 {
185 if (cmd->flags & GRUB_COMMAND_FLAG_CMDLINE)
186 {
187 if (add_completion (cmd->name, " ", GRUB_COMPLETION_TYPE_COMMAND))
188 return 1;
189 }
190 }
191
192 return 0;
193 }
194
195 /* Complete a device. */
196 static int
197 complete_device (void)
198 {
199 /* Check if this is a device or a partition. */
200 char *p = grub_strchr (++current_word, ',');
201 grub_device_t dev;
202
203 if (! p)
204 {
205 /* Complete the disk part. */
206 if (grub_disk_dev_iterate (iterate_dev))
207 return 1;
208 }
209 else
210 {
211 /* Complete the partition part. */
212 *p = '\0';
213 dev = grub_device_open (current_word);
214 *p = ',';
215 grub_errno = GRUB_ERR_NONE;
216
217 if (dev)
218 {
219 if (dev->disk && dev->disk->has_partitions)
220 {
221 if (grub_partition_iterate (dev->disk, iterate_partition))
222 {
223 grub_device_close (dev);
224 return 1;
225 }
226 }
227
228 grub_device_close (dev);
229 }
230 else
231 return 1;
232 }
233
234 return 0;
235 }
236
237 /* Complete a file. */
238 static int
239 complete_file (void)
240 {
241 char *device;
242 char *dir;
243 char *last_dir;
244 grub_fs_t fs;
245 grub_device_t dev;
246 int ret = 0;
247
248 device = grub_file_get_device_name (current_word);
249 if (grub_errno != GRUB_ERR_NONE)
250 return 1;
251
252 dev = grub_device_open (device);
253 if (! dev)
254 {
255 ret = 1;
256 goto fail;
257 }
258
259 fs = grub_fs_probe (dev);
260 if (! fs)
261 {
262 ret = 1;
263 goto fail;
264 }
265
266 dir = grub_strchr (current_word, '/');
267 last_dir = grub_strrchr (current_word, '/');
268 if (dir)
269 {
270 char *dirfile;
271
272 current_word = last_dir + 1;
273
274 dir = grub_strdup (dir);
275 if (! dir)
276 {
277 ret = 1;
278 goto fail;
279 }
280
281 /* Cut away the filename part. */
282 dirfile = grub_strrchr (dir, '/');
283 dirfile[1] = '\0';
284
285 /* Iterate the directory. */
286 (fs->dir) (dev, dir, iterate_dir);
287
288 grub_free (dir);
289
290 if (grub_errno)
291 {
292 ret = 1;
293 goto fail;
294 }
295 }
296 else
297 {
298 current_word += grub_strlen (current_word);
299 match = grub_strdup ("/");
300 if (! match)
301 {
302 ret = 1;
303 goto fail;
304 }
305
306 suffix = "";
307 num_found = 1;
308 }
309
310 fail:
311 if (dev)
312 grub_device_close (dev);
313 grub_free (device);
314 return ret;
315 }
316
317 /* Complete an argument. */
318 static int
319 complete_arguments (char *command)
320 {
321 grub_command_t cmd;
322 grub_extcmd_t ext;
323 const struct grub_arg_option *option;
324 char shortarg[] = "- ";
325
326 cmd = grub_command_find (command);
327
328 if (!cmd || !(cmd->flags & GRUB_COMMAND_FLAG_EXTCMD))
329 return 0;
330
331 ext = cmd->data;
332 if (!ext->options)
333 return 0;
334
335 if (add_completion ("-u", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
336 return 1;
337
338 /* Add the short arguments. */
339 for (option = ext->options; option->doc; option++)
340 {
341 if (! option->shortarg)
342 continue;
343
344 shortarg[1] = option->shortarg;
345 if (add_completion (shortarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
346 return 1;
347
348 }
349
350 /* First add the built-in arguments. */
351 if (add_completion ("--help", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
352 return 1;
353 if (add_completion ("--usage", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
354 return 1;
355
356 /* Add the long arguments. */
357 for (option = ext->options; option->doc; option++)
358 {
359 char *longarg;
360 if (!option->longarg)
361 continue;
362
363 longarg = grub_malloc (grub_strlen (option->longarg));
364 grub_sprintf (longarg, "--%s", option->longarg);
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 current_word = argv[argc];
413
414 /* Determine the state the command line is in, depending on the
415 state, it can be determined how to complete. */
416 cmdline_state = get_state (buf);
417
418 if (argc == 0)
419 {
420 /* Complete a command. */
421 if (grub_command_iterate (iterate_command))
422 goto fail;
423 }
424 else if (*current_word == '-')
425 {
426 if (complete_arguments (buf))
427 goto fail;
428 }
429 else if (*current_word == '(' && ! grub_strchr (current_word, ')'))
430 {
431 /* Complete a device. */
432 if (complete_device ())
433 goto fail;
434 }
435 else
436 {
437 /* Complete a file. */
438 if (complete_file ())
439 goto fail;
440 }
441
442 /* If more than one match is found those matches will be printed and
443 the prompt should be restored. */
444 if (num_found > 1)
445 *restore = 1;
446 else
447 *restore = 0;
448
449 /* Return the part that matches. */
450 if (match)
451 {
452 char *ret;
453 char *escstr;
454 char *newstr;
455 int current_len;
456 int match_len;
457 int spaces = 0;
458
459 current_len = grub_strlen (current_word);
460 match_len = grub_strlen (match);
461
462 /* Count the number of spaces that have to be escaped. XXX:
463 More than just spaces have to be escaped. */
464 for (escstr = match + current_len; *escstr; escstr++)
465 if (*escstr == ' ')
466 spaces++;
467
468 ret = grub_malloc (match_len - current_len + grub_strlen (suffix) + spaces + 1);
469 newstr = ret;
470 for (escstr = match + current_len; *escstr; escstr++)
471 {
472 if (*escstr == ' ' && cmdline_state != GRUB_PARSER_STATE_QUOTE
473 && cmdline_state != GRUB_PARSER_STATE_QUOTE)
474 *(newstr++) = '\\';
475 *(newstr++) = *escstr;
476 }
477 *newstr = '\0';
478
479 if (num_found == 1)
480 grub_strcat (ret, suffix);
481
482 if (*ret == '\0')
483 {
484 grub_free (ret);
485 goto fail;
486 }
487
488 grub_free (argv[0]);
489 grub_free (match);
490 return ret;
491 }
492
493 fail:
494 grub_free (argv[0]);
495 grub_free (match);
496 grub_errno = GRUB_ERR_NONE;
497
498 return 0;
499 }