completion.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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. #include <grub/misc.h>
  20. #include <grub/err.h>
  21. #include <grub/mm.h>
  22. #include <grub/partition.h>
  23. #include <grub/disk.h>
  24. #include <grub/file.h>
  25. #include <grub/parser.h>
  26. #include <grub/extcmd.h>
  27. #include <grub/lib.h>
  28. GRUB_EXPORT(grub_complete);
  29. /* The current word. */
  30. static char *current_word;
  31. /* The matched string. */
  32. static char *match;
  33. /* The count of candidates. */
  34. static int num_found;
  35. /* The string to be appended. */
  36. static const char *suffix;
  37. /* The callback function to print items. */
  38. static void (*print_func) (const char *, grub_completion_type_t, int);
  39. /* The state the command line is in. */
  40. static grub_parser_state_t cmdline_state;
  41. /* Add a string to the list of possible completions. COMPLETION is the
  42. string that should be added. EXTRA will be appended if COMPLETION
  43. matches uniquely. The type TYPE specifies what kind of data is added. */
  44. static int
  45. add_completion (const char *completion, const char *extra,
  46. grub_completion_type_t type)
  47. {
  48. if (grub_strncmp (current_word, completion, grub_strlen (current_word)) == 0)
  49. {
  50. num_found++;
  51. switch (num_found)
  52. {
  53. case 1:
  54. match = grub_strdup (completion);
  55. if (! match)
  56. return 1;
  57. suffix = extra;
  58. break;
  59. case 2:
  60. if (print_func)
  61. print_func (match, type, 0);
  62. /* Fall through. */
  63. default:
  64. {
  65. char *s = match;
  66. const char *t = completion;
  67. if (print_func)
  68. print_func (completion, type, num_found - 1);
  69. /* Detect the matched portion. */
  70. while (*s && *t && *s == *t)
  71. {
  72. s++;
  73. t++;
  74. }
  75. *s = '\0';
  76. }
  77. break;
  78. }
  79. }
  80. return 0;
  81. }
  82. static int
  83. iterate_partition (grub_disk_t disk, const grub_partition_t p,
  84. void *closure __attribute__ ((unused)))
  85. {
  86. const char *disk_name = disk->name;
  87. char *partition_name = grub_partition_get_name (p);
  88. char *name;
  89. int ret;
  90. if (! partition_name)
  91. return 1;
  92. name = grub_xasprintf ("%s,%s", disk_name, partition_name);
  93. grub_free (partition_name);
  94. if (! name)
  95. return 1;
  96. ret = add_completion (name, ")", GRUB_COMPLETION_TYPE_PARTITION);
  97. grub_free (name);
  98. return ret;
  99. }
  100. static int
  101. iterate_dir (const char *filename, const struct grub_dirhook_info *info,
  102. void *closure __attribute__ ((unused)))
  103. {
  104. if (! info->dir)
  105. {
  106. const char *prefix;
  107. if (cmdline_state == GRUB_PARSER_STATE_DQUOTE)
  108. prefix = "\" ";
  109. else if (cmdline_state == GRUB_PARSER_STATE_QUOTE)
  110. prefix = "\' ";
  111. else
  112. prefix = " ";
  113. if (add_completion (filename, prefix, GRUB_COMPLETION_TYPE_FILE))
  114. return 1;
  115. }
  116. else if (grub_strcmp (filename, ".") && grub_strcmp (filename, ".."))
  117. {
  118. char *fname;
  119. fname = grub_xasprintf ("%s/", filename);
  120. if (add_completion (fname, "", GRUB_COMPLETION_TYPE_FILE))
  121. {
  122. grub_free (fname);
  123. return 1;
  124. }
  125. grub_free (fname);
  126. }
  127. return 0;
  128. }
  129. static int
  130. iterate_dev (const char *devname, void *closure __attribute__ ((unused)))
  131. {
  132. grub_device_t dev;
  133. /* Complete the partition part. */
  134. dev = grub_device_open (devname);
  135. if (dev)
  136. {
  137. if (dev->disk && dev->disk->has_partitions)
  138. {
  139. if (add_completion (devname, ",", GRUB_COMPLETION_TYPE_DEVICE))
  140. return 1;
  141. }
  142. else
  143. {
  144. if (add_completion (devname, ")", GRUB_COMPLETION_TYPE_DEVICE))
  145. return 1;
  146. }
  147. }
  148. grub_errno = GRUB_ERR_NONE;
  149. return 0;
  150. }
  151. static int
  152. iterate_command (grub_command_t cmd, void *closure __attribute__ ((unused)))
  153. {
  154. if (cmd->prio & GRUB_PRIO_LIST_FLAG_ACTIVE)
  155. {
  156. if (cmd->flags & GRUB_COMMAND_FLAG_CMDLINE)
  157. {
  158. if (add_completion (cmd->name, " ", GRUB_COMPLETION_TYPE_COMMAND))
  159. return 1;
  160. }
  161. }
  162. return 0;
  163. }
  164. /* Complete a device. */
  165. static int
  166. complete_device (void)
  167. {
  168. /* Check if this is a device or a partition. */
  169. char *p = grub_strchr (++current_word, ',');
  170. grub_device_t dev;
  171. if (! p)
  172. {
  173. /* Complete the disk part. */
  174. if (grub_disk_dev_iterate (iterate_dev, 0))
  175. return 1;
  176. }
  177. else
  178. {
  179. /* Complete the partition part. */
  180. *p = '\0';
  181. dev = grub_device_open (current_word);
  182. *p = ',';
  183. grub_errno = GRUB_ERR_NONE;
  184. if (dev)
  185. {
  186. if (dev->disk && dev->disk->has_partitions)
  187. {
  188. if (grub_partition_iterate (dev->disk, iterate_partition, 0))
  189. {
  190. grub_device_close (dev);
  191. return 1;
  192. }
  193. }
  194. grub_device_close (dev);
  195. }
  196. else
  197. return 1;
  198. }
  199. return 0;
  200. }
  201. /* Complete a file. */
  202. static int
  203. complete_file (void)
  204. {
  205. char *device;
  206. char *dir;
  207. char *last_dir;
  208. grub_fs_t fs;
  209. grub_device_t dev;
  210. int ret = 0;
  211. device = grub_file_get_device_name (current_word);
  212. if (grub_errno != GRUB_ERR_NONE)
  213. return 1;
  214. dev = grub_device_open (device);
  215. if (! dev)
  216. {
  217. ret = 1;
  218. goto fail;
  219. }
  220. fs = grub_fs_probe (dev);
  221. if (! fs)
  222. {
  223. ret = 1;
  224. goto fail;
  225. }
  226. dir = grub_strchr (current_word, '/');
  227. last_dir = grub_strrchr (current_word, '/');
  228. if (dir)
  229. {
  230. char *dirfile;
  231. current_word = last_dir + 1;
  232. dir = grub_strdup (dir);
  233. if (! dir)
  234. {
  235. ret = 1;
  236. goto fail;
  237. }
  238. /* Cut away the filename part. */
  239. dirfile = grub_strrchr (dir, '/');
  240. dirfile[1] = '\0';
  241. /* Iterate the directory. */
  242. (fs->dir) (dev, dir, iterate_dir, 0);
  243. grub_free (dir);
  244. if (grub_errno)
  245. {
  246. ret = 1;
  247. goto fail;
  248. }
  249. }
  250. else
  251. {
  252. current_word += grub_strlen (current_word);
  253. match = grub_strdup ("/");
  254. if (! match)
  255. {
  256. ret = 1;
  257. goto fail;
  258. }
  259. suffix = "";
  260. num_found = 1;
  261. }
  262. fail:
  263. if (dev)
  264. grub_device_close (dev);
  265. grub_free (device);
  266. return ret;
  267. }
  268. /* Complete an argument. */
  269. static int
  270. complete_arguments (char *command)
  271. {
  272. grub_command_t cmd;
  273. grub_extcmd_t ext;
  274. const struct grub_arg_option *option;
  275. char shortarg[] = "- ";
  276. cmd = grub_command_find (command);
  277. if (!cmd || !(cmd->flags & GRUB_COMMAND_FLAG_EXTCMD))
  278. return 0;
  279. ext = cmd->data;
  280. if (!ext->options)
  281. return 0;
  282. if (add_completion ("-u", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
  283. return 1;
  284. /* Add the short arguments. */
  285. for (option = ext->options; option->doc; option++)
  286. {
  287. if (! option->shortarg)
  288. continue;
  289. shortarg[1] = option->shortarg;
  290. if (add_completion (shortarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
  291. return 1;
  292. }
  293. /* First add the built-in arguments. */
  294. if (add_completion ("--help", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
  295. return 1;
  296. if (add_completion ("--usage", " ", GRUB_COMPLETION_TYPE_ARGUMENT))
  297. return 1;
  298. /* Add the long arguments. */
  299. for (option = ext->options; option->doc; option++)
  300. {
  301. char *longarg;
  302. if (!option->longarg)
  303. continue;
  304. longarg = grub_xasprintf ("--%s", option->longarg);
  305. if (!longarg)
  306. return 1;
  307. if (add_completion (longarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT))
  308. {
  309. grub_free (longarg);
  310. return 1;
  311. }
  312. grub_free (longarg);
  313. }
  314. return 0;
  315. }
  316. static grub_parser_state_t
  317. get_state (const char *cmdline)
  318. {
  319. grub_parser_state_t state = GRUB_PARSER_STATE_TEXT;
  320. char use;
  321. while (*cmdline)
  322. state = grub_parser_cmdline_state (state, *(cmdline++), &use);
  323. return state;
  324. }
  325. /* Try to complete the string in BUF. Return the characters that
  326. should be added to the string. This command outputs the possible
  327. completions by calling HOOK, in that case set RESTORE to 1 so the
  328. caller can restore the prompt. */
  329. char *
  330. grub_complete (char *buf, int *restore,
  331. void (*hook) (const char *, grub_completion_type_t, int))
  332. {
  333. int argc;
  334. char **argv;
  335. /* Initialize variables. */
  336. match = 0;
  337. num_found = 0;
  338. suffix = "";
  339. print_func = hook;
  340. *restore = 1;
  341. if (grub_parser_split_cmdline (buf, 0, 0, &argc, &argv))
  342. return 0;
  343. if (argc == 0)
  344. current_word = "";
  345. else
  346. current_word = argv[argc - 1];
  347. /* Determine the state the command line is in, depending on the
  348. state, it can be determined how to complete. */
  349. cmdline_state = get_state (buf);
  350. if (argc == 1 || argc == 0)
  351. {
  352. /* Complete a command. */
  353. if (grub_command_iterate (iterate_command, 0))
  354. goto fail;
  355. }
  356. else if (*current_word == '-')
  357. {
  358. if (complete_arguments (buf))
  359. goto fail;
  360. }
  361. else if (*current_word == '(' && ! grub_strchr (current_word, ')'))
  362. {
  363. /* Complete a device. */
  364. if (complete_device ())
  365. goto fail;
  366. }
  367. else
  368. {
  369. /* Complete a file. */
  370. if (complete_file ())
  371. goto fail;
  372. }
  373. /* If more than one match is found those matches will be printed and
  374. the prompt should be restored. */
  375. if (num_found > 1)
  376. *restore = 1;
  377. else
  378. *restore = 0;
  379. /* Return the part that matches. */
  380. if (match)
  381. {
  382. char *ret;
  383. char *escstr;
  384. char *newstr;
  385. int current_len;
  386. int match_len;
  387. int spaces = 0;
  388. current_len = grub_strlen (current_word);
  389. match_len = grub_strlen (match);
  390. /* Count the number of spaces that have to be escaped. XXX:
  391. More than just spaces have to be escaped. */
  392. for (escstr = match + current_len; *escstr; escstr++)
  393. if (*escstr == ' ')
  394. spaces++;
  395. ret = grub_malloc (match_len - current_len + grub_strlen (suffix) + spaces + 1);
  396. newstr = ret;
  397. for (escstr = match + current_len; *escstr; escstr++)
  398. {
  399. if (*escstr == ' ' && cmdline_state != GRUB_PARSER_STATE_QUOTE
  400. && cmdline_state != GRUB_PARSER_STATE_QUOTE)
  401. *(newstr++) = '\\';
  402. *(newstr++) = *escstr;
  403. }
  404. *newstr = '\0';
  405. if (num_found == 1)
  406. grub_strcat (ret, suffix);
  407. if (*ret == '\0')
  408. {
  409. grub_free (ret);
  410. goto fail;
  411. }
  412. if (argc != 0)
  413. grub_free (argv[0]);
  414. grub_free (match);
  415. return ret;
  416. }
  417. fail:
  418. if (argc != 0)
  419. grub_free (argv[0]);
  420. grub_free (match);
  421. grub_errno = GRUB_ERR_NONE;
  422. return 0;
  423. }