123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509 |
- /* completion.c - complete a command, a disk, a partition or a file */
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc.
- *
- * GRUB is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <grub/misc.h>
- #include <grub/err.h>
- #include <grub/mm.h>
- #include <grub/partition.h>
- #include <grub/disk.h>
- #include <grub/file.h>
- #include <grub/parser.h>
- #include <grub/extcmd.h>
- #include <grub/lib.h>
- GRUB_EXPORT(grub_complete);
- /* The current word. */
- static char *current_word;
- /* The matched string. */
- static char *match;
- /* The count of candidates. */
- static int num_found;
- /* The string to be appended. */
- static const char *suffix;
- /* The callback function to print items. */
- static void (*print_func) (const char *, grub_completion_type_t, int);
- /* The state the command line is in. */
- static grub_parser_state_t cmdline_state;
- /* Add a string to the list of possible completions. COMPLETION is the
- string that should be added. EXTRA will be appended if COMPLETION
- matches uniquely. The type TYPE specifies what kind of data is added. */
- static int
- add_completion (const char *completion, const char *extra,
- grub_completion_type_t type)
- {
- if (grub_strncmp (current_word, completion, grub_strlen (current_word)) == 0)
- {
- num_found++;
- switch (num_found)
- {
- case 1:
- match = grub_strdup (completion);
- if (! match)
- return 1;
- suffix = extra;
- break;
- case 2:
- if (print_func)
- print_func (match, type, 0);
- /* Fall through. */
- default:
- {
- char *s = match;
- const char *t = completion;
- if (print_func)
- print_func (completion, type, num_found - 1);
- /* Detect the matched portion. */
- while (*s && *t && *s == *t)
- {
- s++;
- t++;
- }
- *s = '\0';
- }
- break;
- }
- }
- return 0;
- }
- static int
- iterate_partition (grub_disk_t disk, const grub_partition_t p,
- void *closure __attribute__ ((unused)))
- {
- const char *disk_name = disk->name;
- char *partition_name = grub_partition_get_name (p);
- char *name;
- int ret;
- if (! partition_name)
- return 1;
- name = grub_xasprintf ("%s,%s", disk_name, partition_name);
- grub_free (partition_name);
- if (! name)
- return 1;
- ret = add_completion (name, ")", GRUB_COMPLETION_TYPE_PARTITION);
- grub_free (name);
- return ret;
- }
- static int
- iterate_dir (const char *filename, const struct grub_dirhook_info *info,
- void *closure __attribute__ ((unused)))
- {
- if (! info->dir)
- {
- const char *prefix;
- if (cmdline_state == GRUB_PARSER_STATE_DQUOTE)
- prefix = "\" ";
- else if (cmdline_state == GRUB_PARSER_STATE_QUOTE)
- prefix = "\' ";
- else
- prefix = " ";
- if (add_completion (filename, prefix, GRUB_COMPLETION_TYPE_FILE))
- return 1;
- }
- else if (grub_strcmp (filename, ".") && grub_strcmp (filename, ".."))
- {
- char *fname;
- fname = grub_xasprintf ("%s/", filename);
- if (add_completion (fname, "", GRUB_COMPLETION_TYPE_FILE))
- {
- grub_free (fname);
- return 1;
- }
- grub_free (fname);
- }
- return 0;
- }
- static int
- iterate_dev (const char *devname, void *closure __attribute__ ((unused)))
- {
- grub_device_t dev;
- /* Complete the partition part. */
- dev = grub_device_open (devname);
- if (dev)
- {
- if (dev->disk && dev->disk->has_partitions)
- {
- if (add_completion (devname, ",", GRUB_COMPLETION_TYPE_DEVICE))
- return 1;
- }
- else
- {
- if (add_completion (devname, ")", GRUB_COMPLETION_TYPE_DEVICE))
- return 1;
- }
- }
- grub_errno = GRUB_ERR_NONE;
- return 0;
- }
- static int
- iterate_command (grub_command_t cmd, void *closure __attribute__ ((unused)))
- {
- if (cmd->prio & GRUB_PRIO_LIST_FLAG_ACTIVE)
- {
- if (cmd->flags & GRUB_COMMAND_FLAG_CMDLINE)
- {
- if (add_completion (cmd->name, " ", GRUB_COMPLETION_TYPE_COMMAND))
- return 1;
- }
- }
- return 0;
- }
- /* Complete a device. */
- static int
- complete_device (void)
- {
- /* Check if this is a device or a partition. */
- char *p = grub_strchr (++current_word, ',');
- grub_device_t dev;
- if (! p)
- {
- /* Complete the disk part. */
- if (grub_disk_dev_iterate (iterate_dev, 0))
- return 1;
- }
- else
- {
- /* Complete the partition part. */
- *p = '\0';
- dev = grub_device_open (current_word);
- *p = ',';
- grub_errno = GRUB_ERR_NONE;
- if (dev)
- {
- if (dev->disk && dev->disk->has_partitions)
- {
- if (grub_partition_iterate (dev->disk, iterate_partition, 0))
- {
- grub_device_close (dev);
- return 1;
- }
- }
- grub_device_close (dev);
- }
- else
- return 1;
- }
- return 0;
- }
- /* Complete a file. */
- static int
- complete_file (void)
- {
- char *device;
- char *dir;
- char *last_dir;
- grub_fs_t fs;
- grub_device_t dev;
- int ret = 0;
- device = grub_file_get_device_name (current_word);
- if (grub_errno != GRUB_ERR_NONE)
- return 1;
- dev = grub_device_open (device);
- if (! dev)
- {
- ret = 1;
- goto fail;
- }
- fs = grub_fs_probe (dev);
- if (! fs)
- {
- ret = 1;
- goto fail;
- }
- dir = grub_strchr (current_word, '/');
- last_dir = grub_strrchr (current_word, '/');
- if (dir)
- {
- char *dirfile;
- current_word = last_dir + 1;
- dir = grub_strdup (dir);
- if (! dir)
- {
- ret = 1;
- goto fail;
- }
- /* Cut away the filename part. */
- dirfile = grub_strrchr (dir, '/');
- dirfile[1] = '\0';
- /* Iterate the directory. */
- (fs->dir) (dev, dir, iterate_dir, 0);
- grub_free (dir);
- if (grub_errno)
- {
- ret = 1;
- goto fail;
- }
- }
- else
- {
- current_word += grub_strlen (current_word);
- match = grub_strdup ("/");
- if (! match)
- {
- ret = 1;
- goto fail;
- }
- suffix = "";
- num_found = 1;
- }
- fail:
- if (dev)
- grub_device_close (dev);
- grub_free (device);
- return ret;
- }
- /* Complete an argument. */
- static int
- complete_arguments (char *command)
- {
- grub_command_t cmd;
- grub_extcmd_t ext;
- const struct grub_arg_option *option;
- char shortarg[] = "- ";
- cmd = grub_command_find (command);
- if (!cmd || !(cmd->flags & GRUB_COMMAND_FLAG_EXTCMD))
- return 0;
- ext = cmd->data;
- if (!ext->options)
- return 0;
- if (add_completion ("-u", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
- return 1;
- /* Add the short arguments. */
- for (option = ext->options; option->doc; option++)
- {
- if (! option->shortarg)
- continue;
- shortarg[1] = option->shortarg;
- if (add_completion (shortarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
- return 1;
- }
- /* First add the built-in arguments. */
- if (add_completion ("--help", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
- return 1;
- if (add_completion ("--usage", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
- return 1;
- /* Add the long arguments. */
- for (option = ext->options; option->doc; option++)
- {
- char *longarg;
- if (!option->longarg)
- continue;
- longarg = grub_xasprintf ("--%s", option->longarg);
- if (!longarg)
- return 1;
- if (add_completion (longarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
- {
- grub_free (longarg);
- return 1;
- }
- grub_free (longarg);
- }
- return 0;
- }
- static grub_parser_state_t
- get_state (const char *cmdline)
- {
- grub_parser_state_t state = GRUB_PARSER_STATE_TEXT;
- char use;
- while (*cmdline)
- state = grub_parser_cmdline_state (state, *(cmdline++), &use);
- return state;
- }
- /* Try to complete the string in BUF. Return the characters that
- should be added to the string. This command outputs the possible
- completions by calling HOOK, in that case set RESTORE to 1 so the
- caller can restore the prompt. */
- char *
- grub_complete (char *buf, int *restore,
- void (*hook) (const char *, grub_completion_type_t, int))
- {
- int argc;
- char **argv;
- /* Initialize variables. */
- match = 0;
- num_found = 0;
- suffix = "";
- print_func = hook;
- *restore = 1;
- if (grub_parser_split_cmdline (buf, 0, 0, &argc, &argv))
- return 0;
- if (argc == 0)
- current_word = "";
- else
- current_word = argv[argc - 1];
- /* Determine the state the command line is in, depending on the
- state, it can be determined how to complete. */
- cmdline_state = get_state (buf);
- if (argc == 1 || argc == 0)
- {
- /* Complete a command. */
- if (grub_command_iterate (iterate_command, 0))
- goto fail;
- }
- else if (*current_word == '-')
- {
- if (complete_arguments (buf))
- goto fail;
- }
- else if (*current_word == '(' && ! grub_strchr (current_word, ')'))
- {
- /* Complete a device. */
- if (complete_device ())
- goto fail;
- }
- else
- {
- /* Complete a file. */
- if (complete_file ())
- goto fail;
- }
- /* If more than one match is found those matches will be printed and
- the prompt should be restored. */
- if (num_found > 1)
- *restore = 1;
- else
- *restore = 0;
- /* Return the part that matches. */
- if (match)
- {
- char *ret;
- char *escstr;
- char *newstr;
- int current_len;
- int match_len;
- int spaces = 0;
- current_len = grub_strlen (current_word);
- match_len = grub_strlen (match);
- /* Count the number of spaces that have to be escaped. XXX:
- More than just spaces have to be escaped. */
- for (escstr = match + current_len; *escstr; escstr++)
- if (*escstr == ' ')
- spaces++;
- ret = grub_malloc (match_len - current_len + grub_strlen (suffix) + spaces + 1);
- newstr = ret;
- for (escstr = match + current_len; *escstr; escstr++)
- {
- if (*escstr == ' ' && cmdline_state != GRUB_PARSER_STATE_QUOTE
- && cmdline_state != GRUB_PARSER_STATE_QUOTE)
- *(newstr++) = '\\';
- *(newstr++) = *escstr;
- }
- *newstr = '\0';
- if (num_found == 1)
- grub_strcat (ret, suffix);
- if (*ret == '\0')
- {
- grub_free (ret);
- goto fail;
- }
- if (argc != 0)
- grub_free (argv[0]);
- grub_free (match);
- return ret;
- }
- fail:
- if (argc != 0)
- grub_free (argv[0]);
- grub_free (match);
- grub_errno = GRUB_ERR_NONE;
- return 0;
- }
|