3f05d693d1
This attempts to fix the places where we do the following where arithmetic_expr may include unvalidated data: X = grub_malloc(arithmetic_expr); It accomplishes this by doing the arithmetic ahead of time using grub_add(), grub_sub(), grub_mul() and testing for overflow before proceeding. Among other issues, this fixes: - allocation of integer overflow in grub_video_bitmap_create() reported by Chris Coulson, - allocation of integer overflow in grub_png_decode_image_header() reported by Chris Coulson, - allocation of integer overflow in grub_squash_read_symlink() reported by Chris Coulson, - allocation of integer overflow in grub_ext2_read_symlink() reported by Chris Coulson, - allocation of integer overflow in read_section_as_string() reported by Chris Coulson. Fixes: CVE-2020-14309, CVE-2020-14310, CVE-2020-14311 Signed-off-by: Peter Jones <pjones@redhat.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
1460 lines
33 KiB
C
1460 lines
33 KiB
C
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2005,2006,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/normal.h>
|
|
#include <grub/term.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/loader.h>
|
|
#include <grub/command.h>
|
|
#include <grub/parser.h>
|
|
#include <grub/script_sh.h>
|
|
#include <grub/auth.h>
|
|
#include <grub/i18n.h>
|
|
#include <grub/charset.h>
|
|
#include <grub/safemath.h>
|
|
|
|
enum update_mode
|
|
{
|
|
NO_LINE,
|
|
SINGLE_LINE,
|
|
ALL_LINES
|
|
};
|
|
|
|
struct line
|
|
{
|
|
/* The line buffer. */
|
|
grub_uint32_t *buf;
|
|
/* The length of the line. */
|
|
int len;
|
|
/* The maximum length of the line. */
|
|
int max_len;
|
|
struct grub_term_pos **pos;
|
|
};
|
|
|
|
struct per_term_screen
|
|
{
|
|
struct grub_term_output *term;
|
|
int y_line_start;
|
|
struct grub_term_screen_geometry geo;
|
|
/* Scratch variables used when updating. Having them here avoids
|
|
loads of small mallocs. */
|
|
int orig_num;
|
|
int down;
|
|
enum update_mode mode;
|
|
};
|
|
|
|
struct screen
|
|
{
|
|
/* The array of lines. */
|
|
struct line *lines;
|
|
/* The number of lines. */
|
|
int num_lines;
|
|
/* The current column. */
|
|
int column;
|
|
/* The real column. */
|
|
int real_column;
|
|
/* The current line. */
|
|
int line;
|
|
/* The kill buffer. */
|
|
char *killed_text;
|
|
/* The flag of a completion window. */
|
|
int completion_shown;
|
|
|
|
int submenu;
|
|
|
|
struct per_term_screen *terms;
|
|
unsigned nterms;
|
|
};
|
|
|
|
/* Used for storing completion items temporarily. */
|
|
static struct {
|
|
char *buf;
|
|
grub_size_t len;
|
|
grub_size_t max_len;
|
|
} completion_buffer;
|
|
static int completion_type;
|
|
|
|
/* Initialize a line. */
|
|
static int
|
|
init_line (struct screen *screen, struct line *linep)
|
|
{
|
|
linep->len = 0;
|
|
linep->max_len = 80;
|
|
linep->buf = grub_calloc (linep->max_len + 1, sizeof (linep->buf[0]));
|
|
linep->pos = grub_calloc (screen->nterms, sizeof (linep->pos[0]));
|
|
if (! linep->buf || !linep->pos)
|
|
{
|
|
grub_free (linep->buf);
|
|
grub_free (linep->pos);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Allocate extra space if necessary. */
|
|
static int
|
|
ensure_space (struct line *linep, int extra)
|
|
{
|
|
if (linep->max_len < linep->len + extra)
|
|
{
|
|
grub_size_t sz0, sz1;
|
|
|
|
if (grub_add (linep->len, extra, &sz0) ||
|
|
grub_mul (sz0, 2, &sz0) ||
|
|
grub_add (sz0, 1, &sz1) ||
|
|
grub_mul (sz1, sizeof (linep->buf[0]), &sz1))
|
|
return 0;
|
|
|
|
linep->buf = grub_realloc (linep->buf, sz1);
|
|
if (! linep->buf)
|
|
return 0;
|
|
linep->max_len = sz0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Return the number of lines occupied by this line on the screen. */
|
|
static int
|
|
get_logical_num_lines (struct line *linep, struct per_term_screen *term_screen)
|
|
{
|
|
grub_size_t width = grub_getstringwidth (linep->buf, linep->buf + linep->len,
|
|
term_screen->term);
|
|
|
|
/* Empty line still consumes space on screen */
|
|
return width ? (width + (unsigned) term_screen->geo.entry_width - 1) /
|
|
(unsigned) term_screen->geo.entry_width
|
|
: 1;
|
|
}
|
|
|
|
static void
|
|
advance_to (struct screen *screen, int c)
|
|
{
|
|
if (c > screen->lines[screen->line].len)
|
|
c = screen->lines[screen->line].len;
|
|
|
|
screen->column = grub_unicode_get_comb_end (screen->lines[screen->line].buf
|
|
+ screen->lines[screen->line].len,
|
|
screen->lines[screen->line].buf
|
|
+ c)
|
|
- screen->lines[screen->line].buf;
|
|
}
|
|
|
|
/* Print an empty line. */
|
|
static void
|
|
print_empty_line (int y, struct per_term_screen *term_screen)
|
|
{
|
|
int i;
|
|
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { term_screen->geo.first_entry_x,
|
|
y + term_screen->geo.first_entry_y });
|
|
|
|
for (i = 0; i < term_screen->geo.entry_width + 1; i++)
|
|
grub_putcode (' ', term_screen->term);
|
|
}
|
|
|
|
static void
|
|
print_updown (int upflag, int downflag, struct per_term_screen *term_screen)
|
|
{
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { term_screen->geo.first_entry_x
|
|
+ term_screen->geo.entry_width + 1
|
|
+ term_screen->geo.border,
|
|
term_screen->geo.first_entry_y });
|
|
|
|
if (upflag && downflag)
|
|
grub_putcode (GRUB_UNICODE_UPDOWNARROW, term_screen->term);
|
|
else if (upflag)
|
|
grub_putcode (GRUB_UNICODE_UPARROW, term_screen->term);
|
|
else if (downflag)
|
|
grub_putcode (GRUB_UNICODE_DOWNARROW, term_screen->term);
|
|
else
|
|
grub_putcode (' ', term_screen->term);
|
|
}
|
|
|
|
/* Print an up arrow. */
|
|
static void
|
|
print_up (int flag, struct per_term_screen *term_screen)
|
|
{
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { term_screen->geo.first_entry_x
|
|
+ term_screen->geo.entry_width + 1
|
|
+ term_screen->geo.border,
|
|
term_screen->geo.first_entry_y });
|
|
|
|
if (flag)
|
|
grub_putcode (GRUB_UNICODE_UPARROW, term_screen->term);
|
|
else
|
|
grub_putcode (' ', term_screen->term);
|
|
}
|
|
|
|
/* Print a down arrow. */
|
|
static void
|
|
print_down (int flag, struct per_term_screen *term_screen)
|
|
{
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { term_screen->geo.first_entry_x
|
|
+ term_screen->geo.entry_width + 1
|
|
+ term_screen->geo.border,
|
|
term_screen->geo.first_entry_y
|
|
+ term_screen->geo.num_entries - 1 });
|
|
|
|
if (flag)
|
|
grub_putcode (GRUB_UNICODE_DOWNARROW, term_screen->term);
|
|
else
|
|
grub_putcode (' ', term_screen->term);
|
|
}
|
|
|
|
/* Draw the lines of the screen SCREEN. */
|
|
static void
|
|
update_screen (struct screen *screen, struct per_term_screen *term_screen,
|
|
int region_start, int region_column __attribute__ ((unused)),
|
|
int up, int down, enum update_mode mode)
|
|
{
|
|
int up_flag = 0;
|
|
int down_flag = 0;
|
|
int y;
|
|
int i;
|
|
struct line *linep;
|
|
|
|
y = term_screen->y_line_start;
|
|
linep = screen->lines;
|
|
|
|
for (i = 0; i < screen->line; i++, linep++)
|
|
y += get_logical_num_lines (linep, term_screen);
|
|
linep = screen->lines + screen->line;
|
|
grub_size_t t = grub_getstringwidth (linep->buf, linep->buf + screen->column,
|
|
term_screen->term);
|
|
y += t / (unsigned) term_screen->geo.entry_width;
|
|
if (t % (unsigned) term_screen->geo.entry_width == 0
|
|
&& t != 0 && screen->column == linep->len)
|
|
y--;
|
|
/* Check if scrolling is necessary. */
|
|
if (y < 0 || y >= term_screen->geo.num_entries)
|
|
{
|
|
int delta;
|
|
if (y < 0)
|
|
delta = -y;
|
|
else
|
|
delta = term_screen->geo.num_entries - 1 - y;
|
|
term_screen->y_line_start += delta;
|
|
|
|
region_start = 0;
|
|
up = 1;
|
|
down = 1;
|
|
mode = ALL_LINES;
|
|
}
|
|
|
|
grub_term_setcursor (term_screen->term, 0);
|
|
|
|
if (mode != NO_LINE)
|
|
{
|
|
/* Draw lines. This code is tricky, because this must calculate logical
|
|
positions. */
|
|
y = term_screen->y_line_start;
|
|
i = 0;
|
|
linep = screen->lines;
|
|
while (1)
|
|
{
|
|
int add;
|
|
add = get_logical_num_lines (linep, term_screen);
|
|
if (y + add > 0)
|
|
break;
|
|
i++;
|
|
linep++;
|
|
y += add;
|
|
}
|
|
|
|
if (y < 0 || i > 0)
|
|
up_flag = 1;
|
|
|
|
do
|
|
{
|
|
struct grub_term_pos **pos;
|
|
|
|
if (linep >= screen->lines + screen->num_lines)
|
|
break;
|
|
|
|
pos = linep->pos + (term_screen - screen->terms);
|
|
|
|
if (!*pos)
|
|
*pos = grub_calloc (linep->len + 1, sizeof (**pos));
|
|
|
|
if (i == region_start || linep == screen->lines + screen->line
|
|
|| (i > region_start && mode == ALL_LINES))
|
|
{
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { term_screen->geo.first_entry_x,
|
|
(y < 0 ? 0 : y)
|
|
+ term_screen->geo.first_entry_y });
|
|
|
|
grub_print_ucs4_menu (linep->buf,
|
|
linep->buf + linep->len,
|
|
term_screen->geo.first_entry_x,
|
|
term_screen->geo.right_margin,
|
|
term_screen->term,
|
|
(y < 0) ? -y : 0,
|
|
term_screen->geo.num_entries
|
|
- ((y > 0) ? y : 0), '\\',
|
|
*pos);
|
|
}
|
|
y += get_logical_num_lines (linep, term_screen);
|
|
if (y >= term_screen->geo.num_entries)
|
|
{
|
|
if (i + 1 < screen->num_lines)
|
|
down_flag = 1;
|
|
}
|
|
|
|
linep++;
|
|
i++;
|
|
|
|
if (mode == ALL_LINES && i == screen->num_lines)
|
|
for (; y < term_screen->geo.num_entries; y++)
|
|
print_empty_line (y, term_screen);
|
|
}
|
|
while (y < term_screen->geo.num_entries);
|
|
|
|
/* Draw up and down arrows. */
|
|
|
|
if (term_screen->geo.num_entries == 1)
|
|
{
|
|
if (up || down)
|
|
print_updown (up_flag, down_flag, term_screen);
|
|
}
|
|
else
|
|
{
|
|
if (up)
|
|
print_up (up_flag, term_screen);
|
|
if (down)
|
|
print_down (down_flag, term_screen);
|
|
}
|
|
}
|
|
|
|
/* Place the cursor. */
|
|
if (screen->lines[screen->line].pos[term_screen - screen->terms])
|
|
{
|
|
const struct grub_term_pos *cpos;
|
|
for (cpos = &(screen->lines[screen->line].pos[term_screen - screen->terms])[screen->column];
|
|
cpos >= &(screen->lines[screen->line].pos[term_screen - screen->terms])[0];
|
|
cpos--)
|
|
if (cpos->valid)
|
|
break;
|
|
y = term_screen->y_line_start;
|
|
for (i = 0; i < screen->line; i++)
|
|
y += get_logical_num_lines (screen->lines + i, term_screen);
|
|
if (cpos >= &(screen->lines[screen->line].pos[term_screen - screen->terms])[0])
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { cpos->x + term_screen->geo.first_entry_x,
|
|
cpos->y + y
|
|
+ term_screen->geo.first_entry_y });
|
|
else
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { term_screen->geo.first_entry_x,
|
|
y + term_screen->geo.first_entry_y });
|
|
|
|
}
|
|
|
|
grub_term_setcursor (term_screen->term, 1);
|
|
|
|
grub_term_refresh (term_screen->term);
|
|
}
|
|
|
|
static void
|
|
update_screen_all (struct screen *screen,
|
|
int region_start, int region_column,
|
|
int up, int down, enum update_mode mode)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < screen->nterms; i++)
|
|
update_screen (screen, &screen->terms[i], region_start, region_column,
|
|
up, down, mode);
|
|
}
|
|
|
|
static int
|
|
insert_string (struct screen *screen, const char *s, int update)
|
|
{
|
|
int region_start = screen->num_lines;
|
|
int region_column = 0;
|
|
unsigned i;
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
screen->terms[i].down = 0;
|
|
screen->terms[i].mode = NO_LINE;
|
|
}
|
|
|
|
while (*s)
|
|
{
|
|
if (*s == '\n')
|
|
{
|
|
/* LF is special because it creates a new line. */
|
|
struct line *current_linep;
|
|
struct line *next_linep;
|
|
int size;
|
|
|
|
/* Make a new line. */
|
|
screen->num_lines++;
|
|
screen->lines = grub_realloc (screen->lines,
|
|
screen->num_lines
|
|
* sizeof (screen->lines[0]));
|
|
if (! screen->lines)
|
|
return 0;
|
|
|
|
/* Shift down if not appending after the last line. */
|
|
if (screen->line < screen->num_lines - 2)
|
|
grub_memmove (screen->lines + screen->line + 2,
|
|
screen->lines + screen->line + 1,
|
|
((screen->num_lines - screen->line - 2)
|
|
* sizeof (struct line)));
|
|
|
|
if (! init_line (screen, screen->lines + screen->line + 1))
|
|
return 0;
|
|
|
|
/* Fold the line. */
|
|
current_linep = screen->lines + screen->line;
|
|
next_linep = current_linep + 1;
|
|
size = current_linep->len - screen->column;
|
|
|
|
if (! ensure_space (next_linep, size))
|
|
return 0;
|
|
|
|
grub_memmove (next_linep->buf,
|
|
current_linep->buf + screen->column,
|
|
size * sizeof (next_linep->buf[0]));
|
|
current_linep->len = screen->column;
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
grub_free (current_linep->pos[i]);
|
|
current_linep->pos[i] = 0;
|
|
}
|
|
next_linep->len = size;
|
|
|
|
/* Update a dirty region. */
|
|
if (region_start > screen->line)
|
|
{
|
|
region_start = screen->line;
|
|
region_column = screen->column;
|
|
}
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
screen->terms[i].mode = ALL_LINES;
|
|
screen->terms[i].down = 1; /* XXX not optimal. */
|
|
}
|
|
|
|
/* Move the cursor. */
|
|
screen->column = screen->real_column = 0;
|
|
screen->line++;
|
|
s++;
|
|
}
|
|
else
|
|
{
|
|
/* All but LF. */
|
|
const char *p;
|
|
struct line *current_linep;
|
|
int size;
|
|
grub_uint32_t *unicode_msg;
|
|
|
|
/* Find a string delimited by LF. */
|
|
p = grub_strchr (s, '\n');
|
|
if (! p)
|
|
p = s + grub_strlen (s);
|
|
|
|
/* Insert the string. */
|
|
current_linep = screen->lines + screen->line;
|
|
unicode_msg = grub_calloc (p - s, sizeof (grub_uint32_t));
|
|
|
|
if (!unicode_msg)
|
|
return 0;
|
|
|
|
size = grub_utf8_to_ucs4 (unicode_msg, (p - s),
|
|
(grub_uint8_t *) s, (p - s), 0);
|
|
|
|
if (! ensure_space (current_linep, size))
|
|
{
|
|
grub_free (unicode_msg);
|
|
return 0;
|
|
}
|
|
|
|
grub_memmove (current_linep->buf + screen->column + size,
|
|
current_linep->buf + screen->column,
|
|
(current_linep->len - screen->column)
|
|
* sizeof (current_linep->buf[0]));
|
|
grub_memmove (current_linep->buf + screen->column,
|
|
unicode_msg,
|
|
size * sizeof (current_linep->buf[0]));
|
|
grub_free (unicode_msg);
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
grub_free (current_linep->pos[i]);
|
|
current_linep->pos[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
screen->terms[i].orig_num = get_logical_num_lines (current_linep,
|
|
&screen->terms[i]);
|
|
current_linep->len += size;
|
|
|
|
/* Update the dirty region. */
|
|
if (region_start > screen->line)
|
|
{
|
|
region_start = screen->line;
|
|
region_column = screen->column;
|
|
}
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
int new_num = get_logical_num_lines (current_linep,
|
|
&screen->terms[i]);
|
|
if (screen->terms[i].orig_num != new_num)
|
|
{
|
|
screen->terms[i].mode = ALL_LINES;
|
|
screen->terms[i].down = 1; /* XXX not optimal. */
|
|
}
|
|
else if (screen->terms[i].mode != ALL_LINES)
|
|
screen->terms[i].mode = SINGLE_LINE;
|
|
}
|
|
|
|
/* Move the cursor. */
|
|
advance_to (screen, screen->column + size);
|
|
|
|
screen->real_column = screen->column;
|
|
s = p;
|
|
}
|
|
}
|
|
|
|
if (update)
|
|
for (i = 0; i < screen->nterms; i++)
|
|
update_screen (screen, &screen->terms[i],
|
|
region_start, region_column, 0, screen->terms[i].down,
|
|
screen->terms[i].mode);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Release the resource allocated for SCREEN. */
|
|
static void
|
|
destroy_screen (struct screen *screen)
|
|
{
|
|
int i;
|
|
|
|
if (screen->lines)
|
|
for (i = 0; i < screen->num_lines; i++)
|
|
{
|
|
struct line *linep = screen->lines + i;
|
|
|
|
if (linep)
|
|
{
|
|
unsigned j;
|
|
if (linep->pos)
|
|
for (j = 0; j < screen->nterms; j++)
|
|
grub_free (linep->pos[j]);
|
|
|
|
grub_free (linep->buf);
|
|
grub_free (linep->pos);
|
|
}
|
|
}
|
|
|
|
grub_free (screen->killed_text);
|
|
grub_free (screen->lines);
|
|
grub_free (screen->terms);
|
|
grub_free (screen);
|
|
}
|
|
|
|
/* Make a new screen. */
|
|
static struct screen *
|
|
make_screen (grub_menu_entry_t entry)
|
|
{
|
|
struct screen *screen;
|
|
unsigned i;
|
|
|
|
/* Initialize the screen. */
|
|
screen = grub_zalloc (sizeof (*screen));
|
|
if (! screen)
|
|
return 0;
|
|
|
|
screen->submenu = entry->submenu;
|
|
|
|
screen->num_lines = 1;
|
|
screen->lines = grub_malloc (sizeof (struct line));
|
|
if (! screen->lines)
|
|
goto fail;
|
|
|
|
/* Initialize the first line which must be always present. */
|
|
if (! init_line (screen, screen->lines))
|
|
goto fail;
|
|
|
|
insert_string (screen, (char *) entry->sourcecode, 0);
|
|
|
|
/* Reset the cursor position. */
|
|
screen->column = 0;
|
|
screen->real_column = 0;
|
|
screen->line = 0;
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
screen->terms[i].y_line_start = 0;
|
|
}
|
|
|
|
return screen;
|
|
|
|
fail:
|
|
destroy_screen (screen);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
forward_char (struct screen *screen, int update)
|
|
{
|
|
struct line *linep;
|
|
|
|
linep = screen->lines + screen->line;
|
|
if (screen->column < linep->len)
|
|
{
|
|
screen->column = grub_unicode_get_comb_end (screen->lines[screen->line].buf
|
|
+ screen->lines[screen->line].len,
|
|
screen->lines[screen->line].buf
|
|
+ screen->column + 1)
|
|
- screen->lines[screen->line].buf;
|
|
}
|
|
else if (screen->num_lines > screen->line + 1)
|
|
{
|
|
screen->column = 0;
|
|
screen->line++;
|
|
}
|
|
|
|
screen->real_column = screen->column;
|
|
|
|
if (update)
|
|
update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
backward_char (struct screen *screen, int update)
|
|
{
|
|
if (screen->column > 0)
|
|
{
|
|
struct grub_unicode_glyph glyph;
|
|
struct line *linep;
|
|
|
|
linep = screen->lines + screen->line;
|
|
|
|
screen->column--;
|
|
screen->column = grub_unicode_get_comb_start (linep->buf,
|
|
linep->buf + screen->column)
|
|
- linep->buf;
|
|
|
|
grub_unicode_aglomerate_comb (screen->lines[screen->line].buf + screen->column,
|
|
screen->lines[screen->line].len - screen->column,
|
|
&glyph);
|
|
screen->column = grub_unicode_get_comb_start (linep->buf,
|
|
linep->buf + screen->column)
|
|
- linep->buf;
|
|
|
|
grub_unicode_destroy_glyph (&glyph);
|
|
}
|
|
else if (screen->line > 0)
|
|
{
|
|
screen->line--;
|
|
screen->column = screen->lines[screen->line].len;
|
|
}
|
|
|
|
screen->real_column = screen->column;
|
|
|
|
if (update)
|
|
update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
previous_line (struct screen *screen, int update)
|
|
{
|
|
if (screen->line > 0)
|
|
{
|
|
struct line *linep;
|
|
int col;
|
|
|
|
screen->line--;
|
|
|
|
linep = screen->lines + screen->line;
|
|
if (linep->len < screen->real_column)
|
|
col = linep->len;
|
|
else
|
|
col = screen->real_column;
|
|
|
|
screen->column = 0;
|
|
advance_to (screen, col);
|
|
}
|
|
else
|
|
{
|
|
screen->column = 0;
|
|
}
|
|
|
|
if (update)
|
|
update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
next_line (struct screen *screen, int update)
|
|
{
|
|
if (screen->line < screen->num_lines - 1)
|
|
{
|
|
struct line *linep;
|
|
int c;
|
|
|
|
/* How many physical lines from the current position
|
|
to the last physical line? */
|
|
linep = screen->lines + screen->line;
|
|
|
|
screen->line++;
|
|
if ((linep + 1)->len < screen->real_column)
|
|
c = (linep + 1)->len;
|
|
else
|
|
c = screen->real_column;
|
|
screen->column = 0;
|
|
|
|
advance_to (screen, c);
|
|
}
|
|
else
|
|
advance_to (screen, screen->lines[screen->line].len);
|
|
|
|
if (update)
|
|
update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
beginning_of_line (struct screen *screen, int update)
|
|
{
|
|
screen->column = screen->real_column = 0;
|
|
|
|
if (update)
|
|
update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
end_of_line (struct screen *screen, int update)
|
|
{
|
|
advance_to (screen, screen->lines[screen->line].len);
|
|
|
|
if (update)
|
|
update_screen_all (screen, screen->num_lines, 0, 0, 0, NO_LINE);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
delete_char (struct screen *screen, int update)
|
|
{
|
|
struct line *linep;
|
|
int start = screen->num_lines;
|
|
int column = 0;
|
|
|
|
linep = screen->lines + screen->line;
|
|
if (linep->len > screen->column)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
screen->terms[i].orig_num = get_logical_num_lines (linep, &screen->terms[i]);
|
|
|
|
grub_memmove (linep->buf + screen->column,
|
|
linep->buf + screen->column + 1,
|
|
(linep->len - screen->column - 1)
|
|
* sizeof (linep->buf[0]));
|
|
linep->len--;
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
grub_free (linep->pos[i]);
|
|
linep->pos[i] = 0;
|
|
}
|
|
start = screen->line;
|
|
column = screen->column;
|
|
|
|
screen->real_column = screen->column;
|
|
|
|
if (update)
|
|
{
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
int new_num;
|
|
new_num = get_logical_num_lines (linep, &screen->terms[i]);
|
|
if (screen->terms[i].orig_num != new_num)
|
|
update_screen (screen, &screen->terms[i],
|
|
start, column, 0, 0, ALL_LINES);
|
|
else
|
|
update_screen (screen, &screen->terms[i],
|
|
start, column, 0, 0, SINGLE_LINE);
|
|
}
|
|
}
|
|
}
|
|
else if (screen->num_lines > screen->line + 1)
|
|
{
|
|
struct line *next_linep;
|
|
unsigned i;
|
|
|
|
next_linep = linep + 1;
|
|
if (! ensure_space (linep, next_linep->len))
|
|
return 0;
|
|
|
|
grub_memmove (linep->buf + linep->len, next_linep->buf,
|
|
next_linep->len * sizeof (linep->buf[0]));
|
|
linep->len += next_linep->len;
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
grub_free (linep->pos[i]);
|
|
linep->pos[i] = 0;
|
|
}
|
|
|
|
grub_free (next_linep->buf);
|
|
grub_memmove (next_linep,
|
|
next_linep + 1,
|
|
(screen->num_lines - screen->line - 2)
|
|
* sizeof (struct line));
|
|
screen->num_lines--;
|
|
|
|
start = screen->line;
|
|
column = screen->column;
|
|
|
|
screen->real_column = screen->column;
|
|
if (update)
|
|
update_screen_all (screen, start, column, 0, 1, ALL_LINES);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
backward_delete_char (struct screen *screen, int update)
|
|
{
|
|
int saved_column;
|
|
int saved_line;
|
|
|
|
saved_column = screen->column;
|
|
saved_line = screen->line;
|
|
|
|
if (! backward_char (screen, 0))
|
|
return 0;
|
|
|
|
if (saved_column != screen->column || saved_line != screen->line)
|
|
if (! delete_char (screen, update))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
kill_line (struct screen *screen, int continuous, int update)
|
|
{
|
|
struct line *linep;
|
|
char *p;
|
|
int size;
|
|
int offset;
|
|
|
|
p = screen->killed_text;
|
|
if (! continuous && p)
|
|
p[0] = '\0';
|
|
|
|
linep = screen->lines + screen->line;
|
|
size = linep->len - screen->column;
|
|
|
|
if (p)
|
|
offset = grub_strlen (p);
|
|
else
|
|
offset = 0;
|
|
|
|
if (size > 0)
|
|
{
|
|
unsigned i;
|
|
|
|
p = grub_realloc (p, offset + size + 1);
|
|
if (! p)
|
|
return 0;
|
|
|
|
grub_memmove (p + offset, linep->buf + screen->column, size);
|
|
p[offset + size] = '\0';
|
|
|
|
screen->killed_text = p;
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
screen->terms[i].orig_num = get_logical_num_lines (linep, &screen->terms[i]);
|
|
linep->len = screen->column;
|
|
|
|
if (update)
|
|
{
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
int new_num;
|
|
new_num = get_logical_num_lines (linep, &screen->terms[i]);
|
|
if (screen->terms[i].orig_num != new_num)
|
|
update_screen (screen, &screen->terms[i],
|
|
screen->line, screen->column, 0, 1, ALL_LINES);
|
|
else
|
|
update_screen (screen, &screen->terms[i],
|
|
screen->line, screen->column, 0, 0, SINGLE_LINE);
|
|
}
|
|
}
|
|
}
|
|
else if (screen->line + 1 < screen->num_lines)
|
|
{
|
|
p = grub_realloc (p, offset + 1 + 1);
|
|
if (! p)
|
|
return 0;
|
|
|
|
p[offset] = '\n';
|
|
p[offset + 1] = '\0';
|
|
|
|
screen->killed_text = p;
|
|
|
|
return delete_char (screen, update);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
yank (struct screen *screen, int update)
|
|
{
|
|
if (screen->killed_text)
|
|
return insert_string (screen, screen->killed_text, update);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
open_line (struct screen *screen, int update)
|
|
{
|
|
if (! insert_string (screen, "\n", 0))
|
|
return 0;
|
|
|
|
if (! backward_char (screen, 0))
|
|
return 0;
|
|
|
|
if (update)
|
|
update_screen_all (screen, screen->line, screen->column, 0, 1, ALL_LINES);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* A completion hook to print items. */
|
|
static void
|
|
store_completion (const char *item, grub_completion_type_t type,
|
|
int count __attribute__ ((unused)))
|
|
{
|
|
char *p;
|
|
|
|
completion_type = type;
|
|
|
|
/* Make sure that the completion buffer has enough room. */
|
|
if (completion_buffer.max_len < (completion_buffer.len
|
|
+ (int) grub_strlen (item) + 1 + 1))
|
|
{
|
|
grub_size_t new_len;
|
|
|
|
new_len = completion_buffer.len + grub_strlen (item) + 80;
|
|
p = grub_realloc (completion_buffer.buf, new_len);
|
|
if (! p)
|
|
{
|
|
/* Possibly not fatal. */
|
|
grub_errno = GRUB_ERR_NONE;
|
|
return;
|
|
}
|
|
p[completion_buffer.len] = 0;
|
|
completion_buffer.buf = p;
|
|
completion_buffer.max_len = new_len;
|
|
}
|
|
|
|
p = completion_buffer.buf + completion_buffer.len;
|
|
if (completion_buffer.len != 0)
|
|
{
|
|
*p++ = ' ';
|
|
completion_buffer.len++;
|
|
}
|
|
grub_strcpy (p, item);
|
|
completion_buffer.len += grub_strlen (item);
|
|
}
|
|
|
|
static int
|
|
complete (struct screen *screen, int continuous, int update)
|
|
{
|
|
struct line *linep;
|
|
int restore;
|
|
char *insert;
|
|
static int count = -1;
|
|
unsigned i;
|
|
grub_uint32_t *ucs4;
|
|
grub_size_t buflen;
|
|
grub_ssize_t ucs4len;
|
|
char *u8;
|
|
|
|
if (continuous)
|
|
count++;
|
|
else
|
|
count = 0;
|
|
|
|
completion_buffer.buf = 0;
|
|
completion_buffer.len = 0;
|
|
completion_buffer.max_len = 0;
|
|
|
|
linep = screen->lines + screen->line;
|
|
u8 = grub_ucs4_to_utf8_alloc (linep->buf, screen->column);
|
|
if (!u8)
|
|
return 1;
|
|
|
|
insert = grub_normal_do_completion (u8, &restore, store_completion);
|
|
|
|
if (completion_buffer.buf)
|
|
{
|
|
buflen = grub_strlen (completion_buffer.buf);
|
|
ucs4 = grub_calloc (buflen + 1, sizeof (grub_uint32_t));
|
|
|
|
if (!ucs4)
|
|
{
|
|
grub_print_error ();
|
|
grub_errno = GRUB_ERR_NONE;
|
|
return 1;
|
|
}
|
|
|
|
ucs4len = grub_utf8_to_ucs4 (ucs4, buflen,
|
|
(grub_uint8_t *) completion_buffer.buf,
|
|
buflen, 0);
|
|
ucs4[ucs4len] = 0;
|
|
|
|
if (restore)
|
|
for (i = 0; i < screen->nterms; i++)
|
|
{
|
|
unsigned width = grub_term_width (screen->terms[i].term);
|
|
if (width > 2)
|
|
width -= 2;
|
|
if (width > 15)
|
|
width -= 6;
|
|
unsigned num_sections = ((completion_buffer.len
|
|
+ width - 1)
|
|
/ width);
|
|
grub_uint32_t *endp;
|
|
struct grub_term_coordinate pos;
|
|
grub_uint32_t *p = ucs4;
|
|
|
|
pos = grub_term_getxy (screen->terms[i].term);
|
|
|
|
screen->completion_shown = 1;
|
|
|
|
grub_term_gotoxy (screen->terms[i].term,
|
|
(struct grub_term_coordinate) { 0,
|
|
screen->terms[i].geo.timeout_y });
|
|
if (screen->terms[i].geo.timeout_lines >= 2)
|
|
{
|
|
grub_puts_terminal (" ", screen->terms[i].term);
|
|
switch (completion_type)
|
|
{
|
|
case GRUB_COMPLETION_TYPE_COMMAND:
|
|
grub_puts_terminal (_("Possible commands are:"),
|
|
screen->terms[i].term);
|
|
break;
|
|
case GRUB_COMPLETION_TYPE_DEVICE:
|
|
grub_puts_terminal (_("Possible devices are:"),
|
|
screen->terms[i].term);
|
|
break;
|
|
case GRUB_COMPLETION_TYPE_FILE:
|
|
grub_puts_terminal (_("Possible files are:"),
|
|
screen->terms[i].term);
|
|
break;
|
|
case GRUB_COMPLETION_TYPE_PARTITION:
|
|
grub_puts_terminal (_("Possible partitions are:"),
|
|
screen->terms[i].term);
|
|
break;
|
|
case GRUB_COMPLETION_TYPE_ARGUMENT:
|
|
grub_puts_terminal (_("Possible arguments are:"),
|
|
screen->terms[i].term);
|
|
break;
|
|
default:
|
|
grub_puts_terminal (_("Possible things are:"),
|
|
screen->terms[i].term);
|
|
break;
|
|
}
|
|
|
|
grub_puts_terminal ("\n ", screen->terms[i].term);
|
|
}
|
|
|
|
p += ((unsigned) count % num_sections) * width;
|
|
endp = p + width;
|
|
|
|
if (p != ucs4)
|
|
grub_putcode (GRUB_UNICODE_LEFTARROW, screen->terms[i].term);
|
|
else
|
|
grub_putcode (' ', screen->terms[i].term);
|
|
|
|
grub_print_ucs4 (p, ucs4 + ucs4len < endp ? ucs4 + ucs4len : endp,
|
|
0, 0, screen->terms[i].term);
|
|
|
|
if (ucs4 + ucs4len > endp)
|
|
grub_putcode (GRUB_UNICODE_RIGHTARROW, screen->terms[i].term);
|
|
grub_term_gotoxy (screen->terms[i].term, pos);
|
|
}
|
|
}
|
|
|
|
if (insert)
|
|
{
|
|
insert_string (screen, insert, update);
|
|
count = -1;
|
|
grub_free (insert);
|
|
}
|
|
else if (update)
|
|
grub_refresh ();
|
|
|
|
grub_free (completion_buffer.buf);
|
|
return 1;
|
|
}
|
|
|
|
/* Clear displayed completions. */
|
|
static void
|
|
clear_completions (struct per_term_screen *term_screen)
|
|
{
|
|
struct grub_term_coordinate pos;
|
|
unsigned j;
|
|
int i;
|
|
|
|
pos = grub_term_getxy (term_screen->term);
|
|
grub_term_gotoxy (term_screen->term,
|
|
(struct grub_term_coordinate) { 0,
|
|
term_screen->geo.timeout_y });
|
|
|
|
for (i = 0; i < term_screen->geo.timeout_lines; i++)
|
|
{
|
|
for (j = 0; j < grub_term_width (term_screen->term) - 1; j++)
|
|
grub_putcode (' ', term_screen->term);
|
|
if (i + 1 < term_screen->geo.timeout_lines)
|
|
grub_putcode ('\n', term_screen->term);
|
|
}
|
|
|
|
grub_term_gotoxy (term_screen->term, pos);
|
|
grub_term_refresh (term_screen->term);
|
|
}
|
|
|
|
static void
|
|
clear_completions_all (struct screen *screen)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < screen->nterms; i++)
|
|
clear_completions (&screen->terms[i]);
|
|
}
|
|
|
|
/* Execute the command list in the screen SCREEN. */
|
|
static int
|
|
run (struct screen *screen)
|
|
{
|
|
char *script;
|
|
int errs_before;
|
|
grub_menu_t menu = NULL;
|
|
char *dummy[1] = { NULL };
|
|
|
|
grub_cls ();
|
|
grub_printf (" ");
|
|
grub_printf_ (N_("Booting a command list"));
|
|
grub_printf ("\n\n");
|
|
|
|
errs_before = grub_err_printed_errors;
|
|
|
|
if (screen->submenu)
|
|
{
|
|
grub_env_context_open ();
|
|
menu = grub_zalloc (sizeof (*menu));
|
|
if (! menu)
|
|
return 0;
|
|
grub_env_set_menu (menu);
|
|
}
|
|
|
|
/* Execute the script, line for line. */
|
|
{
|
|
int i;
|
|
grub_size_t size = 0, tot_size = 0;
|
|
|
|
for (i = 0; i < screen->num_lines; i++)
|
|
tot_size += grub_get_num_of_utf8_bytes (screen->lines[i].buf,
|
|
screen->lines[i].len) + 1;
|
|
|
|
script = grub_malloc (tot_size + 1);
|
|
if (! script)
|
|
return 0;
|
|
|
|
for (i = 0; i < screen->num_lines; i++)
|
|
{
|
|
size += grub_ucs4_to_utf8 (screen->lines[i].buf, screen->lines[i].len,
|
|
(grub_uint8_t *) script + size,
|
|
tot_size - size);
|
|
script[size++] = '\n';
|
|
}
|
|
script[size] = '\0';
|
|
}
|
|
grub_script_execute_new_scope (script, 0, dummy);
|
|
grub_free (script);
|
|
|
|
if (errs_before != grub_err_printed_errors)
|
|
grub_wait_after_message ();
|
|
|
|
if (grub_errno == GRUB_ERR_NONE && grub_loader_is_loaded ())
|
|
/* Implicit execution of boot, only if something is loaded. */
|
|
grub_command_execute ("boot", 0, 0);
|
|
|
|
if (screen->submenu)
|
|
{
|
|
if (menu && menu->size)
|
|
{
|
|
grub_show_menu (menu, 1, 0);
|
|
grub_normal_free_menu (menu);
|
|
}
|
|
grub_env_context_close ();
|
|
}
|
|
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
{
|
|
grub_print_error ();
|
|
grub_errno = GRUB_ERR_NONE;
|
|
grub_wait_after_message ();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Edit a menu entry with an Emacs-like interface. */
|
|
void
|
|
grub_menu_entry_run (grub_menu_entry_t entry)
|
|
{
|
|
struct screen *screen;
|
|
int prev_c;
|
|
grub_err_t err = GRUB_ERR_NONE;
|
|
unsigned i;
|
|
grub_term_output_t term;
|
|
|
|
err = grub_auth_check_authentication (NULL);
|
|
|
|
if (err)
|
|
{
|
|
grub_print_error ();
|
|
grub_errno = GRUB_ERR_NONE;
|
|
return;
|
|
}
|
|
|
|
screen = make_screen (entry);
|
|
if (! screen)
|
|
return;
|
|
|
|
screen->terms = NULL;
|
|
|
|
refresh:
|
|
grub_free (screen->terms);
|
|
screen->nterms = 0;
|
|
FOR_ACTIVE_TERM_OUTPUTS(term)
|
|
screen->nterms++;
|
|
|
|
for (i = 0; i < (unsigned) screen->num_lines; i++)
|
|
{
|
|
grub_free (screen->lines[i].pos);
|
|
screen->lines[i].pos = grub_calloc (screen->nterms, sizeof (screen->lines[i].pos[0]));
|
|
if (! screen->lines[i].pos)
|
|
{
|
|
grub_print_error ();
|
|
destroy_screen (screen);
|
|
grub_errno = GRUB_ERR_NONE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
screen->terms = grub_calloc (screen->nterms, sizeof (screen->terms[0]));
|
|
if (!screen->terms)
|
|
{
|
|
grub_print_error ();
|
|
destroy_screen (screen);
|
|
grub_errno = GRUB_ERR_NONE;
|
|
return;
|
|
}
|
|
i = 0;
|
|
FOR_ACTIVE_TERM_OUTPUTS(term)
|
|
{
|
|
screen->terms[i].term = term;
|
|
screen->terms[i].y_line_start = 0;
|
|
i++;
|
|
}
|
|
/* Draw the screen. */
|
|
for (i = 0; i < screen->nterms; i++)
|
|
grub_menu_init_page (0, 1, &screen->terms[i].geo,
|
|
screen->terms[i].term);
|
|
update_screen_all (screen, 0, 0, 1, 1, ALL_LINES);
|
|
for (i = 0; i < screen->nterms; i++)
|
|
grub_term_setcursor (screen->terms[i].term, 1);
|
|
prev_c = '\0';
|
|
|
|
while (1)
|
|
{
|
|
int c = grub_getkey ();
|
|
|
|
if (screen->completion_shown)
|
|
{
|
|
clear_completions_all (screen);
|
|
screen->completion_shown = 0;
|
|
}
|
|
|
|
if (grub_normal_exit_level)
|
|
{
|
|
destroy_screen (screen);
|
|
return;
|
|
}
|
|
|
|
switch (c)
|
|
{
|
|
case GRUB_TERM_KEY_UP:
|
|
case GRUB_TERM_CTRL | 'p':
|
|
if (! previous_line (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'n':
|
|
case GRUB_TERM_KEY_DOWN:
|
|
if (! next_line (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'f':
|
|
case GRUB_TERM_KEY_RIGHT:
|
|
if (! forward_char (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'b':
|
|
case GRUB_TERM_KEY_LEFT:
|
|
if (! backward_char (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'a':
|
|
case GRUB_TERM_KEY_HOME:
|
|
if (! beginning_of_line (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'e':
|
|
case GRUB_TERM_KEY_END:
|
|
if (! end_of_line (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'i':
|
|
case '\t':
|
|
if (! complete (screen, prev_c == c, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'd':
|
|
case GRUB_TERM_KEY_DC:
|
|
if (! delete_char (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'h':
|
|
case '\b':
|
|
if (! backward_delete_char (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'k':
|
|
if (! kill_line (screen, prev_c == c, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'u':
|
|
/* FIXME: What behavior is good for this key? */
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'y':
|
|
if (! yank (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_CTRL | 'l':
|
|
/* FIXME: centering. */
|
|
goto refresh;
|
|
|
|
case GRUB_TERM_CTRL | 'o':
|
|
if (! open_line (screen, 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case '\n':
|
|
case '\r':
|
|
if (! insert_string (screen, "\n", 1))
|
|
goto fail;
|
|
break;
|
|
|
|
case GRUB_TERM_ESC:
|
|
destroy_screen (screen);
|
|
return;
|
|
|
|
case GRUB_TERM_CTRL | 'c':
|
|
case GRUB_TERM_KEY_F2:
|
|
grub_cmdline_run (1, 0);
|
|
goto refresh;
|
|
|
|
case GRUB_TERM_CTRL | 'x':
|
|
case GRUB_TERM_KEY_F10:
|
|
run (screen);
|
|
goto refresh;
|
|
|
|
case GRUB_TERM_CTRL | 'r':
|
|
case GRUB_TERM_CTRL | 's':
|
|
case GRUB_TERM_CTRL | 't':
|
|
/* FIXME */
|
|
break;
|
|
|
|
default:
|
|
if (grub_isprint (c))
|
|
{
|
|
char buf[2];
|
|
|
|
buf[0] = c;
|
|
buf[1] = '\0';
|
|
if (! insert_string (screen, buf, 1))
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
|
|
prev_c = c;
|
|
}
|
|
|
|
fail:
|
|
destroy_screen (screen);
|
|
|
|
grub_cls ();
|
|
grub_print_error ();
|
|
grub_errno = GRUB_ERR_NONE;
|
|
grub_xputs ("\n");
|
|
grub_printf_ (N_("Press any key to continue..."));
|
|
(void) grub_getkey ();
|
|
}
|