cmdline.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. /*
  2. * GRUB -- GRand Unified Bootloader
  3. * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2009 Free Software Foundation, Inc.
  4. *
  5. * GRUB is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * GRUB is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include <grub/normal.h>
  19. #include <grub/misc.h>
  20. #include <grub/term.h>
  21. #include <grub/err.h>
  22. #include <grub/types.h>
  23. #include <grub/mm.h>
  24. #include <grub/partition.h>
  25. #include <grub/disk.h>
  26. #include <grub/file.h>
  27. #include <grub/env.h>
  28. #include <grub/i18n.h>
  29. #include <grub/charset.h>
  30. #include <grub/safemath.h>
  31. static grub_uint32_t *kill_buf;
  32. static int hist_size;
  33. static grub_uint32_t **hist_lines = 0;
  34. static int hist_pos = 0;
  35. static int hist_end = 0;
  36. static int hist_used = 0;
  37. grub_err_t
  38. grub_set_history (int newsize)
  39. {
  40. grub_uint32_t **old_hist_lines = hist_lines;
  41. hist_lines = grub_calloc (newsize, sizeof (grub_uint32_t *));
  42. /* Copy the old lines into the new buffer. */
  43. if (old_hist_lines)
  44. {
  45. /* Remove the lines that don't fit in the new buffer. */
  46. if (newsize < hist_used)
  47. {
  48. grub_size_t i;
  49. grub_size_t delsize = hist_used - newsize;
  50. hist_used = newsize;
  51. for (i = 1; i < delsize + 1; i++)
  52. {
  53. grub_ssize_t pos = hist_end - i;
  54. if (pos < 0)
  55. pos += hist_size;
  56. grub_free (old_hist_lines[pos]);
  57. }
  58. hist_end -= delsize;
  59. if (hist_end < 0)
  60. hist_end += hist_size;
  61. }
  62. if (hist_pos < hist_end)
  63. grub_memmove (hist_lines, old_hist_lines + hist_pos,
  64. (hist_end - hist_pos) * sizeof (grub_uint32_t *));
  65. else if (hist_used)
  66. {
  67. /* Copy the older part. */
  68. grub_memmove (hist_lines, old_hist_lines + hist_pos,
  69. (hist_size - hist_pos) * sizeof (grub_uint32_t *));
  70. /* Copy the newer part. */
  71. grub_memmove (hist_lines + hist_size - hist_pos, old_hist_lines,
  72. hist_end * sizeof (grub_uint32_t *));
  73. }
  74. }
  75. grub_free (old_hist_lines);
  76. hist_size = newsize;
  77. hist_pos = 0;
  78. hist_end = hist_used;
  79. return 0;
  80. }
  81. /* Get the entry POS from the history where `0' is the newest
  82. entry. */
  83. static grub_uint32_t *
  84. grub_history_get (unsigned pos)
  85. {
  86. pos = (hist_pos + pos) % hist_size;
  87. return hist_lines[pos];
  88. }
  89. static grub_size_t
  90. strlen_ucs4 (const grub_uint32_t *s)
  91. {
  92. const grub_uint32_t *p = s;
  93. while (*p)
  94. p++;
  95. return p - s;
  96. }
  97. /* Replace the history entry on position POS with the string S. */
  98. static void
  99. grub_history_set (int pos, grub_uint32_t *s, grub_size_t len)
  100. {
  101. grub_free (hist_lines[pos]);
  102. hist_lines[pos] = grub_calloc (len + 1, sizeof (grub_uint32_t));
  103. if (!hist_lines[pos])
  104. {
  105. grub_print_error ();
  106. grub_errno = GRUB_ERR_NONE;
  107. return ;
  108. }
  109. grub_memcpy (hist_lines[pos], s, len * sizeof (grub_uint32_t));
  110. hist_lines[pos][len] = 0;
  111. }
  112. /* Insert a new history line S on the top of the history. */
  113. static void
  114. grub_history_add (grub_uint32_t *s, grub_size_t len)
  115. {
  116. /* Remove the oldest entry in the history to make room for a new
  117. entry. */
  118. if (hist_used + 1 > hist_size)
  119. {
  120. hist_end--;
  121. if (hist_end < 0)
  122. hist_end = hist_size + hist_end;
  123. grub_free (hist_lines[hist_end]);
  124. }
  125. else
  126. hist_used++;
  127. /* Move to the next position. */
  128. hist_pos--;
  129. if (hist_pos < 0)
  130. hist_pos = hist_size + hist_pos;
  131. /* Insert into history. */
  132. hist_lines[hist_pos] = NULL;
  133. grub_history_set (hist_pos, s, len);
  134. }
  135. /* Replace the history entry on position POS with the string S. */
  136. static void
  137. grub_history_replace (unsigned pos, grub_uint32_t *s, grub_size_t len)
  138. {
  139. grub_history_set ((hist_pos + pos) % hist_size, s, len);
  140. }
  141. /* A completion hook to print items. */
  142. static void
  143. print_completion (const char *item, grub_completion_type_t type, int count)
  144. {
  145. if (count == 0)
  146. {
  147. /* If this is the first time, print a label. */
  148. grub_puts ("");
  149. switch (type)
  150. {
  151. case GRUB_COMPLETION_TYPE_COMMAND:
  152. grub_puts_ (N_("Possible commands are:"));
  153. break;
  154. case GRUB_COMPLETION_TYPE_DEVICE:
  155. grub_puts_ (N_("Possible devices are:"));
  156. break;
  157. case GRUB_COMPLETION_TYPE_FILE:
  158. grub_puts_ (N_("Possible files are:"));
  159. break;
  160. case GRUB_COMPLETION_TYPE_PARTITION:
  161. grub_puts_ (N_("Possible partitions are:"));
  162. break;
  163. case GRUB_COMPLETION_TYPE_ARGUMENT:
  164. grub_puts_ (N_("Possible arguments are:"));
  165. break;
  166. default:
  167. /* TRANSLATORS: this message is used if none of above matches.
  168. This shouldn't happen but please use the general term for
  169. "thing" or "object". */
  170. grub_puts_ (N_("Possible things are:"));
  171. break;
  172. }
  173. grub_puts ("");
  174. }
  175. if (type == GRUB_COMPLETION_TYPE_PARTITION)
  176. {
  177. grub_normal_print_device_info (item);
  178. grub_errno = GRUB_ERR_NONE;
  179. }
  180. else
  181. grub_printf (" %s", item);
  182. }
  183. struct cmdline_term
  184. {
  185. struct grub_term_coordinate pos;
  186. unsigned ystart, width, height;
  187. unsigned prompt_len;
  188. struct grub_term_output *term;
  189. };
  190. static inline void
  191. cl_set_pos (struct cmdline_term *cl_term, grub_size_t lpos)
  192. {
  193. cl_term->pos.x = (cl_term->prompt_len + lpos) % cl_term->width;
  194. cl_term->pos.y = cl_term->ystart
  195. + (cl_term->prompt_len + lpos) / cl_term->width;
  196. if (cl_term->pos.y >= cl_term->height)
  197. cl_term->pos.y = cl_term->height - 1;
  198. grub_term_gotoxy (cl_term->term, cl_term->pos);
  199. }
  200. static void
  201. cl_set_pos_all (struct cmdline_term *cl_terms, unsigned nterms,
  202. grub_size_t lpos)
  203. {
  204. unsigned i;
  205. for (i = 0; i < nterms; i++)
  206. cl_set_pos (&cl_terms[i], lpos);
  207. }
  208. static inline void __attribute__ ((always_inline))
  209. cl_print (struct cmdline_term *cl_term, grub_uint32_t c,
  210. grub_uint32_t *start, grub_uint32_t *end)
  211. {
  212. grub_uint32_t *p;
  213. for (p = start; p < end; p++)
  214. {
  215. if (c)
  216. grub_putcode (c, cl_term->term);
  217. else
  218. grub_putcode (*p, cl_term->term);
  219. cl_term->pos.x++;
  220. if (cl_term->pos.x >= cl_term->width - 1)
  221. {
  222. cl_term->pos.x = 0;
  223. if (cl_term->pos.y >= (unsigned) (cl_term->height - 1))
  224. {
  225. if (cl_term->ystart > 0)
  226. cl_term->ystart--;
  227. }
  228. else
  229. cl_term->pos.y++;
  230. grub_putcode ('\n', cl_term->term);
  231. }
  232. }
  233. }
  234. static void
  235. cl_print_all (struct cmdline_term *cl_terms, unsigned nterms,
  236. grub_uint32_t c, grub_uint32_t *start, grub_uint32_t *end)
  237. {
  238. unsigned i;
  239. for (i = 0; i < nterms; i++)
  240. cl_print (&cl_terms[i], c, start, end);
  241. }
  242. static void
  243. init_clterm (struct cmdline_term *cl_term_cur)
  244. {
  245. cl_term_cur->pos.x = cl_term_cur->prompt_len;
  246. cl_term_cur->pos.y = grub_term_getxy (cl_term_cur->term).y;
  247. cl_term_cur->ystart = cl_term_cur->pos.y;
  248. cl_term_cur->width = grub_term_width (cl_term_cur->term);
  249. cl_term_cur->height = grub_term_height (cl_term_cur->term);
  250. }
  251. static void
  252. cl_delete (struct cmdline_term *cl_terms, unsigned nterms,
  253. grub_uint32_t *buf,
  254. grub_size_t lpos, grub_size_t *llen, unsigned len)
  255. {
  256. if (lpos + len <= (*llen))
  257. {
  258. cl_set_pos_all (cl_terms, nterms, (*llen) - len);
  259. cl_print_all (cl_terms, nterms, ' ', buf + (*llen) - len, buf + (*llen));
  260. cl_set_pos_all (cl_terms, nterms, lpos);
  261. grub_memmove (buf + lpos, buf + lpos + len,
  262. sizeof (grub_uint32_t) * ((*llen) - lpos + 1));
  263. (*llen) -= len;
  264. cl_print_all (cl_terms, nterms, 0, buf + lpos, buf + (*llen));
  265. cl_set_pos_all (cl_terms, nterms, lpos);
  266. }
  267. }
  268. static void
  269. cl_insert (struct cmdline_term *cl_terms, unsigned nterms,
  270. grub_size_t *lpos, grub_size_t *llen,
  271. grub_size_t *max_len, grub_uint32_t **buf,
  272. const grub_uint32_t *str)
  273. {
  274. grub_size_t len = strlen_ucs4 (str);
  275. if (len + (*llen) >= (*max_len))
  276. {
  277. grub_uint32_t *nbuf;
  278. grub_size_t sz;
  279. if (grub_mul (*max_len, 2, max_len) ||
  280. grub_mul (*max_len, sizeof (grub_uint32_t), &sz))
  281. {
  282. grub_errno = GRUB_ERR_OUT_OF_RANGE;
  283. goto fail;
  284. }
  285. nbuf = grub_realloc ((*buf), sz);
  286. if (nbuf)
  287. (*buf) = nbuf;
  288. else
  289. {
  290. fail:
  291. grub_print_error ();
  292. grub_errno = GRUB_ERR_NONE;
  293. (*max_len) /= 2;
  294. }
  295. }
  296. if (len + (*llen) < (*max_len))
  297. {
  298. grub_memmove ((*buf) + (*lpos) + len, (*buf) + (*lpos),
  299. ((*llen) - (*lpos) + 1) * sizeof (grub_uint32_t));
  300. grub_memmove ((*buf) + (*lpos), str, len * sizeof (grub_uint32_t));
  301. (*llen) += len;
  302. cl_set_pos_all (cl_terms, nterms, (*lpos));
  303. cl_print_all (cl_terms, nterms, 0, *buf + (*lpos), *buf + (*llen));
  304. (*lpos) += len;
  305. cl_set_pos_all (cl_terms, nterms, (*lpos));
  306. }
  307. }
  308. /* Get a command-line. If ESC is pushed, return zero,
  309. otherwise return command line. */
  310. /* FIXME: The dumb interface is not supported yet. */
  311. char *
  312. grub_cmdline_get (const char *prompt_translated)
  313. {
  314. grub_size_t lpos, llen;
  315. grub_uint32_t *buf;
  316. grub_size_t max_len = 256;
  317. int key;
  318. int histpos = 0;
  319. struct cmdline_term *cl_terms;
  320. char *ret;
  321. unsigned nterms;
  322. buf = grub_calloc (max_len, sizeof (grub_uint32_t));
  323. if (!buf)
  324. return 0;
  325. lpos = llen = 0;
  326. buf[0] = '\0';
  327. {
  328. grub_term_output_t term;
  329. FOR_ACTIVE_TERM_OUTPUTS(term)
  330. if ((grub_term_getxy (term).x) != 0)
  331. grub_putcode ('\n', term);
  332. }
  333. grub_xputs (prompt_translated);
  334. grub_xputs (" ");
  335. grub_normal_reset_more ();
  336. {
  337. struct cmdline_term *cl_term_cur;
  338. struct grub_term_output *cur;
  339. grub_uint32_t *unicode_msg;
  340. grub_size_t msg_len = grub_strlen (prompt_translated) + 3;
  341. nterms = 0;
  342. FOR_ACTIVE_TERM_OUTPUTS(cur)
  343. nterms++;
  344. cl_terms = grub_calloc (nterms, sizeof (cl_terms[0]));
  345. if (!cl_terms)
  346. {
  347. grub_free (buf);
  348. return 0;
  349. }
  350. cl_term_cur = cl_terms;
  351. unicode_msg = grub_calloc (msg_len, sizeof (grub_uint32_t));
  352. if (!unicode_msg)
  353. {
  354. grub_free (buf);
  355. grub_free (cl_terms);
  356. return 0;
  357. }
  358. msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len - 1,
  359. (grub_uint8_t *) prompt_translated, -1, 0);
  360. unicode_msg[msg_len++] = ' ';
  361. FOR_ACTIVE_TERM_OUTPUTS(cur)
  362. {
  363. cl_term_cur->term = cur;
  364. cl_term_cur->prompt_len = grub_getstringwidth (unicode_msg,
  365. unicode_msg + msg_len,
  366. cur);
  367. init_clterm (cl_term_cur);
  368. cl_term_cur++;
  369. }
  370. grub_free (unicode_msg);
  371. }
  372. if (hist_used == 0)
  373. grub_history_add (buf, llen);
  374. grub_refresh ();
  375. while ((key = grub_getkey ()) != '\n' && key != '\r')
  376. {
  377. switch (key)
  378. {
  379. case GRUB_TERM_CTRL | 'a':
  380. case GRUB_TERM_KEY_HOME:
  381. lpos = 0;
  382. cl_set_pos_all (cl_terms, nterms, lpos);
  383. break;
  384. case GRUB_TERM_CTRL | 'b':
  385. case GRUB_TERM_KEY_LEFT:
  386. if (lpos > 0)
  387. {
  388. lpos--;
  389. cl_set_pos_all (cl_terms, nterms, lpos);
  390. }
  391. break;
  392. case GRUB_TERM_CTRL | 'e':
  393. case GRUB_TERM_KEY_END:
  394. lpos = llen;
  395. cl_set_pos_all (cl_terms, nterms, lpos);
  396. break;
  397. case GRUB_TERM_CTRL | 'f':
  398. case GRUB_TERM_KEY_RIGHT:
  399. if (lpos < llen)
  400. {
  401. lpos++;
  402. cl_set_pos_all (cl_terms, nterms, lpos);
  403. }
  404. break;
  405. case GRUB_TERM_CTRL | 'i':
  406. case '\t':
  407. {
  408. int restore;
  409. char *insertu8;
  410. char *bufu8;
  411. grub_uint32_t c;
  412. c = buf[lpos];
  413. buf[lpos] = '\0';
  414. bufu8 = grub_ucs4_to_utf8_alloc (buf, lpos);
  415. buf[lpos] = c;
  416. if (!bufu8)
  417. {
  418. grub_print_error ();
  419. grub_errno = GRUB_ERR_NONE;
  420. break;
  421. }
  422. insertu8 = grub_normal_do_completion (bufu8, &restore,
  423. print_completion);
  424. grub_free (bufu8);
  425. grub_normal_reset_more ();
  426. if (restore)
  427. {
  428. unsigned i;
  429. /* Restore the prompt. */
  430. grub_xputs ("\n");
  431. grub_xputs (prompt_translated);
  432. grub_xputs (" ");
  433. for (i = 0; i < nterms; i++)
  434. init_clterm (&cl_terms[i]);
  435. cl_print_all (cl_terms, nterms, 0, buf, buf + llen);
  436. }
  437. if (insertu8)
  438. {
  439. grub_size_t insertlen;
  440. grub_ssize_t t;
  441. grub_uint32_t *insert;
  442. insertlen = grub_strlen (insertu8);
  443. insert = grub_calloc (insertlen + 1, sizeof (grub_uint32_t));
  444. if (!insert)
  445. {
  446. grub_free (insertu8);
  447. grub_print_error ();
  448. grub_errno = GRUB_ERR_NONE;
  449. break;
  450. }
  451. t = grub_utf8_to_ucs4 (insert, insertlen,
  452. (grub_uint8_t *) insertu8,
  453. insertlen, 0);
  454. if (t > 0)
  455. {
  456. if (insert[t-1] == ' ' && buf[lpos] == ' ')
  457. {
  458. insert[t-1] = 0;
  459. if (t != 1)
  460. cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, insert);
  461. lpos++;
  462. }
  463. else
  464. {
  465. insert[t] = 0;
  466. cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, insert);
  467. }
  468. }
  469. grub_free (insertu8);
  470. grub_free (insert);
  471. }
  472. cl_set_pos_all (cl_terms, nterms, lpos);
  473. }
  474. break;
  475. case GRUB_TERM_CTRL | 'k':
  476. if (lpos < llen)
  477. {
  478. grub_free (kill_buf);
  479. kill_buf = grub_malloc ((llen - lpos + 1)
  480. * sizeof (grub_uint32_t));
  481. if (grub_errno)
  482. {
  483. grub_print_error ();
  484. grub_errno = GRUB_ERR_NONE;
  485. }
  486. else
  487. {
  488. grub_memcpy (kill_buf, buf + lpos,
  489. (llen - lpos + 1) * sizeof (grub_uint32_t));
  490. kill_buf[llen - lpos] = 0;
  491. }
  492. cl_delete (cl_terms, nterms,
  493. buf, lpos, &llen, llen - lpos);
  494. }
  495. break;
  496. case GRUB_TERM_CTRL | 'n':
  497. case GRUB_TERM_KEY_DOWN:
  498. {
  499. grub_uint32_t *hist;
  500. lpos = 0;
  501. if (histpos > 0)
  502. {
  503. grub_history_replace (histpos, buf, llen);
  504. histpos--;
  505. }
  506. cl_delete (cl_terms, nterms,
  507. buf, lpos, &llen, llen);
  508. hist = grub_history_get (histpos);
  509. cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, hist);
  510. break;
  511. }
  512. case GRUB_TERM_KEY_UP:
  513. case GRUB_TERM_CTRL | 'p':
  514. {
  515. grub_uint32_t *hist;
  516. lpos = 0;
  517. if (histpos < hist_used - 1)
  518. {
  519. grub_history_replace (histpos, buf, llen);
  520. histpos++;
  521. }
  522. cl_delete (cl_terms, nterms,
  523. buf, lpos, &llen, llen);
  524. hist = grub_history_get (histpos);
  525. cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, hist);
  526. }
  527. break;
  528. case GRUB_TERM_CTRL | 'u':
  529. if (lpos > 0)
  530. {
  531. grub_size_t n = lpos;
  532. grub_free (kill_buf);
  533. kill_buf = grub_calloc (n + 1, sizeof (grub_uint32_t));
  534. if (grub_errno)
  535. {
  536. grub_print_error ();
  537. grub_errno = GRUB_ERR_NONE;
  538. }
  539. if (kill_buf)
  540. {
  541. grub_memcpy (kill_buf, buf, n * sizeof(grub_uint32_t));
  542. kill_buf[n] = 0;
  543. }
  544. lpos = 0;
  545. cl_set_pos_all (cl_terms, nterms, lpos);
  546. cl_delete (cl_terms, nterms,
  547. buf, lpos, &llen, n);
  548. }
  549. break;
  550. case GRUB_TERM_CTRL | 'y':
  551. if (kill_buf)
  552. cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, kill_buf);
  553. break;
  554. case GRUB_TERM_ESC:
  555. grub_free (cl_terms);
  556. grub_free (buf);
  557. return 0;
  558. case GRUB_TERM_BACKSPACE:
  559. if (lpos > 0)
  560. {
  561. lpos--;
  562. cl_set_pos_all (cl_terms, nterms, lpos);
  563. }
  564. else
  565. break;
  566. /* fall through */
  567. case GRUB_TERM_CTRL | 'd':
  568. case GRUB_TERM_KEY_DC:
  569. if (lpos < llen)
  570. cl_delete (cl_terms, nterms,
  571. buf, lpos, &llen, 1);
  572. break;
  573. default:
  574. if (grub_isprint (key))
  575. {
  576. grub_uint32_t str[2];
  577. str[0] = key;
  578. str[1] = '\0';
  579. cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, str);
  580. }
  581. break;
  582. }
  583. grub_refresh ();
  584. }
  585. grub_xputs ("\n");
  586. grub_refresh ();
  587. histpos = 0;
  588. if (strlen_ucs4 (buf) > 0)
  589. {
  590. grub_uint32_t empty[] = { 0 };
  591. grub_history_replace (histpos, buf, llen);
  592. grub_history_add (empty, 0);
  593. }
  594. ret = grub_ucs4_to_utf8_alloc (buf, llen + 1);
  595. grub_free (buf);
  596. grub_free (cl_terms);
  597. return ret;
  598. }