grub/grub-core/normal/menu_entry.c
Michael Chang 13febd78db disk/cryptodisk: Require authentication after TPM unlock for CLI access
The GRUB may use TPM to verify the integrity of boot components and the
result can determine whether a previously sealed key can be released. If
everything checks out, showing nothing has been tampered with, the key
is released and GRUB unlocks the encrypted root partition for the next
stage of booting.

However, the liberal Command Line Interface (CLI) can be misused by
anyone in this case to access files in the encrypted partition one way
or another. Despite efforts to keep the CLI secure by preventing utility
command output from leaking file content, many techniques in the wild
could still be used to exploit the CLI, enabling attacks or learning
methods to attack. It's nearly impossible to account for all scenarios
where a hack could be applied.

Therefore, to mitigate potential misuse of the CLI after the root device
has been successfully unlocked via TPM, the user should be required to
authenticate using the LUKS password. This added layer of security
ensures that only authorized users can access the CLI reducing the risk
of exploitation or unauthorized access to the encrypted partition.

Fixes: CVE-2024-49504

Signed-off-by: Michael Chang <mchang@suse.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2025-01-23 16:22:47 +01:00

1465 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_ERR_NONE)
err = grub_auth_check_cli_access ();
if (err)
{
grub_print_error ();
grub_wait_after_message ();
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 ();
}