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