diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:54:16 +0000 |
commit | 485f6ecd453d8a2fd8b9b9fadea03159d8b50797 (patch) | |
tree | 32451fa3cdd9321fb2591fada9891b2cb70a9cd1 /grub-core/normal | |
parent | Initial commit. (diff) | |
download | grub2-485f6ecd453d8a2fd8b9b9fadea03159d8b50797.tar.xz grub2-485f6ecd453d8a2fd8b9b9fadea03159d8b50797.zip |
Adding upstream version 2.06.upstream/2.06upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'grub-core/normal')
-rw-r--r-- | grub-core/normal/auth.c | 278 | ||||
-rw-r--r-- | grub-core/normal/autofs.c | 148 | ||||
-rw-r--r-- | grub-core/normal/charset.c | 1312 | ||||
-rw-r--r-- | grub-core/normal/cmdline.c | 691 | ||||
-rw-r--r-- | grub-core/normal/color.c | 145 | ||||
-rw-r--r-- | grub-core/normal/completion.c | 526 | ||||
-rw-r--r-- | grub-core/normal/context.c | 214 | ||||
-rw-r--r-- | grub-core/normal/crypto.c | 163 | ||||
-rw-r--r-- | grub-core/normal/dyncmd.c | 210 | ||||
-rw-r--r-- | grub-core/normal/main.c | 587 | ||||
-rw-r--r-- | grub-core/normal/menu.c | 912 | ||||
-rw-r--r-- | grub-core/normal/menu_entry.c | 1460 | ||||
-rw-r--r-- | grub-core/normal/menu_text.c | 602 | ||||
-rw-r--r-- | grub-core/normal/misc.c | 194 | ||||
-rw-r--r-- | grub-core/normal/term.c | 1098 |
15 files changed, 8540 insertions, 0 deletions
diff --git a/grub-core/normal/auth.c b/grub-core/normal/auth.c new file mode 100644 index 0000000..6be678c --- /dev/null +++ b/grub-core/normal/auth.c @@ -0,0 +1,278 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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/auth.h> +#include <grub/list.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/env.h> +#include <grub/normal.h> +#include <grub/time.h> +#include <grub/i18n.h> + +struct grub_auth_user +{ + struct grub_auth_user *next; + struct grub_auth_user **prev; + char *name; + grub_auth_callback_t callback; + void *arg; + int authenticated; +}; + +static struct grub_auth_user *users = NULL; + +grub_err_t +grub_auth_register_authentication (const char *user, + grub_auth_callback_t callback, + void *arg) +{ + struct grub_auth_user *cur; + + cur = grub_named_list_find (GRUB_AS_NAMED_LIST (users), user); + if (!cur) + cur = grub_zalloc (sizeof (*cur)); + if (!cur) + return grub_errno; + cur->callback = callback; + cur->arg = arg; + if (! cur->name) + { + cur->name = grub_strdup (user); + if (!cur->name) + { + grub_free (cur); + return grub_errno; + } + grub_list_push (GRUB_AS_LIST_P (&users), GRUB_AS_LIST (cur)); + } + return GRUB_ERR_NONE; +} + +grub_err_t +grub_auth_unregister_authentication (const char *user) +{ + struct grub_auth_user *cur; + cur = grub_named_list_find (GRUB_AS_NAMED_LIST (users), user); + if (!cur) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "user '%s' not found", user); + if (!cur->authenticated) + { + grub_free (cur->name); + grub_list_remove (GRUB_AS_LIST (cur)); + grub_free (cur); + } + else + { + cur->callback = NULL; + cur->arg = NULL; + } + return GRUB_ERR_NONE; +} + +grub_err_t +grub_auth_authenticate (const char *user) +{ + struct grub_auth_user *cur; + + cur = grub_named_list_find (GRUB_AS_NAMED_LIST (users), user); + if (!cur) + cur = grub_zalloc (sizeof (*cur)); + if (!cur) + return grub_errno; + + cur->authenticated = 1; + + if (! cur->name) + { + cur->name = grub_strdup (user); + if (!cur->name) + { + grub_free (cur); + return grub_errno; + } + grub_list_push (GRUB_AS_LIST_P (&users), GRUB_AS_LIST (cur)); + } + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_auth_deauthenticate (const char *user) +{ + struct grub_auth_user *cur; + cur = grub_named_list_find (GRUB_AS_NAMED_LIST (users), user); + if (!cur) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "user '%s' not found", user); + if (!cur->callback) + { + grub_free (cur->name); + grub_list_remove (GRUB_AS_LIST (cur)); + grub_free (cur); + } + else + cur->authenticated = 0; + return GRUB_ERR_NONE; +} + +static int +is_authenticated (const char *userlist) +{ + const char *superusers; + struct grub_auth_user *user; + + superusers = grub_env_get ("superusers"); + + if (!superusers) + return 1; + + FOR_LIST_ELEMENTS (user, users) + { + if (!(user->authenticated)) + continue; + + if ((userlist && grub_strword (userlist, user->name)) + || grub_strword (superusers, user->name)) + return 1; + } + + return 0; +} + +static int +grub_username_get (char buf[], unsigned buf_size) +{ + unsigned cur_len = 0; + int key; + + while (1) + { + key = grub_getkey (); + if (key == '\n' || key == '\r') + break; + + if (key == GRUB_TERM_ESC) + { + cur_len = 0; + break; + } + + if (key == GRUB_TERM_BACKSPACE) + { + if (cur_len) + { + cur_len--; + grub_printf ("\b \b"); + } + continue; + } + + if (!grub_isprint (key)) + continue; + + if (cur_len + 2 < buf_size) + { + buf[cur_len++] = key; + grub_printf ("%c", key); + } + } + + grub_memset (buf + cur_len, 0, buf_size - cur_len); + + grub_xputs ("\n"); + grub_refresh (); + + return (key != GRUB_TERM_ESC); +} + +grub_err_t +grub_auth_check_authentication (const char *userlist) +{ + char login[1024]; + struct grub_auth_user *cur = NULL; + static unsigned long punishment_delay = 1; + char entered[GRUB_AUTH_MAX_PASSLEN]; + struct grub_auth_user *user; + + grub_memset (login, 0, sizeof (login)); + + if (is_authenticated (userlist)) + { + punishment_delay = 1; + return GRUB_ERR_NONE; + } + + grub_puts_ (N_("Enter username: ")); + + if (!grub_username_get (login, sizeof (login) - 1)) + goto access_denied; + + grub_puts_ (N_("Enter password: ")); + + if (!grub_password_get (entered, GRUB_AUTH_MAX_PASSLEN)) + goto access_denied; + + FOR_LIST_ELEMENTS (user, users) + { + if (grub_strcmp (login, user->name) == 0) + cur = user; + } + + if (!cur || ! cur->callback) + goto access_denied; + + cur->callback (login, entered, cur->arg); + if (is_authenticated (userlist)) + { + punishment_delay = 1; + return GRUB_ERR_NONE; + } + + access_denied: + grub_sleep (punishment_delay); + + if (punishment_delay < GRUB_ULONG_MAX / 2) + punishment_delay *= 2; + + return GRUB_ACCESS_DENIED; +} + +static grub_err_t +grub_cmd_authenticate (struct grub_command *cmd __attribute__ ((unused)), + int argc, char **args) +{ + return grub_auth_check_authentication ((argc >= 1) ? args[0] : ""); +} + +static grub_command_t cmd; + +void +grub_normal_auth_init (void) +{ + cmd = grub_register_command ("authenticate", + grub_cmd_authenticate, + N_("[USERLIST]"), + N_("Check whether user is in USERLIST.")); + +} + +void +grub_normal_auth_fini (void) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/normal/autofs.c b/grub-core/normal/autofs.c new file mode 100644 index 0000000..7a7cf2b --- /dev/null +++ b/grub-core/normal/autofs.c @@ -0,0 +1,148 @@ +/* autofs.c - support auto-loading from fs.lst */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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/mm.h> +#include <grub/dl.h> +#include <grub/env.h> +#include <grub/misc.h> +#include <grub/fs.h> +#include <grub/normal.h> + +/* This is used to store the names of filesystem modules for auto-loading. */ +static grub_named_list_t fs_module_list; + +/* The auto-loading hook for filesystems. */ +static int +autoload_fs_module (void) +{ + grub_named_list_t p; + int ret = 0; + + while ((p = fs_module_list) != NULL) + { + if (! grub_dl_get (p->name) && grub_dl_load (p->name)) + { + ret = 1; + break; + } + + if (grub_errno) + grub_print_error (); + + fs_module_list = p->next; + grub_free (p->name); + grub_free (p); + } + + return ret; +} + +/* Read the file fs.lst for auto-loading. */ +void +read_fs_list (const char *prefix) +{ + if (prefix) + { + char *filename; + + filename = grub_xasprintf ("%s/" GRUB_TARGET_CPU "-" GRUB_PLATFORM + "/fs.lst", prefix); + if (filename) + { + grub_file_t file; + grub_fs_autoload_hook_t tmp_autoload_hook; + + /* This rules out the possibility that read_fs_list() is invoked + recursively when we call grub_file_open() below. */ + tmp_autoload_hook = grub_fs_autoload_hook; + grub_fs_autoload_hook = NULL; + + file = grub_file_open (filename, GRUB_FILE_TYPE_GRUB_MODULE_LIST); + if (file) + { + /* Override previous fs.lst. */ + while (fs_module_list) + { + grub_named_list_t tmp; + tmp = fs_module_list->next; + grub_free (fs_module_list); + fs_module_list = tmp; + } + + while (1) + { + char *buf; + char *p; + char *q; + grub_named_list_t fs_mod; + + buf = grub_file_getline (file); + if (! buf) + break; + + p = buf; + q = buf + grub_strlen (buf) - 1; + + /* Ignore space. */ + while (grub_isspace (*p)) + p++; + + while (p < q && grub_isspace (*q)) + *q-- = '\0'; + + /* If the line is empty, skip it. */ + if (p >= q) + { + grub_free (buf); + continue; + } + + fs_mod = grub_malloc (sizeof (*fs_mod)); + if (! fs_mod) + { + grub_free (buf); + continue; + } + + fs_mod->name = grub_strdup (p); + grub_free (buf); + if (! fs_mod->name) + { + grub_free (fs_mod); + continue; + } + + fs_mod->next = fs_module_list; + fs_module_list = fs_mod; + } + + grub_file_close (file); + grub_fs_autoload_hook = tmp_autoload_hook; + } + + grub_free (filename); + } + } + + /* Ignore errors. */ + grub_errno = GRUB_ERR_NONE; + + /* Set the hook. */ + grub_fs_autoload_hook = autoload_fs_module; +} diff --git a/grub-core/normal/charset.c b/grub-core/normal/charset.c new file mode 100644 index 0000000..4dfcc31 --- /dev/null +++ b/grub-core/normal/charset.c @@ -0,0 +1,1312 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,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/>. + */ + +/* + Current problems with Unicode rendering: + - B and BN bidi type characters (ignored) + - Mc type characters with combining class 0 (poorly combined) + - Mn type characters with combining class 0 (poorly combined) + - Me type characters with combining class 0 (poorly combined) + - Cf type characters (ignored) + - Cc type characters (ignored) + - Line-breaking rules (e.g. Zs type characters) + - Indic languages + - non-Semitic shaping (rarely used) + - Zl and Zp characters + - Combining characters of types 7, 8, 9, 21, 35, 36, 84, 91, 103, 107, + 118, 122, 129, 130, 132, 218, 224, 226, 233, 234 + - Private use characters (not really a problem) + - Variations (no font support) + - Vertical text + - Ligatures + Font information ignored: + - Kerning + - Justification data + - Glyph posititioning + - Baseline data + Most underline diacritics aren't displayed in gfxterm + */ + +#include <grub/charset.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/unicode.h> +#include <grub/term.h> +#include <grub/normal.h> +#include <grub/safemath.h> + +#if HAVE_FONT_SOURCE +#include "widthspec.h" +#endif + +/* Returns -2 if not enough space, -1 on invalid character. */ +grub_ssize_t +grub_encode_utf8_character (grub_uint8_t *dest, grub_uint8_t *destend, + grub_uint32_t code) +{ + if (dest >= destend) + return -2; + if (code <= 0x007F) + { + *dest++ = code; + return 1; + } + if (code <= 0x07FF) + { + if (dest + 1 >= destend) + return -2; + *dest++ = (code >> 6) | 0xC0; + *dest++ = (code & 0x3F) | 0x80; + return 2; + } + if ((code >= 0xDC00 && code <= 0xDFFF) + || (code >= 0xD800 && code <= 0xDBFF)) + { + /* No surrogates in UCS-4... */ + return -1; + } + if (code < 0x10000) + { + if (dest + 2 >= destend) + return -2; + *dest++ = (code >> 12) | 0xE0; + *dest++ = ((code >> 6) & 0x3F) | 0x80; + *dest++ = (code & 0x3F) | 0x80; + return 3; + } + { + if (dest + 3 >= destend) + return -2; + *dest++ = (code >> 18) | 0xF0; + *dest++ = ((code >> 12) & 0x3F) | 0x80; + *dest++ = ((code >> 6) & 0x3F) | 0x80; + *dest++ = (code & 0x3F) | 0x80; + return 4; + } + +} + +/* Convert UCS-4 to UTF-8. */ +grub_size_t +grub_ucs4_to_utf8 (const grub_uint32_t *src, grub_size_t size, + grub_uint8_t *dest, grub_size_t destsize) +{ + /* Keep last char for \0. */ + grub_uint8_t *destend = dest + destsize - 1; + grub_uint8_t *dest0 = dest; + + while (size-- && dest < destend) + { + grub_uint32_t code = *src++; + grub_ssize_t s; + s = grub_encode_utf8_character (dest, destend, code); + if (s == -2) + break; + if (s == -1) + { + *dest++ = '?'; + continue; + } + dest += s; + } + *dest = 0; + return dest - dest0; +} + +/* Returns the number of bytes the string src would occupy is converted + to UTF-8, excluding trailing \0. */ +grub_size_t +grub_get_num_of_utf8_bytes (const grub_uint32_t *src, grub_size_t size) +{ + grub_size_t remaining; + const grub_uint32_t *ptr; + grub_size_t cnt = 0; + + remaining = size; + ptr = src; + while (remaining--) + { + grub_uint32_t code = *ptr++; + + if (code <= 0x007F) + cnt++; + else if (code <= 0x07FF) + cnt += 2; + else if ((code >= 0xDC00 && code <= 0xDFFF) + || (code >= 0xD800 && code <= 0xDBFF)) + /* No surrogates in UCS-4... */ + cnt++; + else if (code < 0x10000) + cnt += 3; + else + cnt += 4; + } + return cnt; +} + +/* Convert UCS-4 to UTF-8. */ +char * +grub_ucs4_to_utf8_alloc (const grub_uint32_t *src, grub_size_t size) +{ + grub_uint8_t *ret; + grub_size_t cnt = grub_get_num_of_utf8_bytes (src, size) + 1; + + ret = grub_malloc (cnt); + if (!ret) + return 0; + + grub_ucs4_to_utf8 (src, size, ret, cnt); + + return (char *) ret; +} + +int +grub_is_valid_utf8 (const grub_uint8_t *src, grub_size_t srcsize) +{ + int count = 0; + grub_uint32_t code = 0; + + while (srcsize) + { + if (srcsize != (grub_size_t)-1) + srcsize--; + if (!grub_utf8_process (*src++, &code, &count)) + return 0; + if (count != 0) + continue; + if (code == 0) + return 1; + if (code > GRUB_UNICODE_LAST_VALID) + return 0; + } + + return 1; +} + +grub_ssize_t +grub_utf8_to_ucs4_alloc (const char *msg, grub_uint32_t **unicode_msg, + grub_uint32_t **last_position) +{ + grub_size_t msg_len = grub_strlen (msg); + + *unicode_msg = grub_calloc (msg_len, sizeof (grub_uint32_t)); + + if (!*unicode_msg) + return -1; + + msg_len = grub_utf8_to_ucs4 (*unicode_msg, msg_len, + (grub_uint8_t *) msg, -1, 0); + + if (last_position) + *last_position = *unicode_msg + msg_len; + + return msg_len; +} + +/* Convert a (possibly null-terminated) UTF-8 string of at most SRCSIZE + bytes (if SRCSIZE is -1, it is ignored) in length to a UCS-4 string. + Return the number of characters converted. DEST must be able to hold + at least DESTSIZE characters. + If SRCEND is not NULL, then *SRCEND is set to the next byte after the + last byte used in SRC. */ +grub_size_t +grub_utf8_to_ucs4 (grub_uint32_t *dest, grub_size_t destsize, + const grub_uint8_t *src, grub_size_t srcsize, + const grub_uint8_t **srcend) +{ + grub_uint32_t *p = dest; + int count = 0; + grub_uint32_t code = 0; + + if (srcend) + *srcend = src; + + while (srcsize && destsize) + { + int was_count = count; + if (srcsize != (grub_size_t)-1) + srcsize--; + if (!grub_utf8_process (*src++, &code, &count)) + { + code = '?'; + count = 0; + /* Character c may be valid, don't eat it. */ + if (was_count) + src--; + } + if (count != 0) + continue; + if (code == 0) + break; + *p++ = code; + destsize--; + } + + if (srcend) + *srcend = src; + return p - dest; +} + +static grub_uint8_t *join_types = NULL; + +static void +unpack_join (void) +{ + unsigned i; + struct grub_unicode_compact_range *cur; + + join_types = grub_zalloc (GRUB_UNICODE_MAX_CACHED_CHAR); + if (!join_types) + { + grub_errno = GRUB_ERR_NONE; + return; + } + for (cur = grub_unicode_compact; cur->len; cur++) + for (i = cur->start; i < cur->start + (unsigned) cur->len + && i < GRUB_UNICODE_MAX_CACHED_CHAR; i++) + join_types[i] = cur->join_type; +} + +static grub_uint8_t *bidi_types = NULL; + +static void +unpack_bidi (void) +{ + unsigned i; + struct grub_unicode_compact_range *cur; + + bidi_types = grub_zalloc (GRUB_UNICODE_MAX_CACHED_CHAR); + if (!bidi_types) + { + grub_errno = GRUB_ERR_NONE; + return; + } + for (cur = grub_unicode_compact; cur->len; cur++) + for (i = cur->start; i < cur->start + (unsigned) cur->len + && i < GRUB_UNICODE_MAX_CACHED_CHAR; i++) + if (cur->bidi_mirror) + bidi_types[i] = cur->bidi_type | 0x80; + else + bidi_types[i] = cur->bidi_type | 0x00; +} + +static inline enum grub_bidi_type +get_bidi_type (grub_uint32_t c) +{ + struct grub_unicode_compact_range *cur; + + if (!bidi_types) + unpack_bidi (); + + if (bidi_types && c < GRUB_UNICODE_MAX_CACHED_CHAR) + return bidi_types[c] & 0x7f; + + for (cur = grub_unicode_compact; cur->len; cur++) + if (cur->start <= c && c < cur->start + (unsigned) cur->len) + return cur->bidi_type; + + return GRUB_BIDI_TYPE_L; +} + +static inline enum grub_join_type +get_join_type (grub_uint32_t c) +{ + struct grub_unicode_compact_range *cur; + + if (!join_types) + unpack_join (); + + if (join_types && c < GRUB_UNICODE_MAX_CACHED_CHAR) + return join_types[c]; + + for (cur = grub_unicode_compact; cur->len; cur++) + if (cur->start <= c && c < cur->start + (unsigned) cur->len) + return cur->join_type; + + return GRUB_JOIN_TYPE_NONJOINING; +} + +static inline int +is_mirrored (grub_uint32_t c) +{ + struct grub_unicode_compact_range *cur; + + if (!bidi_types) + unpack_bidi (); + + if (bidi_types && c < GRUB_UNICODE_MAX_CACHED_CHAR) + return !!(bidi_types[c] & 0x80); + + for (cur = grub_unicode_compact; cur->len; cur++) + if (cur->start <= c && c < cur->start + (unsigned) cur->len) + return cur->bidi_mirror; + + return 0; +} + +enum grub_comb_type +grub_unicode_get_comb_type (grub_uint32_t c) +{ + static grub_uint8_t *comb_types = NULL; + struct grub_unicode_compact_range *cur; + + if (!comb_types) + { + unsigned i; + comb_types = grub_zalloc (GRUB_UNICODE_MAX_CACHED_CHAR); + if (comb_types) + for (cur = grub_unicode_compact; cur->len; cur++) + for (i = cur->start; i < cur->start + (unsigned) cur->len + && i < GRUB_UNICODE_MAX_CACHED_CHAR; i++) + comb_types[i] = cur->comb_type; + else + grub_errno = GRUB_ERR_NONE; + } + + if (comb_types && c < GRUB_UNICODE_MAX_CACHED_CHAR) + return comb_types[c]; + + for (cur = grub_unicode_compact; cur->len; cur++) + if (cur->start <= c && c < cur->start + (unsigned) cur->len) + return cur->comb_type; + + return GRUB_UNICODE_COMB_NONE; +} + +#if HAVE_FONT_SOURCE + +grub_size_t +grub_unicode_estimate_width (const struct grub_unicode_glyph *c) +{ + if (grub_unicode_get_comb_type (c->base)) + return 0; + if (widthspec[c->base >> 3] & (1 << (c->base & 7))) + return 2; + else + return 1; +} + +#endif + +static inline int +is_type_after (enum grub_comb_type a, enum grub_comb_type b) +{ + /* Shadda is numerically higher than most of Arabic diacritics but has + to be rendered before them. */ + if (a == GRUB_UNICODE_COMB_ARABIC_SHADDA + && b <= GRUB_UNICODE_COMB_ARABIC_KASRA + && b >= GRUB_UNICODE_COMB_ARABIC_FATHATAN) + return 0; + if (b == GRUB_UNICODE_COMB_ARABIC_SHADDA + && a <= GRUB_UNICODE_COMB_ARABIC_KASRA + && a >= GRUB_UNICODE_COMB_ARABIC_FATHATAN) + return 1; + return a > b; +} + +grub_size_t +grub_unicode_aglomerate_comb (const grub_uint32_t *in, grub_size_t inlen, + struct grub_unicode_glyph *out) +{ + int haveout = 0; + const grub_uint32_t *ptr; + unsigned last_comb_pointer = 0; + + grub_memset (out, 0, sizeof (*out)); + + if (inlen && grub_iscntrl (*in)) + { + out->base = *in; + out->variant = 0; + out->attributes = 0; + out->ncomb = 0; + out->estimated_width = 1; + return 1; + } + + for (ptr = in; ptr < in + inlen; ptr++) + { + /* Variation selectors >= 17 are outside of BMP and SMP. + Handle variation selectors first to avoid potentially costly lookups. + */ + if (*ptr >= GRUB_UNICODE_VARIATION_SELECTOR_1 + && *ptr <= GRUB_UNICODE_VARIATION_SELECTOR_16) + { + if (haveout) + out->variant = *ptr - GRUB_UNICODE_VARIATION_SELECTOR_1 + 1; + continue; + } + if (*ptr >= GRUB_UNICODE_VARIATION_SELECTOR_17 + && *ptr <= GRUB_UNICODE_VARIATION_SELECTOR_256) + { + if (haveout) + out->variant = *ptr - GRUB_UNICODE_VARIATION_SELECTOR_17 + 17; + continue; + } + + enum grub_comb_type comb_type; + comb_type = grub_unicode_get_comb_type (*ptr); + if (comb_type) + { + struct grub_unicode_combining *n; + unsigned j; + grub_size_t sz; + + if (!haveout) + continue; + + if (comb_type == GRUB_UNICODE_COMB_MC + || comb_type == GRUB_UNICODE_COMB_ME + || comb_type == GRUB_UNICODE_COMB_MN) + last_comb_pointer = out->ncomb; + + if (out->ncomb + 1 <= (int) ARRAY_SIZE (out->combining_inline)) + n = out->combining_inline; + else if (out->ncomb > (int) ARRAY_SIZE (out->combining_inline)) + { + if (grub_add (out->ncomb, 1, &sz) || + grub_mul (sz, sizeof (n[0]), &sz)) + goto fail; + + n = grub_realloc (out->combining_ptr, sz); + if (!n) + { + fail: + grub_errno = GRUB_ERR_NONE; + continue; + } + out->combining_ptr = n; + } + else + { + n = grub_calloc (out->ncomb + 1, sizeof (n[0])); + if (!n) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + grub_memcpy (n, out->combining_inline, + sizeof (out->combining_inline)); + out->combining_ptr = n; + } + + for (j = last_comb_pointer; j < out->ncomb; j++) + if (is_type_after (n[j].type, comb_type)) + break; + grub_memmove (n + j + 1, + n + j, + (out->ncomb - j) + * sizeof (n[0])); + n[j].code = *ptr; + n[j].type = comb_type; + out->ncomb++; + continue; + } + if (haveout) + return ptr - in; + haveout = 1; + out->base = *ptr; + out->variant = 0; + out->attributes = 0; + out->ncomb = 0; + out->estimated_width = 1; + } + return ptr - in; +} + +static void +revert (struct grub_unicode_glyph *visual, + struct grub_term_pos *pos, + unsigned start, unsigned end) +{ + struct grub_unicode_glyph t; + unsigned i; + int a = 0, b = 0; + if (pos) + { + a = pos[visual[start].orig_pos].x; + b = pos[visual[end].orig_pos].x; + } + for (i = 0; i < (end - start) / 2 + 1; i++) + { + t = visual[start + i]; + visual[start + i] = visual[end - i]; + visual[end - i] = t; + + if (pos) + { + pos[visual[start + i].orig_pos].x = a + b - pos[visual[start + i].orig_pos].x; + pos[visual[end - i].orig_pos].x = a + b - pos[visual[end - i].orig_pos].x; + } + } +} + + +static grub_ssize_t +bidi_line_wrap (struct grub_unicode_glyph *visual_out, + struct grub_unicode_glyph *visual, + grub_size_t visual_len, + grub_size_t (*getcharwidth) (const struct grub_unicode_glyph *visual, void *getcharwidth_arg), + void *getcharwidth_arg, + grub_size_t maxwidth, grub_size_t startwidth, + grub_uint32_t contchar, + struct grub_term_pos *pos, int primitive_wrap, + grub_size_t log_end) +{ + struct grub_unicode_glyph *outptr = visual_out; + unsigned line_start = 0; + grub_ssize_t line_width; + unsigned k; + grub_ssize_t last_space = -1; + grub_ssize_t last_space_width = 0; + int lines = 0; + + if (!visual_len) + return 0; + + if (startwidth >= maxwidth && (grub_ssize_t) maxwidth > 0) + { + if (contchar) + { + grub_memset (outptr, 0, sizeof (visual[0])); + outptr->base = contchar; + outptr++; + } + grub_memset (outptr, 0, sizeof (visual[0])); + outptr->base = '\n'; + outptr++; + startwidth = 0; + } + + line_width = startwidth; + + for (k = 0; k <= visual_len; k++) + { + grub_ssize_t last_width = 0; + + if (pos && k != visual_len) + { + pos[visual[k].orig_pos].x = line_width; + pos[visual[k].orig_pos].y = lines; + pos[visual[k].orig_pos].valid = 1; + } + + if (k == visual_len && pos) + { + pos[log_end].x = line_width; + pos[log_end].y = lines; + pos[log_end].valid = 1; + } + + if (getcharwidth && k != visual_len) + line_width += last_width = getcharwidth (&visual[k], getcharwidth_arg); + + if (k != visual_len && (visual[k].base == ' ' + || visual[k].base == '\t') + && !primitive_wrap) + { + last_space = k; + last_space_width = line_width; + } + + if (((grub_ssize_t) maxwidth > 0 + && line_width > (grub_ssize_t) maxwidth) || k == visual_len) + { + unsigned min_odd_level = 0xffffffff; + unsigned max_level = 0; + unsigned kk = k; + + lines++; + + if (k != visual_len && last_space > (signed) line_start) + { + kk = last_space; + line_width -= last_space_width; + } + else if (k != visual_len && line_start == 0 && startwidth != 0 + && !primitive_wrap && lines == 1 + && line_width - startwidth < maxwidth) + { + kk = 0; + line_width -= startwidth; + } + else + line_width = last_width; + + { + unsigned i; + for (i = line_start; i < kk; i++) + { + if (visual[i].bidi_level > max_level) + max_level = visual[i].bidi_level; + if (visual[i].bidi_level < min_odd_level && (visual[i].bidi_level & 1)) + min_odd_level = visual[i].bidi_level; + } + } + + { + unsigned j; + /* FIXME: can be optimized. */ + for (j = max_level; j > min_odd_level - 1; j--) + { + unsigned in = line_start; + unsigned i; + for (i = line_start; i < kk; i++) + { + if (i != line_start && visual[i].bidi_level >= j + && visual[i-1].bidi_level < j) + in = i; + if (visual[i].bidi_level >= j && (i + 1 == kk + || visual[i+1].bidi_level < j)) + revert (visual, pos, in, i); + } + } + } + + { + unsigned i; + for (i = line_start; i < kk; i++) + { + if (is_mirrored (visual[i].base) && visual[i].bidi_level) + visual[i].attributes |= GRUB_UNICODE_GLYPH_ATTRIBUTE_MIRROR; + if ((visual[i].attributes & GRUB_UNICODE_GLYPH_ATTRIBUTES_JOIN) + && visual[i].bidi_level) + { + int left, right; + left = visual[i].attributes + & (GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED + | GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED_EXPLICIT); + right = visual[i].attributes + & (GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED + | GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED_EXPLICIT); + visual[i].attributes &= ~GRUB_UNICODE_GLYPH_ATTRIBUTES_JOIN; + left <<= GRUB_UNICODE_GLYPH_ATTRIBUTES_JOIN_LEFT_TO_RIGHT_SHIFT; + right >>= GRUB_UNICODE_GLYPH_ATTRIBUTES_JOIN_LEFT_TO_RIGHT_SHIFT; + visual[i].attributes |= (left | right); + } + } + } + + { + int left_join = 0; + unsigned i; + for (i = line_start; i < kk; i++) + { + enum grub_join_type join_type = get_join_type (visual[i].base); + if (!(visual[i].attributes + & GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED_EXPLICIT) + && (join_type == GRUB_JOIN_TYPE_LEFT + || join_type == GRUB_JOIN_TYPE_DUAL)) + { + if (left_join) + visual[i].attributes + |= GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED; + else + visual[i].attributes + &= ~GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED; + } + if (join_type == GRUB_JOIN_TYPE_NONJOINING + || join_type == GRUB_JOIN_TYPE_LEFT) + left_join = 0; + if (join_type == GRUB_JOIN_TYPE_RIGHT + || join_type == GRUB_JOIN_TYPE_DUAL + || join_type == GRUB_JOIN_TYPE_CAUSING) + left_join = 1; + } + } + + { + int right_join = 0; + signed i; + for (i = kk - 1; i >= 0 && (unsigned) i + 1 > line_start; + i--) + { + enum grub_join_type join_type = get_join_type (visual[i].base); + if (!(visual[i].attributes + & GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED_EXPLICIT) + && (join_type == GRUB_JOIN_TYPE_RIGHT + || join_type == GRUB_JOIN_TYPE_DUAL)) + { + if (right_join) + visual[i].attributes + |= GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED; + else + visual[i].attributes + &= ~GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED; + } + if (join_type == GRUB_JOIN_TYPE_NONJOINING + || join_type == GRUB_JOIN_TYPE_RIGHT) + right_join = 0; + if (join_type == GRUB_JOIN_TYPE_LEFT + || join_type == GRUB_JOIN_TYPE_DUAL + || join_type == GRUB_JOIN_TYPE_CAUSING) + right_join = 1; + } + } + + grub_memcpy (outptr, &visual[line_start], + (kk - line_start) * sizeof (visual[0])); + outptr += kk - line_start; + if (kk != visual_len) + { + if (contchar) + { + grub_memset (outptr, 0, sizeof (visual[0])); + outptr->base = contchar; + outptr++; + } + grub_memset (outptr, 0, sizeof (visual[0])); + outptr->base = '\n'; + outptr++; + } + + if ((signed) kk == last_space) + kk++; + + line_start = kk; + if (pos && kk != visual_len) + { + pos[visual[kk].orig_pos].x = 0; + pos[visual[kk].orig_pos].y = lines; + } + } + } + + return outptr - visual_out; +} + + +static grub_ssize_t +grub_bidi_line_logical_to_visual (const grub_uint32_t *logical, + grub_size_t logical_len, + struct grub_unicode_glyph *visual_out, + grub_size_t (*getcharwidth) (const struct grub_unicode_glyph *visual, void *getcharwidth_arg), + void *getcharwidth_arg, + grub_size_t maxwidth, grub_size_t startwidth, + grub_uint32_t contchar, + struct grub_term_pos *pos, + int primitive_wrap, + grub_size_t log_end) +{ + enum grub_bidi_type type = GRUB_BIDI_TYPE_L; + enum override_status {OVERRIDE_NEUTRAL = 0, OVERRIDE_R, OVERRIDE_L}; + unsigned base_level; + enum override_status cur_override; + unsigned i; + unsigned stack_level[GRUB_BIDI_MAX_EXPLICIT_LEVEL + 3]; + enum override_status stack_override[GRUB_BIDI_MAX_EXPLICIT_LEVEL + 3]; + unsigned stack_depth = 0; + unsigned invalid_pushes = 0; + unsigned visual_len = 0; + unsigned run_start, run_end; + struct grub_unicode_glyph *visual; + unsigned cur_level; + int bidi_needed = 0; + +#define push_stack(new_override, new_level) \ + { \ + if (new_level > GRUB_BIDI_MAX_EXPLICIT_LEVEL) \ + { \ + invalid_pushes++; \ + } \ + else \ + { \ + stack_level[stack_depth] = cur_level; \ + stack_override[stack_depth] = cur_override; \ + stack_depth++; \ + cur_level = new_level; \ + cur_override = new_override; \ + } \ + } + +#define pop_stack() \ + { \ + if (invalid_pushes) \ + { \ + invalid_pushes--; \ + } \ + else if (stack_depth) \ + { \ + stack_depth--; \ + cur_level = stack_level[stack_depth]; \ + cur_override = stack_override[stack_depth]; \ + } \ + } + + visual = grub_calloc (logical_len, sizeof (visual[0])); + if (!visual) + return -1; + + for (i = 0; i < logical_len; i++) + { + type = get_bidi_type (logical[i]); + if (type == GRUB_BIDI_TYPE_L || type == GRUB_BIDI_TYPE_AL + || type == GRUB_BIDI_TYPE_R) + break; + } + if (type == GRUB_BIDI_TYPE_R || type == GRUB_BIDI_TYPE_AL) + base_level = 1; + else + base_level = 0; + + cur_level = base_level; + cur_override = OVERRIDE_NEUTRAL; + { + const grub_uint32_t *lptr; + enum {JOIN_DEFAULT, NOJOIN, JOIN_FORCE} join_state = JOIN_DEFAULT; + int zwj_propagate_to_previous = 0; + for (lptr = logical; lptr < logical + logical_len;) + { + grub_size_t p; + + if (*lptr == GRUB_UNICODE_ZWJ) + { + if (zwj_propagate_to_previous) + { + visual[visual_len - 1].attributes + |= GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED_EXPLICIT + | GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED; + } + zwj_propagate_to_previous = 0; + join_state = JOIN_FORCE; + lptr++; + continue; + } + + if (*lptr == GRUB_UNICODE_ZWNJ) + { + if (zwj_propagate_to_previous) + { + visual[visual_len - 1].attributes + |= GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED_EXPLICIT; + visual[visual_len - 1].attributes + &= ~GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED; + } + zwj_propagate_to_previous = 0; + join_state = NOJOIN; + lptr++; + continue; + } + + /* The tags: deprecated, never used. */ + if (*lptr >= GRUB_UNICODE_TAG_START && *lptr <= GRUB_UNICODE_TAG_END) + continue; + + p = grub_unicode_aglomerate_comb (lptr, logical + logical_len - lptr, + &visual[visual_len]); + visual[visual_len].orig_pos = lptr - logical; + type = get_bidi_type (visual[visual_len].base); + switch (type) + { + case GRUB_BIDI_TYPE_RLE: + bidi_needed = 1; + push_stack (cur_override, (cur_level | 1) + 1); + break; + case GRUB_BIDI_TYPE_RLO: + bidi_needed = 1; + push_stack (OVERRIDE_R, (cur_level | 1) + 1); + break; + case GRUB_BIDI_TYPE_LRE: + push_stack (cur_override, (cur_level & ~1) + 2); + break; + case GRUB_BIDI_TYPE_LRO: + push_stack (OVERRIDE_L, (cur_level & ~1) + 2); + break; + case GRUB_BIDI_TYPE_PDF: + pop_stack (); + break; + case GRUB_BIDI_TYPE_BN: + break; + case GRUB_BIDI_TYPE_R: + case GRUB_BIDI_TYPE_AL: + bidi_needed = 1; + /* Fallthrough. */ + default: + { + if (join_state == JOIN_FORCE) + { + visual[visual_len].attributes + |= GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED_EXPLICIT + | GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED; + } + + if (join_state == NOJOIN) + { + visual[visual_len].attributes + |= GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED_EXPLICIT; + visual[visual_len].attributes + &= ~GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED; + } + + join_state = JOIN_DEFAULT; + zwj_propagate_to_previous = 1; + + visual[visual_len].bidi_level = cur_level; + if (cur_override != OVERRIDE_NEUTRAL) + visual[visual_len].bidi_type = + (cur_override == OVERRIDE_L) ? GRUB_BIDI_TYPE_L + : GRUB_BIDI_TYPE_R; + else + visual[visual_len].bidi_type = type; + visual_len++; + } + } + lptr += p; + } + } + + if (bidi_needed) + { + for (run_start = 0; run_start < visual_len; run_start = run_end) + { + unsigned prev_level, next_level, cur_run_level; + unsigned last_type, last_strong_type; + for (run_end = run_start; run_end < visual_len && + visual[run_end].bidi_level == visual[run_start].bidi_level; run_end++); + if (run_start == 0) + prev_level = base_level; + else + prev_level = visual[run_start - 1].bidi_level; + if (run_end == visual_len) + next_level = base_level; + else + next_level = visual[run_end].bidi_level; + cur_run_level = visual[run_start].bidi_level; + if (prev_level & 1) + last_type = GRUB_BIDI_TYPE_R; + else + last_type = GRUB_BIDI_TYPE_L; + last_strong_type = last_type; + for (i = run_start; i < run_end; i++) + { + switch (visual[i].bidi_type) + { + case GRUB_BIDI_TYPE_NSM: + visual[i].bidi_type = last_type; + break; + case GRUB_BIDI_TYPE_EN: + if (last_strong_type == GRUB_BIDI_TYPE_AL) + visual[i].bidi_type = GRUB_BIDI_TYPE_AN; + break; + case GRUB_BIDI_TYPE_L: + case GRUB_BIDI_TYPE_R: + last_strong_type = visual[i].bidi_type; + break; + case GRUB_BIDI_TYPE_ES: + if (last_type == GRUB_BIDI_TYPE_EN + && i + 1 < run_end + && visual[i + 1].bidi_type == GRUB_BIDI_TYPE_EN) + visual[i].bidi_type = GRUB_BIDI_TYPE_EN; + else + visual[i].bidi_type = GRUB_BIDI_TYPE_ON; + break; + case GRUB_BIDI_TYPE_ET: + { + unsigned j; + if (last_type == GRUB_BIDI_TYPE_EN) + { + visual[i].bidi_type = GRUB_BIDI_TYPE_EN; + break; + } + for (j = i; j < run_end + && visual[j].bidi_type == GRUB_BIDI_TYPE_ET; j++); + if (j != run_end && visual[j].bidi_type == GRUB_BIDI_TYPE_EN) + { + for (; i < run_end + && visual[i].bidi_type == GRUB_BIDI_TYPE_ET; i++) + visual[i].bidi_type = GRUB_BIDI_TYPE_EN; + i--; + break; + } + for (; i < run_end + && visual[i].bidi_type == GRUB_BIDI_TYPE_ET; i++) + visual[i].bidi_type = GRUB_BIDI_TYPE_ON; + i--; + break; + } + break; + case GRUB_BIDI_TYPE_CS: + if (last_type == GRUB_BIDI_TYPE_EN + && i + 1 < run_end + && visual[i + 1].bidi_type == GRUB_BIDI_TYPE_EN) + { + visual[i].bidi_type = GRUB_BIDI_TYPE_EN; + break; + } + if (last_type == GRUB_BIDI_TYPE_AN + && i + 1 < run_end + && (visual[i + 1].bidi_type == GRUB_BIDI_TYPE_AN + || (visual[i + 1].bidi_type == GRUB_BIDI_TYPE_EN + && last_strong_type == GRUB_BIDI_TYPE_AL))) + { + visual[i].bidi_type = GRUB_BIDI_TYPE_EN; + break; + } + visual[i].bidi_type = GRUB_BIDI_TYPE_ON; + break; + case GRUB_BIDI_TYPE_AL: + last_strong_type = visual[i].bidi_type; + visual[i].bidi_type = GRUB_BIDI_TYPE_R; + break; + default: /* Make GCC happy. */ + break; + } + last_type = visual[i].bidi_type; + if (visual[i].bidi_type == GRUB_BIDI_TYPE_EN + && last_strong_type == GRUB_BIDI_TYPE_L) + visual[i].bidi_type = GRUB_BIDI_TYPE_L; + } + if (prev_level & 1) + last_type = GRUB_BIDI_TYPE_R; + else + last_type = GRUB_BIDI_TYPE_L; + for (i = run_start; i < run_end; ) + { + unsigned j; + unsigned next_type; + for (j = i; j < run_end && + (visual[j].bidi_type == GRUB_BIDI_TYPE_B + || visual[j].bidi_type == GRUB_BIDI_TYPE_S + || visual[j].bidi_type == GRUB_BIDI_TYPE_WS + || visual[j].bidi_type == GRUB_BIDI_TYPE_ON); j++); + if (j == i) + { + if (visual[i].bidi_type == GRUB_BIDI_TYPE_L) + last_type = GRUB_BIDI_TYPE_L; + else + last_type = GRUB_BIDI_TYPE_R; + i++; + continue; + } + if (j == run_end) + next_type = (next_level & 1) ? GRUB_BIDI_TYPE_R : GRUB_BIDI_TYPE_L; + else + { + if (visual[j].bidi_type == GRUB_BIDI_TYPE_L) + next_type = GRUB_BIDI_TYPE_L; + else + next_type = GRUB_BIDI_TYPE_R; + } + if (next_type == last_type) + for (; i < j; i++) + visual[i].bidi_type = last_type; + else + for (; i < j; i++) + visual[i].bidi_type = (cur_run_level & 1) ? GRUB_BIDI_TYPE_R + : GRUB_BIDI_TYPE_L; + } + } + + for (i = 0; i < visual_len; i++) + { + if (!(visual[i].bidi_level & 1) && visual[i].bidi_type == GRUB_BIDI_TYPE_R) + { + visual[i].bidi_level++; + continue; + } + if (!(visual[i].bidi_level & 1) && (visual[i].bidi_type == GRUB_BIDI_TYPE_AN + || visual[i].bidi_type == GRUB_BIDI_TYPE_EN)) + { + visual[i].bidi_level += 2; + continue; + } + if ((visual[i].bidi_level & 1) && (visual[i].bidi_type == GRUB_BIDI_TYPE_L + || visual[i].bidi_type == GRUB_BIDI_TYPE_AN + || visual[i].bidi_type == GRUB_BIDI_TYPE_EN)) + { + visual[i].bidi_level++; + continue; + } + } + } + else + { + for (i = 0; i < visual_len; i++) + visual[i].bidi_level = 0; + } + + { + grub_ssize_t ret; + ret = bidi_line_wrap (visual_out, visual, visual_len, + getcharwidth, getcharwidth_arg, maxwidth, startwidth, contchar, + pos, primitive_wrap, log_end); + grub_free (visual); + return ret; + } +} + +static int +is_visible (const struct grub_unicode_glyph *gl) +{ + if (gl->ncomb) + return 1; + if (gl->base == GRUB_UNICODE_LRM || gl->base == GRUB_UNICODE_RLM) + return 0; + return 1; +} + +grub_ssize_t +grub_bidi_logical_to_visual (const grub_uint32_t *logical, + grub_size_t logical_len, + struct grub_unicode_glyph **visual_out, + grub_size_t (*getcharwidth) (const struct grub_unicode_glyph *visual, void *getcharwidth_arg), + void *getcharwidth_arg, + grub_size_t max_length, grub_size_t startwidth, + grub_uint32_t contchar, struct grub_term_pos *pos, int primitive_wrap) +{ + const grub_uint32_t *line_start = logical, *ptr; + struct grub_unicode_glyph *visual_ptr; + *visual_out = visual_ptr = grub_calloc (logical_len + 2, + 3 * sizeof (visual_ptr[0])); + if (!visual_ptr) + return -1; + for (ptr = logical; ptr <= logical + logical_len; ptr++) + { + if (ptr == logical + logical_len || *ptr == '\n') + { + grub_ssize_t ret; + grub_ssize_t i, j; + ret = grub_bidi_line_logical_to_visual (line_start, + ptr - line_start, + visual_ptr, + getcharwidth, + getcharwidth_arg, + max_length, + startwidth, + contchar, + pos, + primitive_wrap, + logical_len); + startwidth = 0; + + if (ret < 0) + { + grub_free (*visual_out); + return ret; + } + for (i = 0, j = 0; i < ret; i++) + if (is_visible(&visual_ptr[i])) + visual_ptr[j++] = visual_ptr[i]; + visual_ptr += j; + line_start = ptr; + if (ptr != logical + logical_len) + { + grub_memset (visual_ptr, 0, sizeof (visual_ptr[0])); + visual_ptr->base = '\n'; + visual_ptr++; + line_start++; + } + } + } + return visual_ptr - *visual_out; +} + +grub_uint32_t +grub_unicode_mirror_code (grub_uint32_t in) +{ + int i; + for (i = 0; grub_unicode_bidi_pairs[i].key; i++) + if (grub_unicode_bidi_pairs[i].key == in) + return grub_unicode_bidi_pairs[i].replace; + return in; +} + +grub_uint32_t +grub_unicode_shape_code (grub_uint32_t in, grub_uint8_t attr) +{ + int i; + if (!(in >= GRUB_UNICODE_ARABIC_START + && in < GRUB_UNICODE_ARABIC_END)) + return in; + + for (i = 0; grub_unicode_arabic_shapes[i].code; i++) + if (grub_unicode_arabic_shapes[i].code == in) + { + grub_uint32_t out = 0; + switch (attr & (GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED + | GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED)) + { + case 0: + out = grub_unicode_arabic_shapes[i].isolated; + break; + case GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED: + out = grub_unicode_arabic_shapes[i].right_linked; + break; + case GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED: + out = grub_unicode_arabic_shapes[i].left_linked; + break; + case GRUB_UNICODE_GLYPH_ATTRIBUTE_RIGHT_JOINED + |GRUB_UNICODE_GLYPH_ATTRIBUTE_LEFT_JOINED: + out = grub_unicode_arabic_shapes[i].both_linked; + break; + } + if (out) + return out; + } + + return in; +} + +const grub_uint32_t * +grub_unicode_get_comb_start (const grub_uint32_t *str, + const grub_uint32_t *cur) +{ + const grub_uint32_t *ptr; + for (ptr = cur; ptr >= str; ptr--) + { + if (*ptr >= GRUB_UNICODE_VARIATION_SELECTOR_1 + && *ptr <= GRUB_UNICODE_VARIATION_SELECTOR_16) + continue; + + if (*ptr >= GRUB_UNICODE_VARIATION_SELECTOR_17 + && *ptr <= GRUB_UNICODE_VARIATION_SELECTOR_256) + continue; + + enum grub_comb_type comb_type; + comb_type = grub_unicode_get_comb_type (*ptr); + if (comb_type) + continue; + return ptr; + } + return str; +} + +const grub_uint32_t * +grub_unicode_get_comb_end (const grub_uint32_t *end, + const grub_uint32_t *cur) +{ + const grub_uint32_t *ptr; + for (ptr = cur; ptr < end; ptr++) + { + if (*ptr >= GRUB_UNICODE_VARIATION_SELECTOR_1 + && *ptr <= GRUB_UNICODE_VARIATION_SELECTOR_16) + continue; + + if (*ptr >= GRUB_UNICODE_VARIATION_SELECTOR_17 + && *ptr <= GRUB_UNICODE_VARIATION_SELECTOR_256) + continue; + + enum grub_comb_type comb_type; + comb_type = grub_unicode_get_comb_type (*ptr); + if (comb_type) + continue; + return ptr; + } + return end; +} + diff --git a/grub-core/normal/cmdline.c b/grub-core/normal/cmdline.c new file mode 100644 index 0000000..de03fe6 --- /dev/null +++ b/grub-core/normal/cmdline.c @@ -0,0 +1,691 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,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/misc.h> +#include <grub/term.h> +#include <grub/err.h> +#include <grub/types.h> +#include <grub/mm.h> +#include <grub/partition.h> +#include <grub/disk.h> +#include <grub/file.h> +#include <grub/env.h> +#include <grub/i18n.h> +#include <grub/charset.h> +#include <grub/safemath.h> + +static grub_uint32_t *kill_buf; + +static int hist_size; +static grub_uint32_t **hist_lines = 0; +static int hist_pos = 0; +static int hist_end = 0; +static int hist_used = 0; + +grub_err_t +grub_set_history (int newsize) +{ + grub_uint32_t **old_hist_lines = hist_lines; + hist_lines = grub_calloc (newsize, sizeof (grub_uint32_t *)); + + /* Copy the old lines into the new buffer. */ + if (old_hist_lines) + { + /* Remove the lines that don't fit in the new buffer. */ + if (newsize < hist_used) + { + grub_size_t i; + grub_size_t delsize = hist_used - newsize; + hist_used = newsize; + + for (i = 1; i < delsize + 1; i++) + { + grub_ssize_t pos = hist_end - i; + if (pos < 0) + pos += hist_size; + grub_free (old_hist_lines[pos]); + } + + hist_end -= delsize; + if (hist_end < 0) + hist_end += hist_size; + } + + if (hist_pos < hist_end) + grub_memmove (hist_lines, old_hist_lines + hist_pos, + (hist_end - hist_pos) * sizeof (grub_uint32_t *)); + else if (hist_used) + { + /* Copy the older part. */ + grub_memmove (hist_lines, old_hist_lines + hist_pos, + (hist_size - hist_pos) * sizeof (grub_uint32_t *)); + + /* Copy the newer part. */ + grub_memmove (hist_lines + hist_size - hist_pos, old_hist_lines, + hist_end * sizeof (grub_uint32_t *)); + } + } + + grub_free (old_hist_lines); + + hist_size = newsize; + hist_pos = 0; + hist_end = hist_used; + return 0; +} + +/* Get the entry POS from the history where `0' is the newest + entry. */ +static grub_uint32_t * +grub_history_get (unsigned pos) +{ + pos = (hist_pos + pos) % hist_size; + return hist_lines[pos]; +} + +static grub_size_t +strlen_ucs4 (const grub_uint32_t *s) +{ + const grub_uint32_t *p = s; + + while (*p) + p++; + + return p - s; +} + +/* Replace the history entry on position POS with the string S. */ +static void +grub_history_set (int pos, grub_uint32_t *s, grub_size_t len) +{ + grub_free (hist_lines[pos]); + hist_lines[pos] = grub_calloc (len + 1, sizeof (grub_uint32_t)); + if (!hist_lines[pos]) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return ; + } + grub_memcpy (hist_lines[pos], s, len * sizeof (grub_uint32_t)); + hist_lines[pos][len] = 0; +} + +/* Insert a new history line S on the top of the history. */ +static void +grub_history_add (grub_uint32_t *s, grub_size_t len) +{ + /* Remove the oldest entry in the history to make room for a new + entry. */ + if (hist_used + 1 > hist_size) + { + hist_end--; + if (hist_end < 0) + hist_end = hist_size + hist_end; + + grub_free (hist_lines[hist_end]); + } + else + hist_used++; + + /* Move to the next position. */ + hist_pos--; + if (hist_pos < 0) + hist_pos = hist_size + hist_pos; + + /* Insert into history. */ + hist_lines[hist_pos] = NULL; + grub_history_set (hist_pos, s, len); +} + +/* Replace the history entry on position POS with the string S. */ +static void +grub_history_replace (unsigned pos, grub_uint32_t *s, grub_size_t len) +{ + grub_history_set ((hist_pos + pos) % hist_size, s, len); +} + +/* A completion hook to print items. */ +static void +print_completion (const char *item, grub_completion_type_t type, int count) +{ + if (count == 0) + { + /* If this is the first time, print a label. */ + + grub_puts (""); + switch (type) + { + case GRUB_COMPLETION_TYPE_COMMAND: + grub_puts_ (N_("Possible commands are:")); + break; + case GRUB_COMPLETION_TYPE_DEVICE: + grub_puts_ (N_("Possible devices are:")); + break; + case GRUB_COMPLETION_TYPE_FILE: + grub_puts_ (N_("Possible files are:")); + break; + case GRUB_COMPLETION_TYPE_PARTITION: + grub_puts_ (N_("Possible partitions are:")); + break; + case GRUB_COMPLETION_TYPE_ARGUMENT: + grub_puts_ (N_("Possible arguments are:")); + break; + default: + /* TRANSLATORS: this message is used if none of above matches. + This shouldn't happen but please use the general term for + "thing" or "object". */ + grub_puts_ (N_("Possible things are:")); + break; + } + grub_puts (""); + } + + if (type == GRUB_COMPLETION_TYPE_PARTITION) + { + grub_normal_print_device_info (item); + grub_errno = GRUB_ERR_NONE; + } + else + grub_printf (" %s", item); +} + +struct cmdline_term +{ + struct grub_term_coordinate pos; + unsigned ystart, width, height; + unsigned prompt_len; + struct grub_term_output *term; +}; + +static inline void +cl_set_pos (struct cmdline_term *cl_term, grub_size_t lpos) +{ + cl_term->pos.x = (cl_term->prompt_len + lpos) % cl_term->width; + cl_term->pos.y = cl_term->ystart + + (cl_term->prompt_len + lpos) / cl_term->width; + grub_term_gotoxy (cl_term->term, cl_term->pos); +} + +static void +cl_set_pos_all (struct cmdline_term *cl_terms, unsigned nterms, + grub_size_t lpos) +{ + unsigned i; + for (i = 0; i < nterms; i++) + cl_set_pos (&cl_terms[i], lpos); +} + +static inline void __attribute__ ((always_inline)) +cl_print (struct cmdline_term *cl_term, grub_uint32_t c, + grub_uint32_t *start, grub_uint32_t *end) +{ + grub_uint32_t *p; + + for (p = start; p < end; p++) + { + if (c) + grub_putcode (c, cl_term->term); + else + grub_putcode (*p, cl_term->term); + cl_term->pos.x++; + if (cl_term->pos.x >= cl_term->width - 1) + { + cl_term->pos.x = 0; + if (cl_term->pos.y >= (unsigned) (cl_term->height - 1)) + cl_term->ystart--; + else + cl_term->pos.y++; + grub_putcode ('\n', cl_term->term); + } + } +} + +static void +cl_print_all (struct cmdline_term *cl_terms, unsigned nterms, + grub_uint32_t c, grub_uint32_t *start, grub_uint32_t *end) +{ + unsigned i; + for (i = 0; i < nterms; i++) + cl_print (&cl_terms[i], c, start, end); +} + +static void +init_clterm (struct cmdline_term *cl_term_cur) +{ + cl_term_cur->pos.x = cl_term_cur->prompt_len; + cl_term_cur->pos.y = grub_term_getxy (cl_term_cur->term).y; + cl_term_cur->ystart = cl_term_cur->pos.y; + cl_term_cur->width = grub_term_width (cl_term_cur->term); + cl_term_cur->height = grub_term_height (cl_term_cur->term); +} + + +static void +cl_delete (struct cmdline_term *cl_terms, unsigned nterms, + grub_uint32_t *buf, + grub_size_t lpos, grub_size_t *llen, unsigned len) +{ + if (lpos + len <= (*llen)) + { + cl_set_pos_all (cl_terms, nterms, (*llen) - len); + cl_print_all (cl_terms, nterms, ' ', buf + (*llen) - len, buf + (*llen)); + + cl_set_pos_all (cl_terms, nterms, lpos); + + grub_memmove (buf + lpos, buf + lpos + len, + sizeof (grub_uint32_t) * ((*llen) - lpos + 1)); + (*llen) -= len; + cl_print_all (cl_terms, nterms, 0, buf + lpos, buf + (*llen)); + cl_set_pos_all (cl_terms, nterms, lpos); + } +} + + +static void +cl_insert (struct cmdline_term *cl_terms, unsigned nterms, + grub_size_t *lpos, grub_size_t *llen, + grub_size_t *max_len, grub_uint32_t **buf, + const grub_uint32_t *str) +{ + grub_size_t len = strlen_ucs4 (str); + + if (len + (*llen) >= (*max_len)) + { + grub_uint32_t *nbuf; + grub_size_t sz; + + if (grub_mul (*max_len, 2, max_len) || + grub_mul (*max_len, sizeof (grub_uint32_t), &sz)) + { + grub_errno = GRUB_ERR_OUT_OF_RANGE; + goto fail; + } + + nbuf = grub_realloc ((*buf), sz); + if (nbuf) + (*buf) = nbuf; + else + { + fail: + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + (*max_len) /= 2; + } + } + + if (len + (*llen) < (*max_len)) + { + grub_memmove ((*buf) + (*lpos) + len, (*buf) + (*lpos), + ((*llen) - (*lpos) + 1) * sizeof (grub_uint32_t)); + grub_memmove ((*buf) + (*lpos), str, len * sizeof (grub_uint32_t)); + + (*llen) += len; + cl_set_pos_all (cl_terms, nterms, (*lpos)); + cl_print_all (cl_terms, nterms, 0, *buf + (*lpos), *buf + (*llen)); + (*lpos) += len; + cl_set_pos_all (cl_terms, nterms, (*lpos)); + } +} + + +/* Get a command-line. If ESC is pushed, return zero, + otherwise return command line. */ +/* FIXME: The dumb interface is not supported yet. */ +char * +grub_cmdline_get (const char *prompt_translated) +{ + grub_size_t lpos, llen; + grub_uint32_t *buf; + grub_size_t max_len = 256; + int key; + int histpos = 0; + struct cmdline_term *cl_terms; + char *ret; + unsigned nterms; + + buf = grub_calloc (max_len, sizeof (grub_uint32_t)); + if (!buf) + return 0; + + lpos = llen = 0; + buf[0] = '\0'; + + { + grub_term_output_t term; + + FOR_ACTIVE_TERM_OUTPUTS(term) + if ((grub_term_getxy (term).x) != 0) + grub_putcode ('\n', term); + } + grub_xputs (prompt_translated); + grub_xputs (" "); + grub_normal_reset_more (); + + { + struct cmdline_term *cl_term_cur; + struct grub_term_output *cur; + grub_uint32_t *unicode_msg; + grub_size_t msg_len = grub_strlen (prompt_translated) + 3; + + nterms = 0; + FOR_ACTIVE_TERM_OUTPUTS(cur) + nterms++; + + cl_terms = grub_calloc (nterms, sizeof (cl_terms[0])); + if (!cl_terms) + { + grub_free (buf); + return 0; + } + cl_term_cur = cl_terms; + + unicode_msg = grub_calloc (msg_len, sizeof (grub_uint32_t)); + if (!unicode_msg) + { + grub_free (buf); + grub_free (cl_terms); + return 0; + } + msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len - 1, + (grub_uint8_t *) prompt_translated, -1, 0); + unicode_msg[msg_len++] = ' '; + + FOR_ACTIVE_TERM_OUTPUTS(cur) + { + cl_term_cur->term = cur; + cl_term_cur->prompt_len = grub_getstringwidth (unicode_msg, + unicode_msg + msg_len, + cur); + init_clterm (cl_term_cur); + cl_term_cur++; + } + grub_free (unicode_msg); + } + + if (hist_used == 0) + grub_history_add (buf, llen); + + grub_refresh (); + + while ((key = grub_getkey ()) != '\n' && key != '\r') + { + switch (key) + { + case GRUB_TERM_CTRL | 'a': + case GRUB_TERM_KEY_HOME: + lpos = 0; + cl_set_pos_all (cl_terms, nterms, lpos); + break; + + case GRUB_TERM_CTRL | 'b': + case GRUB_TERM_KEY_LEFT: + if (lpos > 0) + { + lpos--; + cl_set_pos_all (cl_terms, nterms, lpos); + } + break; + + case GRUB_TERM_CTRL | 'e': + case GRUB_TERM_KEY_END: + lpos = llen; + cl_set_pos_all (cl_terms, nterms, lpos); + break; + + case GRUB_TERM_CTRL | 'f': + case GRUB_TERM_KEY_RIGHT: + if (lpos < llen) + { + lpos++; + cl_set_pos_all (cl_terms, nterms, lpos); + } + break; + + case GRUB_TERM_CTRL | 'i': + case '\t': + { + int restore; + char *insertu8; + char *bufu8; + grub_uint32_t c; + + c = buf[lpos]; + buf[lpos] = '\0'; + + bufu8 = grub_ucs4_to_utf8_alloc (buf, lpos); + buf[lpos] = c; + if (!bufu8) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + break; + } + + insertu8 = grub_normal_do_completion (bufu8, &restore, + print_completion); + grub_free (bufu8); + + grub_normal_reset_more (); + + if (restore) + { + unsigned i; + + /* Restore the prompt. */ + grub_xputs ("\n"); + grub_xputs (prompt_translated); + grub_xputs (" "); + + for (i = 0; i < nterms; i++) + init_clterm (&cl_terms[i]); + + cl_print_all (cl_terms, nterms, 0, buf, buf + llen); + } + + if (insertu8) + { + grub_size_t insertlen; + grub_ssize_t t; + grub_uint32_t *insert; + + insertlen = grub_strlen (insertu8); + insert = grub_calloc (insertlen + 1, sizeof (grub_uint32_t)); + if (!insert) + { + grub_free (insertu8); + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + break; + } + t = grub_utf8_to_ucs4 (insert, insertlen, + (grub_uint8_t *) insertu8, + insertlen, 0); + if (t > 0) + { + if (insert[t-1] == ' ' && buf[lpos] == ' ') + { + insert[t-1] = 0; + if (t != 1) + cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, insert); + lpos++; + } + else + { + insert[t] = 0; + cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, insert); + } + } + + grub_free (insertu8); + grub_free (insert); + } + cl_set_pos_all (cl_terms, nterms, lpos); + } + break; + + case GRUB_TERM_CTRL | 'k': + if (lpos < llen) + { + grub_free (kill_buf); + + kill_buf = grub_malloc ((llen - lpos + 1) + * sizeof (grub_uint32_t)); + if (grub_errno) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + else + { + grub_memcpy (kill_buf, buf + lpos, + (llen - lpos + 1) * sizeof (grub_uint32_t)); + kill_buf[llen - lpos] = 0; + } + + cl_delete (cl_terms, nterms, + buf, lpos, &llen, llen - lpos); + } + break; + + case GRUB_TERM_CTRL | 'n': + case GRUB_TERM_KEY_DOWN: + { + grub_uint32_t *hist; + + lpos = 0; + + if (histpos > 0) + { + grub_history_replace (histpos, buf, llen); + histpos--; + } + + cl_delete (cl_terms, nterms, + buf, lpos, &llen, llen); + hist = grub_history_get (histpos); + cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, hist); + + break; + } + + case GRUB_TERM_KEY_UP: + case GRUB_TERM_CTRL | 'p': + { + grub_uint32_t *hist; + + lpos = 0; + + if (histpos < hist_used - 1) + { + grub_history_replace (histpos, buf, llen); + histpos++; + } + + cl_delete (cl_terms, nterms, + buf, lpos, &llen, llen); + hist = grub_history_get (histpos); + + cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, hist); + } + break; + + case GRUB_TERM_CTRL | 'u': + if (lpos > 0) + { + grub_size_t n = lpos; + + grub_free (kill_buf); + + kill_buf = grub_calloc (n + 1, sizeof (grub_uint32_t)); + if (grub_errno) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + if (kill_buf) + { + grub_memcpy (kill_buf, buf, n * sizeof(grub_uint32_t)); + kill_buf[n] = 0; + } + + lpos = 0; + cl_set_pos_all (cl_terms, nterms, lpos); + cl_delete (cl_terms, nterms, + buf, lpos, &llen, n); + } + break; + + case GRUB_TERM_CTRL | 'y': + if (kill_buf) + cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, kill_buf); + break; + + case GRUB_TERM_ESC: + grub_free (cl_terms); + grub_free (buf); + return 0; + + case GRUB_TERM_BACKSPACE: + if (lpos > 0) + { + lpos--; + cl_set_pos_all (cl_terms, nterms, lpos); + } + else + break; + /* fall through */ + + case GRUB_TERM_CTRL | 'd': + case GRUB_TERM_KEY_DC: + if (lpos < llen) + cl_delete (cl_terms, nterms, + buf, lpos, &llen, 1); + break; + + default: + if (grub_isprint (key)) + { + grub_uint32_t str[2]; + + str[0] = key; + str[1] = '\0'; + cl_insert (cl_terms, nterms, &lpos, &llen, &max_len, &buf, str); + } + break; + } + + grub_refresh (); + } + + grub_xputs ("\n"); + grub_refresh (); + + histpos = 0; + if (strlen_ucs4 (buf) > 0) + { + grub_uint32_t empty[] = { 0 }; + grub_history_replace (histpos, buf, llen); + grub_history_add (empty, 0); + } + + ret = grub_ucs4_to_utf8_alloc (buf, llen + 1); + grub_free (buf); + grub_free (cl_terms); + return ret; +} diff --git a/grub-core/normal/color.c b/grub-core/normal/color.c new file mode 100644 index 0000000..d22cf90 --- /dev/null +++ b/grub-core/normal/color.c @@ -0,0 +1,145 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2008 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/misc.h> +#include <grub/mm.h> +#include <grub/normal.h> +#include <grub/term.h> +#include <grub/i18n.h> + +/* Borrowed from GRUB Legacy */ +static const char *color_list[16] = +{ + "black", + "blue", + "green", + "cyan", + "red", + "magenta", + "brown", + "light-gray", + "dark-gray", + "light-blue", + "light-green", + "light-cyan", + "light-red", + "light-magenta", + "yellow", + "white" +}; + +static int +parse_color_name (grub_uint8_t *ret, char *name) +{ + grub_uint8_t i; + for (i = 0; i < ARRAY_SIZE(color_list); i++) + if (! grub_strcmp (name, color_list[i])) + { + *ret = i; + return 0; + } + return -1; +} + +int +grub_parse_color_name_pair (grub_uint8_t *color, const char *name) +{ + int result = 1; + grub_uint8_t fg, bg; + char *fg_name, *bg_name; + + /* nothing specified by user */ + if (name == NULL) + return result; + + fg_name = grub_strdup (name); + if (fg_name == NULL) + { + /* "out of memory" message was printed by grub_strdup() */ + grub_wait_after_message (); + return result; + } + + bg_name = grub_strchr (fg_name, '/'); + if (bg_name == NULL) + { + grub_printf_ (N_("Warning: syntax error (missing slash) in `%s'\n"), fg_name); + grub_wait_after_message (); + goto free_and_return; + } + + *(bg_name++) = '\0'; + + if (parse_color_name (&fg, fg_name) == -1) + { + grub_printf_ (N_("Warning: invalid foreground color `%s'\n"), fg_name); + grub_wait_after_message (); + goto free_and_return; + } + if (parse_color_name (&bg, bg_name) == -1) + { + grub_printf_ (N_("Warning: invalid background color `%s'\n"), bg_name); + grub_wait_after_message (); + goto free_and_return; + } + + *color = (bg << 4) | fg; + result = 0; + +free_and_return: + grub_free (fg_name); + return result; +} + +static void +set_colors (void) +{ + struct grub_term_output *term; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + /* Propagates `normal' color to terminal current color. */ + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + } +} + +/* Replace default `normal' colors with the ones specified by user (if any). */ +char * +grub_env_write_color_normal (struct grub_env_var *var __attribute__ ((unused)), + const char *val) +{ + if (grub_parse_color_name_pair (&grub_term_normal_color, val)) + return NULL; + + set_colors (); + + return grub_strdup (val); +} + +/* Replace default `highlight' colors with the ones specified by user (if any). */ +char * +grub_env_write_color_highlight (struct grub_env_var *var __attribute__ ((unused)), + const char *val) +{ + if (grub_parse_color_name_pair (&grub_term_highlight_color, val)) + return NULL; + + set_colors (); + + return grub_strdup (val); +} diff --git a/grub-core/normal/completion.c b/grub-core/normal/completion.c new file mode 100644 index 0000000..18cadfa --- /dev/null +++ b/grub-core/normal/completion.c @@ -0,0 +1,526 @@ +/* completion.c - complete a command, a disk, a partition or a file */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,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/misc.h> +#include <grub/err.h> +#include <grub/mm.h> +#include <grub/partition.h> +#include <grub/disk.h> +#include <grub/file.h> +#include <grub/parser.h> +#include <grub/extcmd.h> +#include <grub/charset.h> + +/* The current word. */ +static const char *current_word; + +/* The matched string. */ +static char *match; + +/* The count of candidates. */ +static int num_found; + +/* The string to be appended. */ +static const char *suffix; + +/* The callback function to print items. */ +static void (*print_func) (const char *, grub_completion_type_t, int); + +/* The state the command line is in. */ +static grub_parser_state_t cmdline_state; + + +/* Add a string to the list of possible completions. COMPLETION is the + string that should be added. EXTRA will be appended if COMPLETION + matches uniquely. The type TYPE specifies what kind of data is added. */ +static int +add_completion (const char *completion, const char *extra, + grub_completion_type_t type) +{ + if (grub_strncmp (current_word, completion, grub_strlen (current_word)) == 0) + { + num_found++; + + switch (num_found) + { + case 1: + match = grub_strdup (completion); + if (! match) + return 1; + suffix = extra; + break; + + case 2: + if (print_func) + print_func (match, type, 0); + + /* Fall through. */ + + default: + { + char *s = match; + const char *t = completion; + + if (print_func) + print_func (completion, type, num_found - 1); + + /* Detect the matched portion. */ + while (*s && *t && *s == *t) + { + s++; + t++; + } + s = match + grub_getend (match, s); + + *s = '\0'; + } + break; + } + } + + return 0; +} + +static int +iterate_partition (grub_disk_t disk, const grub_partition_t p, + void *data __attribute__ ((unused))) +{ + const char *disk_name = disk->name; + char *name; + int ret; + char *part_name; + + part_name = grub_partition_get_name (p); + if (! part_name) + return 1; + + name = grub_xasprintf ("%s,%s", disk_name, part_name); + grub_free (part_name); + + if (! name) + return 1; + + ret = add_completion (name, ")", GRUB_COMPLETION_TYPE_PARTITION); + grub_free (name); + return ret; +} + +static int +iterate_dir (const char *filename, const struct grub_dirhook_info *info, + void *data __attribute__ ((unused))) +{ + if (! info->dir) + { + const char *prefix; + if (cmdline_state == GRUB_PARSER_STATE_DQUOTE) + prefix = "\" "; + else if (cmdline_state == GRUB_PARSER_STATE_QUOTE) + prefix = "\' "; + else + prefix = " "; + + if (add_completion (filename, prefix, GRUB_COMPLETION_TYPE_FILE)) + return 1; + } + else if (grub_strcmp (filename, ".") && grub_strcmp (filename, "..")) + { + char *fname; + + fname = grub_xasprintf ("%s/", filename); + if (add_completion (fname, "", GRUB_COMPLETION_TYPE_FILE)) + { + grub_free (fname); + return 1; + } + grub_free (fname); + } + + return 0; +} + +static int +iterate_dev (const char *devname, void *data __attribute__ ((unused))) +{ + grub_device_t dev; + + /* Complete the partition part. */ + dev = grub_device_open (devname); + + if (!dev) + { + grub_errno = GRUB_ERR_NONE; + return 0; + } + + if (grub_strcmp (devname, current_word) == 0) + { + if (add_completion (devname, ")", GRUB_COMPLETION_TYPE_PARTITION)) + { + grub_device_close (dev); + return 1; + } + + if (dev->disk) + if (grub_partition_iterate (dev->disk, iterate_partition, NULL)) + { + grub_device_close (dev); + return 1; + } + } + else if (add_completion (devname, "", GRUB_COMPLETION_TYPE_DEVICE)) + { + grub_device_close (dev); + return 1; + } + + grub_device_close (dev); + grub_errno = GRUB_ERR_NONE; + return 0; +} + +/* Complete a device. */ +static int +complete_device (void) +{ + /* Check if this is a device or a partition. */ + char *p = grub_strchr (++current_word, ','); + grub_device_t dev; + + if (! p) + { + /* Complete the disk part. */ + if (grub_disk_dev_iterate (iterate_dev, NULL)) + return 1; + } + else + { + /* Complete the partition part. */ + *p = '\0'; + dev = grub_device_open (current_word); + *p = ','; + grub_errno = GRUB_ERR_NONE; + + if (dev) + { + if (dev->disk) + { + if (grub_partition_iterate (dev->disk, iterate_partition, NULL)) + { + grub_device_close (dev); + return 1; + } + } + + grub_device_close (dev); + } + else + return 1; + } + + return 0; +} + +/* Complete a file. */ +static int +complete_file (void) +{ + char *device; + char *dir; + char *last_dir; + grub_fs_t fs; + grub_device_t dev; + int ret = 0; + + device = grub_file_get_device_name (current_word); + if (grub_errno != GRUB_ERR_NONE) + return 1; + + dev = grub_device_open (device); + if (! dev) + { + ret = 1; + goto fail; + } + + fs = grub_fs_probe (dev); + if (! fs) + { + ret = 1; + goto fail; + } + + dir = grub_strchr (current_word + (device ? 2 + grub_strlen (device) : 0), + '/'); + last_dir = grub_strrchr (current_word, '/'); + if (dir) + { + char *dirfile; + + current_word = last_dir + 1; + + dir = grub_strdup (dir); + if (! dir) + { + ret = 1; + goto fail; + } + + /* Cut away the filename part. */ + dirfile = grub_strrchr (dir, '/'); + if (dirfile) + dirfile[1] = '\0'; + + /* Iterate the directory. */ + (fs->fs_dir) (dev, dir, iterate_dir, NULL); + + grub_free (dir); + + if (grub_errno) + { + ret = 1; + goto fail; + } + } + else + { + current_word += grub_strlen (current_word); + match = grub_strdup ("/"); + if (! match) + { + ret = 1; + goto fail; + } + + suffix = ""; + num_found = 1; + } + + fail: + if (dev) + grub_device_close (dev); + grub_free (device); + return ret; +} + +/* Complete an argument. */ +static int +complete_arguments (char *command) +{ + grub_command_t cmd; + grub_extcmd_t ext; + const struct grub_arg_option *option; + char shortarg[] = "- "; + + cmd = grub_command_find (command); + + if (!cmd || !(cmd->flags & GRUB_COMMAND_FLAG_EXTCMD)) + return 0; + + ext = cmd->data; + if (!ext->options) + return 0; + + if (add_completion ("-u", " ", GRUB_COMPLETION_TYPE_ARGUMENT)) + return 1; + + /* Add the short arguments. */ + for (option = ext->options; option->doc; option++) + { + if (! option->shortarg) + continue; + + shortarg[1] = option->shortarg; + if (add_completion (shortarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT)) + return 1; + + } + + /* First add the built-in arguments. */ + if (add_completion ("--help", " ", GRUB_COMPLETION_TYPE_ARGUMENT)) + return 1; + if (add_completion ("--usage", " ", GRUB_COMPLETION_TYPE_ARGUMENT)) + return 1; + + /* Add the long arguments. */ + for (option = ext->options; option->doc; option++) + { + char *longarg; + if (!option->longarg) + continue; + + longarg = grub_xasprintf ("--%s", option->longarg); + if (!longarg) + return 1; + + if (add_completion (longarg, " ", GRUB_COMPLETION_TYPE_ARGUMENT)) + { + grub_free (longarg); + return 1; + } + grub_free (longarg); + } + + return 0; +} + + +static grub_parser_state_t +get_state (const char *cmdline) +{ + grub_parser_state_t state = GRUB_PARSER_STATE_TEXT; + char use; + + while (*cmdline) + state = grub_parser_cmdline_state (state, *(cmdline++), &use); + return state; +} + + +/* Try to complete the string in BUF. Return the characters that + should be added to the string. This command outputs the possible + completions by calling HOOK, in that case set RESTORE to 1 so the + caller can restore the prompt. */ +char * +grub_normal_do_completion (char *buf, int *restore, + void (*hook) (const char *, grub_completion_type_t, int)) +{ + int argc = 0; + char **argv = NULL; + + /* Initialize variables. */ + match = 0; + num_found = 0; + suffix = ""; + print_func = hook; + + *restore = 1; + + if (grub_parser_split_cmdline (buf, 0, 0, &argc, &argv)) + return 0; + + if (argc == 0) + current_word = ""; + else + current_word = argv[argc - 1]; + + if (argc > 1 && ! grub_strcmp (argv[0], "set")) + { + char *equals = grub_strchr (current_word, '='); + if (equals) + /* Complete the value of the variable. */ + current_word = equals + 1; + } + + /* Determine the state the command line is in, depending on the + state, it can be determined how to complete. */ + cmdline_state = get_state (buf); + + if (argc == 1 || argc == 0) + { + /* Complete a command. */ + grub_command_t cmd; + FOR_COMMANDS(cmd) + { + if (cmd->prio & GRUB_COMMAND_FLAG_ACTIVE) + { + if (add_completion (cmd->name, " ", GRUB_COMPLETION_TYPE_COMMAND)) + goto fail; + } + } + } + else if (*current_word == '-') + { + if (complete_arguments (buf)) + goto fail; + } + else if (*current_word == '(' && ! grub_strchr (current_word, ')')) + { + /* Complete a device. */ + if (complete_device ()) + goto fail; + } + else + { + /* Complete a file. */ + if (complete_file ()) + goto fail; + } + + /* If more than one match is found those matches will be printed and + the prompt should be restored. */ + if (num_found > 1) + *restore = 1; + else + *restore = 0; + + /* Return the part that matches. */ + if (match) + { + char *ret; + char *escstr; + char *newstr; + int current_len; + int match_len; + int spaces = 0; + + current_len = grub_strlen (current_word); + match_len = grub_strlen (match); + + /* Count the number of spaces that have to be escaped. XXX: + More than just spaces have to be escaped. */ + for (escstr = match + current_len; *escstr; escstr++) + if (*escstr == ' ') + spaces++; + + ret = grub_malloc (match_len - current_len + grub_strlen (suffix) + spaces + 1); + newstr = ret; + for (escstr = match + current_len; *escstr; escstr++) + { + if (*escstr == ' ' && cmdline_state != GRUB_PARSER_STATE_QUOTE + && cmdline_state != GRUB_PARSER_STATE_QUOTE) + *(newstr++) = '\\'; + *(newstr++) = *escstr; + } + *newstr = '\0'; + + if (num_found == 1) + grub_strcpy (newstr, suffix); + + if (*ret == '\0') + { + grub_free (ret); + goto fail; + } + + if (argc != 0) + grub_free (argv[0]); + grub_free (match); + return ret; + } + + fail: + if (argc != 0) + grub_free (argv[0]); + grub_free (argv); + grub_free (match); + grub_errno = GRUB_ERR_NONE; + + return 0; +} diff --git a/grub-core/normal/context.c b/grub-core/normal/context.c new file mode 100644 index 0000000..ee53d4a --- /dev/null +++ b/grub-core/normal/context.c @@ -0,0 +1,214 @@ +/* env.c - Environment variables */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,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/env.h> +#include <grub/env_private.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/command.h> +#include <grub/normal.h> +#include <grub/i18n.h> + +struct menu_pointer +{ + grub_menu_t menu; + struct menu_pointer *prev; +}; + +static struct menu_pointer initial_menu; +static struct menu_pointer *current_menu = &initial_menu; + +void +grub_env_unset_menu (void) +{ + current_menu->menu = NULL; +} + +grub_menu_t +grub_env_get_menu (void) +{ + return current_menu->menu; +} + +void +grub_env_set_menu (grub_menu_t nmenu) +{ + current_menu->menu = nmenu; +} + +static grub_err_t +grub_env_new_context (int export_all) +{ + struct grub_env_context *context; + int i; + struct menu_pointer *menu; + + context = grub_zalloc (sizeof (*context)); + if (! context) + return grub_errno; + menu = grub_zalloc (sizeof (*menu)); + if (! menu) + { + grub_free (context); + return grub_errno; + } + + context->prev = grub_current_context; + grub_current_context = context; + + menu->prev = current_menu; + current_menu = menu; + + /* Copy exported variables. */ + for (i = 0; i < HASHSZ; i++) + { + struct grub_env_var *var; + + for (var = context->prev->vars[i]; var; var = var->next) + if (var->global || export_all) + { + if (grub_env_set (var->name, var->value) != GRUB_ERR_NONE) + { + grub_env_context_close (); + return grub_errno; + } + grub_env_export (var->name); + grub_register_variable_hook (var->name, var->read_hook, var->write_hook); + } + } + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_env_context_open (void) +{ + return grub_env_new_context (0); +} + +int grub_extractor_level = 0; + +grub_err_t +grub_env_extractor_open (int source) +{ + grub_extractor_level++; + return grub_env_new_context (source); +} + +grub_err_t +grub_env_context_close (void) +{ + struct grub_env_context *context; + int i; + struct menu_pointer *menu; + + if (! grub_current_context->prev) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "cannot close the initial context"); + + /* Free the variables associated with this context. */ + for (i = 0; i < HASHSZ; i++) + { + struct grub_env_var *p, *q; + + for (p = grub_current_context->vars[i]; p; p = q) + { + q = p->next; + grub_free (p->name); + grub_free (p->value); + grub_free (p); + } + } + + /* Restore the previous context. */ + context = grub_current_context->prev; + grub_free (grub_current_context); + grub_current_context = context; + + menu = current_menu->prev; + if (current_menu->menu) + grub_normal_free_menu (current_menu->menu); + grub_free (current_menu); + current_menu = menu; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_env_extractor_close (int source) +{ + grub_menu_t menu = NULL; + grub_menu_entry_t *last; + grub_err_t err; + + if (source) + { + menu = grub_env_get_menu (); + grub_env_unset_menu (); + } + err = grub_env_context_close (); + + if (source && menu) + { + grub_menu_t menu2; + menu2 = grub_env_get_menu (); + + last = &menu2->entry_list; + while (*last) + last = &(*last)->next; + + *last = menu->entry_list; + menu2->size += menu->size; + } + + grub_extractor_level--; + return err; +} + +static grub_command_t export_cmd; + +static grub_err_t +grub_cmd_export (struct grub_command *cmd __attribute__ ((unused)), + int argc, char **args) +{ + int i; + + if (argc < 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("one argument expected")); + + for (i = 0; i < argc; i++) + grub_env_export (args[i]); + + return 0; +} + +void +grub_context_init (void) +{ + export_cmd = grub_register_command ("export", grub_cmd_export, + N_("ENVVAR [ENVVAR] ..."), + N_("Export variables.")); +} + +void +grub_context_fini (void) +{ + grub_unregister_command (export_cmd); +} diff --git a/grub-core/normal/crypto.c b/grub-core/normal/crypto.c new file mode 100644 index 0000000..d01e6f2 --- /dev/null +++ b/grub-core/normal/crypto.c @@ -0,0 +1,163 @@ +/* crypto.c - support crypto autoload */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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/dl.h> +#include <grub/mm.h> +#include <grub/env.h> +#include <grub/misc.h> +#include <grub/crypto.h> +#include <grub/normal.h> + +struct load_spec +{ + struct load_spec *next; + char *name; + char *modname; +}; + +static struct load_spec *crypto_specs = NULL; + +static void +grub_crypto_autoload (const char *name) +{ + struct load_spec *cur; + grub_dl_t mod; + static int depth = 0; + + /* Some bufio of filesystems may want some crypto modules. + It may result in infinite recursion. Hence this check. */ + if (depth) + return; + depth++; + + for (cur = crypto_specs; cur; cur = cur->next) + if (grub_strcasecmp (name, cur->name) == 0) + { + mod = grub_dl_load (cur->modname); + if (mod) + grub_dl_ref (mod); + grub_errno = GRUB_ERR_NONE; + } + depth--; +} + +static void +grub_crypto_spec_free (void) +{ + struct load_spec *cur, *next; + for (cur = crypto_specs; cur; cur = next) + { + next = cur->next; + grub_free (cur->name); + grub_free (cur->modname); + grub_free (cur); + } + crypto_specs = NULL; +} + + +/* Read the file crypto.lst for auto-loading. */ +void +read_crypto_list (const char *prefix) +{ + char *filename; + grub_file_t file; + char *buf = NULL; + + if (!prefix) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + filename = grub_xasprintf ("%s/" GRUB_TARGET_CPU "-" GRUB_PLATFORM + "/crypto.lst", prefix); + if (!filename) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + file = grub_file_open (filename, GRUB_FILE_TYPE_GRUB_MODULE_LIST); + grub_free (filename); + if (!file) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + /* Override previous crypto.lst. */ + grub_crypto_spec_free (); + + for (;; grub_free (buf)) + { + char *p, *name; + struct load_spec *cur; + + buf = grub_file_getline (file); + + if (! buf) + break; + + name = buf; + while (grub_isspace (name[0])) + name++; + + p = grub_strchr (name, ':'); + if (! p) + continue; + + *p = '\0'; + p++; + while (*p == ' ' || *p == '\t') + p++; + + cur = grub_malloc (sizeof (*cur)); + if (!cur) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + + cur->name = grub_strdup (name); + if (! cur->name) + { + grub_errno = GRUB_ERR_NONE; + grub_free (cur); + continue; + } + + cur->modname = grub_strdup (p); + if (! cur->modname) + { + grub_errno = GRUB_ERR_NONE; + grub_free (cur->name); + grub_free (cur); + continue; + } + cur->next = crypto_specs; + crypto_specs = cur; + } + + grub_file_close (file); + + grub_errno = GRUB_ERR_NONE; + + grub_crypto_autoload_hook = grub_crypto_autoload; +} diff --git a/grub-core/normal/dyncmd.c b/grub-core/normal/dyncmd.c new file mode 100644 index 0000000..719ebf4 --- /dev/null +++ b/grub-core/normal/dyncmd.c @@ -0,0 +1,210 @@ +/* dyncmd.c - support dynamic command */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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/dl.h> +#include <grub/mm.h> +#include <grub/env.h> +#include <grub/misc.h> +#include <grub/command.h> +#include <grub/normal.h> +#include <grub/extcmd.h> +#include <grub/script_sh.h> +#include <grub/i18n.h> + +grub_command_t +grub_dyncmd_get_cmd (grub_command_t cmd) +{ + grub_extcmd_t extcmd = cmd->data; + char *modname; + char *name; + grub_dl_t mod; + + modname = extcmd->data; + mod = grub_dl_load (modname); + if (!mod) + return NULL; + + grub_free (modname); + grub_dl_ref (mod); + + name = (char *) cmd->name; + grub_unregister_extcmd (extcmd); + + cmd = grub_command_find (name); + + grub_free (name); + + return cmd; +} + +static grub_err_t +grub_dyncmd_dispatcher (struct grub_extcmd_context *ctxt, + int argc, char **args) +{ + char *modname; + grub_dl_t mod; + grub_err_t ret; + grub_extcmd_t extcmd = ctxt->extcmd; + grub_command_t cmd = extcmd->cmd; + char *name; + + modname = extcmd->data; + mod = grub_dl_load (modname); + if (!mod) + return grub_errno; + + grub_free (modname); + grub_dl_ref (mod); + + name = (char *) cmd->name; + grub_unregister_extcmd (extcmd); + + cmd = grub_command_find (name); + if (cmd) + { + if (cmd->flags & GRUB_COMMAND_FLAG_BLOCKS && + cmd->flags & GRUB_COMMAND_FLAG_EXTCMD) + ret = grub_extcmd_dispatcher (cmd, argc, args, ctxt->script); + else + ret = (cmd->func) (cmd, argc, args); + } + else + ret = grub_errno; + + grub_free (name); + + return ret; +} + +/* Read the file command.lst for auto-loading. */ +void +read_command_list (const char *prefix) +{ + if (prefix) + { + char *filename; + + filename = grub_xasprintf ("%s/" GRUB_TARGET_CPU "-" GRUB_PLATFORM + "/command.lst", prefix); + if (filename) + { + grub_file_t file; + + file = grub_file_open (filename, GRUB_FILE_TYPE_GRUB_MODULE_LIST); + if (file) + { + char *buf = NULL; + grub_command_t ptr, last = 0, next; + + /* Override previous commands.lst. */ + for (ptr = grub_command_list; ptr; ptr = next) + { + next = ptr->next; + if (ptr->flags & GRUB_COMMAND_FLAG_DYNCMD) + { + if (last) + last->next = ptr->next; + else + grub_command_list = ptr->next; + grub_free (ptr->data); /* extcmd struct */ + grub_free (ptr); + } + else + last = ptr; + } + + for (;; grub_free (buf)) + { + char *p, *name, *modname; + grub_extcmd_t cmd; + int prio = 0; + + buf = grub_file_getline (file); + + if (! buf) + break; + + name = buf; + while (grub_isspace (name[0])) + name++; + + if (*name == '*') + { + name++; + prio++; + } + + if (! grub_isgraph (name[0])) + continue; + + p = grub_strchr (name, ':'); + if (! p) + continue; + + *p = '\0'; + p++; + while (*p == ' ' || *p == '\t') + p++; + + if (! grub_isgraph (*p)) + continue; + + if (grub_dl_get (p)) + continue; + + name = grub_strdup (name); + if (! name) + continue; + + modname = grub_strdup (p); + if (! modname) + { + grub_free (name); + continue; + } + + cmd = grub_register_extcmd_prio (name, + grub_dyncmd_dispatcher, + GRUB_COMMAND_FLAG_BLOCKS + | GRUB_COMMAND_FLAG_EXTCMD + | GRUB_COMMAND_FLAG_DYNCMD, + 0, N_("module isn't loaded"), + 0, prio); + if (! cmd) + { + grub_free (name); + grub_free (modname); + continue; + } + cmd->data = modname; + + /* Update the active flag. */ + grub_command_find (name); + } + + grub_file_close (file); + } + + grub_free (filename); + } + } + + /* Ignore errors. */ + grub_errno = GRUB_ERR_NONE; +} diff --git a/grub-core/normal/main.c b/grub-core/normal/main.c new file mode 100644 index 0000000..c4ebe9e --- /dev/null +++ b/grub-core/normal/main.c @@ -0,0 +1,587 @@ +/* main.c - the normal mode main routine */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,2003,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/kernel.h> +#include <grub/net.h> +#include <grub/normal.h> +#include <grub/dl.h> +#include <grub/misc.h> +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/term.h> +#include <grub/env.h> +#include <grub/parser.h> +#include <grub/reader.h> +#include <grub/menu_viewer.h> +#include <grub/auth.h> +#include <grub/i18n.h> +#include <grub/charset.h> +#include <grub/script_sh.h> +#include <grub/bufio.h> + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_DEFAULT_HISTORY_SIZE 50 + +static int nested_level = 0; +int grub_normal_exit_level = 0; + +void +grub_normal_free_menu (grub_menu_t menu) +{ + grub_menu_entry_t entry = menu->entry_list; + + while (entry) + { + grub_menu_entry_t next_entry = entry->next; + grub_size_t i; + + if (entry->classes) + { + struct grub_menu_entry_class *class; + for (class = entry->classes; class; class = class->next) + grub_free (class->name); + grub_free (entry->classes); + } + + if (entry->args) + { + for (i = 0; entry->args[i]; i++) + grub_free (entry->args[i]); + grub_free (entry->args); + } + + grub_free ((void *) entry->id); + grub_free ((void *) entry->users); + grub_free ((void *) entry->title); + grub_free ((void *) entry->sourcecode); + grub_free (entry); + entry = next_entry; + } + + grub_free (menu); + grub_env_unset_menu (); +} + +/* Helper for read_config_file. */ +static grub_err_t +read_config_file_getline (char **line, int cont __attribute__ ((unused)), + void *data) +{ + grub_file_t file = data; + + while (1) + { + char *buf; + + *line = buf = grub_file_getline (file); + if (! buf) + return grub_errno; + + if (buf[0] == '#') + grub_free (*line); + else + break; + } + + return GRUB_ERR_NONE; +} + +static grub_menu_t +read_config_file (const char *config) +{ + grub_file_t rawfile, file; + char *old_file = 0, *old_dir = 0; + char *config_dir, *ptr = 0; + const char *ctmp; + + grub_menu_t newmenu; + + newmenu = grub_env_get_menu (); + if (! newmenu) + { + newmenu = grub_zalloc (sizeof (*newmenu)); + if (! newmenu) + return 0; + + grub_env_set_menu (newmenu); + } + + /* Try to open the config file. */ + rawfile = grub_file_open (config, GRUB_FILE_TYPE_CONFIG); + if (! rawfile) + return 0; + + file = grub_bufio_open (rawfile, 0); + if (! file) + { + grub_file_close (rawfile); + return 0; + } + + ctmp = grub_env_get ("config_file"); + if (ctmp) + old_file = grub_strdup (ctmp); + ctmp = grub_env_get ("config_directory"); + if (ctmp) + old_dir = grub_strdup (ctmp); + if (*config == '(') + { + grub_env_set ("config_file", config); + config_dir = grub_strdup (config); + } + else + { + /* $root is guranteed to be defined, otherwise open above would fail */ + config_dir = grub_xasprintf ("(%s)%s", grub_env_get ("root"), config); + if (config_dir) + grub_env_set ("config_file", config_dir); + } + if (config_dir) + { + ptr = grub_strrchr (config_dir, '/'); + if (ptr) + *ptr = 0; + grub_env_set ("config_directory", config_dir); + grub_free (config_dir); + } + + grub_env_export ("config_file"); + grub_env_export ("config_directory"); + + while (1) + { + char *line; + + /* Print an error, if any. */ + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + + if ((read_config_file_getline (&line, 0, file)) || (! line)) + break; + + grub_normal_parse_line (line, read_config_file_getline, file); + grub_free (line); + } + + if (old_file) + grub_env_set ("config_file", old_file); + else + grub_env_unset ("config_file"); + if (old_dir) + grub_env_set ("config_directory", old_dir); + else + grub_env_unset ("config_directory"); + grub_free (old_file); + grub_free (old_dir); + + grub_file_close (file); + + return newmenu; +} + +/* Initialize the screen. */ +void +grub_normal_init_page (struct grub_term_output *term, + int y) +{ + grub_ssize_t msg_len; + int posx; + char *msg_formatted; + grub_uint32_t *unicode_msg; + grub_uint32_t *last_position; + + grub_term_cls (term); + + msg_formatted = grub_xasprintf (_("GNU GRUB version %s"), PACKAGE_VERSION); + if (!msg_formatted) + return; + + msg_len = grub_utf8_to_ucs4_alloc (msg_formatted, + &unicode_msg, &last_position); + grub_free (msg_formatted); + + if (msg_len < 0) + { + return; + } + + posx = grub_getstringwidth (unicode_msg, last_position, term); + posx = ((int) grub_term_width (term) - posx) / 2; + if (posx < 0) + posx = 0; + grub_term_gotoxy (term, (struct grub_term_coordinate) { posx, y }); + + grub_print_ucs4 (unicode_msg, last_position, 0, 0, term); + grub_putcode ('\n', term); + grub_putcode ('\n', term); + grub_free (unicode_msg); +} + +static void +read_lists (const char *val) +{ + if (! grub_no_modules) + { + read_command_list (val); + read_fs_list (val); + read_crypto_list (val); + read_terminal_list (val); + } + grub_gettext_reread_prefix (val); +} + +static char * +read_lists_hook (struct grub_env_var *var __attribute__ ((unused)), + const char *val) +{ + read_lists (val); + return val ? grub_strdup (val) : NULL; +} + +/* Read the config file CONFIG and execute the menu interface or + the command line interface if BATCH is false. */ +void +grub_normal_execute (const char *config, int nested, int batch) +{ + grub_menu_t menu = 0; + const char *prefix; + + if (! nested) + { + prefix = grub_env_get ("prefix"); + read_lists (prefix); + grub_register_variable_hook ("prefix", NULL, read_lists_hook); + } + + grub_boot_time ("Executing config file"); + + if (config) + { + menu = read_config_file (config); + + /* Ignore any error. */ + grub_errno = GRUB_ERR_NONE; + } + + grub_boot_time ("Executed config file"); + + if (! batch) + { + if (menu && menu->size) + { + + grub_boot_time ("Entering menu"); + grub_show_menu (menu, nested, 0); + if (nested) + grub_normal_free_menu (menu); + } + } +} + +/* This starts the normal mode. */ +void +grub_enter_normal_mode (const char *config) +{ + grub_boot_time ("Entering normal mode"); + nested_level++; + grub_normal_execute (config, 0, 0); + grub_boot_time ("Entering shell"); + grub_cmdline_run (0, 1); + nested_level--; + if (grub_normal_exit_level) + grub_normal_exit_level--; + grub_boot_time ("Exiting normal mode"); +} + +/* Enter normal mode from rescue mode. */ +static grub_err_t +grub_cmd_normal (struct grub_command *cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + if (argc == 0) + { + /* Guess the config filename. It is necessary to make CONFIG static, + so that it won't get broken by longjmp. */ + char *config; + const char *prefix; + + prefix = grub_env_get ("prefix"); + if (prefix) + { + grub_size_t config_len; + int disable_net_search = 0; + const char *net_search_cfg; + + config_len = grub_strlen (prefix) + + sizeof ("/grub.cfg-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"); + config = grub_malloc (config_len); + + if (!config) + goto quit; + + grub_snprintf (config, config_len, "%s/grub.cfg", prefix); + + net_search_cfg = grub_env_get ("feature_net_search_cfg"); + if (net_search_cfg && net_search_cfg[0] == 'n') + disable_net_search = 1; + + if (grub_strncmp (prefix + 1, "tftp", sizeof ("tftp") - 1) == 0 && + !disable_net_search) + grub_net_search_config_file (config); + + grub_enter_normal_mode (config); + grub_free (config); + } + else + grub_enter_normal_mode (0); + } + else + grub_enter_normal_mode (argv[0]); + +quit: + return 0; +} + +/* Exit from normal mode to rescue mode. */ +static grub_err_t +grub_cmd_normal_exit (struct grub_command *cmd __attribute__ ((unused)), + int argc __attribute__ ((unused)), + char *argv[] __attribute__ ((unused))) +{ + if (nested_level <= grub_normal_exit_level) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "not in normal environment"); + grub_normal_exit_level++; + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_normal_reader_init (int nested) +{ + struct grub_term_output *term; + const char *msg_esc = _("ESC at any time exits."); + char *msg_formatted; + + msg_formatted = grub_xasprintf (_("Minimal BASH-like line editing is supported. For " + "the first word, TAB lists possible command completions. Anywhere " + "else TAB lists possible device or file completions. %s"), + nested ? msg_esc : ""); + if (!msg_formatted) + return grub_errno; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + grub_normal_init_page (term, 1); + grub_term_setcursor (term, 1); + + if (grub_term_width (term) > 3 + STANDARD_MARGIN + 20) + grub_print_message_indented (msg_formatted, 3, STANDARD_MARGIN, term); + else + grub_print_message_indented (msg_formatted, 0, 0, term); + grub_putcode ('\n', term); + grub_putcode ('\n', term); + grub_putcode ('\n', term); + } + grub_free (msg_formatted); + + return 0; +} + +static grub_err_t +grub_normal_read_line_real (char **line, int cont, int nested) +{ + const char *prompt; + + if (cont) + /* TRANSLATORS: it's command line prompt. */ + prompt = _(">"); + else + /* TRANSLATORS: it's command line prompt. */ + prompt = _("grub>"); + + if (!prompt) + return grub_errno; + + while (1) + { + *line = grub_cmdline_get (prompt); + if (*line) + return 0; + + if (cont || nested) + { + grub_free (*line); + *line = 0; + return grub_errno; + } + } + +} + +static grub_err_t +grub_normal_read_line (char **line, int cont, + void *data __attribute__ ((unused))) +{ + return grub_normal_read_line_real (line, cont, 0); +} + +void +grub_cmdline_run (int nested, int force_auth) +{ + grub_err_t err = GRUB_ERR_NONE; + + do + { + err = grub_auth_check_authentication (NULL); + } + while (err && force_auth); + + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + + grub_normal_reader_init (nested); + + while (1) + { + char *line = NULL; + + if (grub_normal_exit_level) + break; + + /* Print an error, if any. */ + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + + grub_normal_read_line_real (&line, 0, nested); + if (! line) + break; + + grub_normal_parse_line (line, grub_normal_read_line, NULL); + grub_free (line); + } +} + +static char * +grub_env_write_pager (struct grub_env_var *var __attribute__ ((unused)), + const char *val) +{ + grub_set_more ((*val == '1')); + return grub_strdup (val); +} + +/* clear */ +static grub_err_t +grub_mini_cmd_clear (struct grub_command *cmd __attribute__ ((unused)), + int argc __attribute__ ((unused)), + char *argv[] __attribute__ ((unused))) +{ + grub_cls (); + return 0; +} + +static grub_command_t cmd_clear; + +static void (*grub_xputs_saved) (const char *str); +static const char *features[] = { + "feature_chainloader_bpb", "feature_ntldr", "feature_platform_search_hint", + "feature_default_font_path", "feature_all_video_module", + "feature_menuentry_id", "feature_menuentry_options", "feature_200_final", + "feature_nativedisk_cmd", "feature_timeout_style" +}; + +GRUB_MOD_INIT(normal) +{ + unsigned i; + + grub_boot_time ("Preparing normal module"); + + /* Previously many modules depended on gzio. Be nice to user and load it. */ + grub_dl_load ("gzio"); + grub_errno = 0; + + grub_normal_auth_init (); + grub_context_init (); + grub_script_init (); + grub_menu_init (); + + grub_xputs_saved = grub_xputs; + grub_xputs = grub_xputs_normal; + + /* Normal mode shouldn't be unloaded. */ + if (mod) + grub_dl_ref (mod); + + cmd_clear = + grub_register_command ("clear", grub_mini_cmd_clear, + 0, N_("Clear the screen.")); + + grub_set_history (GRUB_DEFAULT_HISTORY_SIZE); + + grub_register_variable_hook ("pager", 0, grub_env_write_pager); + grub_env_export ("pager"); + + /* Register a command "normal" for the rescue mode. */ + grub_register_command ("normal", grub_cmd_normal, + 0, N_("Enter normal mode.")); + grub_register_command ("normal_exit", grub_cmd_normal_exit, + 0, N_("Exit from normal mode.")); + + /* Reload terminal colors when these variables are written to. */ + grub_register_variable_hook ("color_normal", NULL, grub_env_write_color_normal); + grub_register_variable_hook ("color_highlight", NULL, grub_env_write_color_highlight); + + /* Preserve hooks after context changes. */ + grub_env_export ("color_normal"); + grub_env_export ("color_highlight"); + + /* Set default color names. */ + grub_env_set ("color_normal", "light-gray/black"); + grub_env_set ("color_highlight", "black/light-gray"); + + for (i = 0; i < ARRAY_SIZE (features); i++) + { + grub_env_set (features[i], "y"); + grub_env_export (features[i]); + } + grub_env_set ("grub_cpu", GRUB_TARGET_CPU); + grub_env_export ("grub_cpu"); + grub_env_set ("grub_platform", GRUB_PLATFORM); + grub_env_export ("grub_platform"); + + grub_boot_time ("Normal module prepared"); +} + +GRUB_MOD_FINI(normal) +{ + grub_context_fini (); + grub_script_fini (); + grub_menu_fini (); + grub_normal_auth_fini (); + + grub_xputs = grub_xputs_saved; + + grub_set_history (0); + grub_register_variable_hook ("pager", 0, 0); + grub_fs_autoload_hook = 0; + grub_unregister_command (cmd_clear); +} diff --git a/grub-core/normal/menu.c b/grub-core/normal/menu.c new file mode 100644 index 0000000..8397886 --- /dev/null +++ b/grub-core/normal/menu.c @@ -0,0 +1,912 @@ +/* menu.c - General supporting functionality for menus. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,2005,2006,2007,2008,2009,2010 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/misc.h> +#include <grub/loader.h> +#include <grub/mm.h> +#include <grub/time.h> +#include <grub/env.h> +#include <grub/menu_viewer.h> +#include <grub/command.h> +#include <grub/parser.h> +#include <grub/auth.h> +#include <grub/i18n.h> +#include <grub/term.h> +#include <grub/script_sh.h> +#include <grub/gfxterm.h> +#include <grub/dl.h> + +/* Time to delay after displaying an error message about a default/fallback + entry failing to boot. */ +#define DEFAULT_ENTRY_ERROR_DELAY_MS 2500 + +grub_err_t (*grub_gfxmenu_try_hook) (int entry, grub_menu_t menu, + int nested) = NULL; + +enum timeout_style { + TIMEOUT_STYLE_MENU, + TIMEOUT_STYLE_COUNTDOWN, + TIMEOUT_STYLE_HIDDEN +}; + +struct timeout_style_name { + const char *name; + enum timeout_style style; +} timeout_style_names[] = { + {"menu", TIMEOUT_STYLE_MENU}, + {"countdown", TIMEOUT_STYLE_COUNTDOWN}, + {"hidden", TIMEOUT_STYLE_HIDDEN}, + {NULL, 0} +}; + +/* Wait until the user pushes any key so that the user + can see what happened. */ +void +grub_wait_after_message (void) +{ + grub_uint64_t endtime; + grub_xputs ("\n"); + grub_printf_ (N_("Press any key to continue...")); + grub_refresh (); + + endtime = grub_get_time_ms () + 10000; + + while (grub_get_time_ms () < endtime + && grub_getkey_noblock () == GRUB_TERM_NO_KEY); + + grub_xputs ("\n"); +} + +/* Get a menu entry by its index in the entry list. */ +grub_menu_entry_t +grub_menu_get_entry (grub_menu_t menu, int no) +{ + grub_menu_entry_t e; + + for (e = menu->entry_list; e && no > 0; e = e->next, no--) + ; + + return e; +} + +/* Get the index of a menu entry associated with a given hotkey, or -1. */ +static int +get_entry_index_by_hotkey (grub_menu_t menu, int hotkey) +{ + grub_menu_entry_t entry; + int i; + + for (i = 0, entry = menu->entry_list; i < menu->size; + i++, entry = entry->next) + if (entry->hotkey == hotkey) + return i; + + return -1; +} + +/* Return the timeout style. If the variable "timeout_style" is not set or + invalid, default to TIMEOUT_STYLE_MENU. */ +static enum timeout_style +get_timeout_style (void) +{ + const char *val; + struct timeout_style_name *style_name; + + val = grub_env_get ("timeout_style"); + if (!val) + return TIMEOUT_STYLE_MENU; + + for (style_name = timeout_style_names; style_name->name; style_name++) + if (grub_strcmp (style_name->name, val) == 0) + return style_name->style; + + return TIMEOUT_STYLE_MENU; +} + +/* Return the current timeout. If the variable "timeout" is not set or + invalid, return -1. */ +int +grub_menu_get_timeout (void) +{ + const char *val; + int timeout; + + val = grub_env_get ("timeout"); + if (! val) + return -1; + + grub_error_push (); + + timeout = (int) grub_strtoul (val, 0, 0); + + /* If the value is invalid, unset the variable. */ + if (grub_errno != GRUB_ERR_NONE) + { + grub_env_unset ("timeout"); + grub_errno = GRUB_ERR_NONE; + timeout = -1; + } + + grub_error_pop (); + + return timeout; +} + +/* Set current timeout in the variable "timeout". */ +void +grub_menu_set_timeout (int timeout) +{ + /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */ + if (timeout > 0) + { + char buf[16]; + + grub_snprintf (buf, sizeof (buf), "%d", timeout); + grub_env_set ("timeout", buf); + } +} + +/* Get the first entry number from the value of the environment variable NAME, + which is a space-separated list of non-negative integers. The entry number + which is returned is stripped from the value of NAME. If no entry number + can be found, -1 is returned. */ +static int +get_and_remove_first_entry_number (const char *name) +{ + const char *val, *tail; + int entry; + + val = grub_env_get (name); + if (! val) + return -1; + + grub_error_push (); + + entry = (int) grub_strtoul (val, &tail, 0); + + if (grub_errno == GRUB_ERR_NONE) + { + /* Skip whitespace to find the next digit. */ + while (*tail && grub_isspace (*tail)) + tail++; + grub_env_set (name, tail); + } + else + { + grub_env_unset (name); + grub_errno = GRUB_ERR_NONE; + entry = -1; + } + + grub_error_pop (); + + return entry; +} + +/* Run a menu entry. */ +static void +grub_menu_execute_entry(grub_menu_entry_t entry, int auto_boot) +{ + grub_err_t err = GRUB_ERR_NONE; + int errs_before; + grub_menu_t menu = NULL; + char *optr, *buf, *oldchosen = NULL, *olddefault = NULL; + const char *ptr, *chosen, *def; + grub_size_t sz = 0; + + if (entry->restricted) + err = grub_auth_check_authentication (entry->users); + + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + + errs_before = grub_err_printed_errors; + + chosen = grub_env_get ("chosen"); + def = grub_env_get ("default"); + + if (entry->submenu) + { + grub_env_context_open (); + menu = grub_zalloc (sizeof (*menu)); + if (! menu) + return; + grub_env_set_menu (menu); + if (auto_boot) + grub_env_set ("timeout", "0"); + } + + for (ptr = entry->id; *ptr; ptr++) + sz += (*ptr == '>') ? 2 : 1; + if (chosen) + { + oldchosen = grub_strdup (chosen); + if (!oldchosen) + grub_print_error (); + } + if (def) + { + olddefault = grub_strdup (def); + if (!olddefault) + grub_print_error (); + } + sz++; + if (chosen) + sz += grub_strlen (chosen); + sz++; + buf = grub_malloc (sz); + if (!buf) + grub_print_error (); + else + { + optr = buf; + if (chosen) + { + optr = grub_stpcpy (optr, chosen); + *optr++ = '>'; + } + for (ptr = entry->id; *ptr; ptr++) + { + if (*ptr == '>') + *optr++ = '>'; + *optr++ = *ptr; + } + *optr = 0; + grub_env_set ("chosen", buf); + grub_env_export ("chosen"); + grub_free (buf); + } + + for (ptr = def; ptr && *ptr; ptr++) + { + if (ptr[0] == '>' && ptr[1] == '>') + { + ptr++; + continue; + } + if (ptr[0] == '>') + break; + } + + if (ptr && ptr[0] && ptr[1]) + grub_env_set ("default", ptr + 1); + else + grub_env_unset ("default"); + + grub_script_execute_new_scope (entry->sourcecode, entry->argc, entry->args); + + if (errs_before != grub_err_printed_errors) + grub_wait_after_message (); + + errs_before = grub_err_printed_errors; + + 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 (errs_before != grub_err_printed_errors) + grub_wait_after_message (); + + if (entry->submenu) + { + if (menu && menu->size) + { + grub_show_menu (menu, 1, auto_boot); + grub_normal_free_menu (menu); + } + grub_env_context_close (); + } + if (oldchosen) + grub_env_set ("chosen", oldchosen); + else + grub_env_unset ("chosen"); + if (olddefault) + grub_env_set ("default", olddefault); + else + grub_env_unset ("default"); + grub_env_unset ("timeout"); +} + +/* Execute ENTRY from the menu MENU, falling back to entries specified + in the environment variable "fallback" if it fails. CALLBACK is a + pointer to a struct of function pointers which are used to allow the + caller provide feedback to the user. */ +static void +grub_menu_execute_with_fallback (grub_menu_t menu, + grub_menu_entry_t entry, + int autobooted, + grub_menu_execute_callback_t callback, + void *callback_data) +{ + int fallback_entry; + + callback->notify_booting (entry, callback_data); + + grub_menu_execute_entry (entry, 1); + + /* Deal with fallback entries. */ + while ((fallback_entry = get_and_remove_first_entry_number ("fallback")) + >= 0) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + + entry = grub_menu_get_entry (menu, fallback_entry); + callback->notify_fallback (entry, callback_data); + grub_menu_execute_entry (entry, 1); + /* If the function call to execute the entry returns at all, then this is + taken to indicate a boot failure. For menu entries that do something + other than actually boot an operating system, this could assume + incorrectly that something failed. */ + } + + if (!autobooted) + callback->notify_failure (callback_data); +} + +static struct grub_menu_viewer *viewers; + +static void +menu_set_chosen_entry (int entry) +{ + struct grub_menu_viewer *cur; + for (cur = viewers; cur; cur = cur->next) + cur->set_chosen_entry (entry, cur->data); +} + +static void +menu_print_timeout (int timeout) +{ + struct grub_menu_viewer *cur; + for (cur = viewers; cur; cur = cur->next) + cur->print_timeout (timeout, cur->data); +} + +static void +menu_fini (void) +{ + struct grub_menu_viewer *cur, *next; + for (cur = viewers; cur; cur = next) + { + next = cur->next; + cur->fini (cur->data); + grub_free (cur); + } + viewers = NULL; +} + +static void +menu_init (int entry, grub_menu_t menu, int nested) +{ + struct grub_term_output *term; + int gfxmenu = 0; + + FOR_ACTIVE_TERM_OUTPUTS(term) + if (term->fullscreen) + { + if (grub_env_get ("theme")) + { + if (!grub_gfxmenu_try_hook) + { + grub_dl_load ("gfxmenu"); + grub_print_error (); + } + if (grub_gfxmenu_try_hook) + { + grub_err_t err; + err = grub_gfxmenu_try_hook (entry, menu, nested); + if(!err) + { + gfxmenu = 1; + break; + } + } + else + grub_error (GRUB_ERR_BAD_MODULE, + N_("module `%s' isn't loaded"), + "gfxmenu"); + grub_print_error (); + grub_wait_after_message (); + } + grub_errno = GRUB_ERR_NONE; + term->fullscreen (); + break; + } + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + grub_err_t err; + + if (grub_strcmp (term->name, "gfxterm") == 0 && gfxmenu) + continue; + + err = grub_menu_try_text (term, entry, menu, nested); + if(!err) + continue; + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } +} + +static void +clear_timeout (void) +{ + struct grub_menu_viewer *cur; + for (cur = viewers; cur; cur = cur->next) + cur->clear_timeout (cur->data); +} + +void +grub_menu_register_viewer (struct grub_menu_viewer *viewer) +{ + viewer->next = viewers; + viewers = viewer; +} + +static int +menuentry_eq (const char *id, const char *spec) +{ + const char *ptr1, *ptr2; + ptr1 = id; + ptr2 = spec; + while (1) + { + if (*ptr2 == '>' && ptr2[1] != '>' && *ptr1 == 0) + return 1; + if (*ptr2 == '>' && ptr2[1] != '>') + return 0; + if (*ptr2 == '>') + ptr2++; + if (*ptr1 != *ptr2) + return 0; + if (*ptr1 == 0) + return 1; + ptr1++; + ptr2++; + } +} + + +/* Get the entry number from the variable NAME. */ +static int +get_entry_number (grub_menu_t menu, const char *name) +{ + const char *val; + int entry; + + val = grub_env_get (name); + if (! val) + return -1; + + grub_error_push (); + + entry = (int) grub_strtoul (val, 0, 0); + + if (grub_errno == GRUB_ERR_BAD_NUMBER) + { + /* See if the variable matches the title of a menu entry. */ + grub_menu_entry_t e = menu->entry_list; + int i; + + grub_errno = GRUB_ERR_NONE; + + for (i = 0; e; i++) + { + if (menuentry_eq (e->title, val) + || menuentry_eq (e->id, val)) + { + entry = i; + break; + } + e = e->next; + } + + if (! e) + entry = -1; + } + + if (grub_errno != GRUB_ERR_NONE) + { + grub_errno = GRUB_ERR_NONE; + entry = -1; + } + + grub_error_pop (); + + return entry; +} + +/* Check whether a second has elapsed since the last tick. If so, adjust + the timer and return 1; otherwise, return 0. */ +static int +has_second_elapsed (grub_uint64_t *saved_time) +{ + grub_uint64_t current_time; + + current_time = grub_get_time_ms (); + if (current_time - *saved_time >= 1000) + { + *saved_time = current_time; + return 1; + } + else + return 0; +} + +static void +print_countdown (struct grub_term_coordinate *pos, int n) +{ + grub_term_restore_pos (pos); + /* NOTE: Do not remove the trailing space characters. + They are required to clear the line. */ + grub_printf ("%d ", n); + grub_refresh (); +} + +#define GRUB_MENU_PAGE_SIZE 10 + +/* Show the menu and handle menu entry selection. Returns the menu entry + index that should be executed or -1 if no entry should be executed (e.g., + Esc pressed to exit a sub-menu or switching menu viewers). + If the return value is not -1, then *AUTO_BOOT is nonzero iff the menu + entry to be executed is a result of an automatic default selection because + of the timeout. */ +static int +run_menu (grub_menu_t menu, int nested, int *auto_boot) +{ + grub_uint64_t saved_time; + int default_entry, current_entry; + int timeout; + enum timeout_style timeout_style; + + default_entry = get_entry_number (menu, "default"); + + /* If DEFAULT_ENTRY is not within the menu entries, fall back to + the first entry. */ + if (default_entry < 0 || default_entry >= menu->size) + default_entry = 0; + + timeout = grub_menu_get_timeout (); + if (timeout < 0) + /* If there is no timeout, the "countdown" and "hidden" styles result in + the system doing nothing and providing no or very little indication + why. Technically this is what the user asked for, but it's not very + useful and likely to be a source of confusion, so we disallow this. */ + grub_env_unset ("timeout_style"); + + timeout_style = get_timeout_style (); + + if (timeout_style == TIMEOUT_STYLE_COUNTDOWN + || timeout_style == TIMEOUT_STYLE_HIDDEN) + { + static struct grub_term_coordinate *pos; + int entry = -1; + + if (timeout_style == TIMEOUT_STYLE_COUNTDOWN && timeout) + { + pos = grub_term_save_pos (); + print_countdown (pos, timeout); + } + + /* Enter interruptible sleep until Escape or a menu hotkey is pressed, + or the timeout expires. */ + saved_time = grub_get_time_ms (); + while (1) + { + int key; + + key = grub_getkey_noblock (); + if (key != GRUB_TERM_NO_KEY) + { + entry = get_entry_index_by_hotkey (menu, key); + if (entry >= 0) + break; + } + if (grub_key_is_interrupt (key)) + { + timeout = -1; + break; + } + + if (timeout > 0 && has_second_elapsed (&saved_time)) + { + timeout--; + if (timeout_style == TIMEOUT_STYLE_COUNTDOWN) + print_countdown (pos, timeout); + } + + if (timeout == 0) + /* We will fall through to auto-booting the default entry. */ + break; + } + + grub_env_unset ("timeout"); + grub_env_unset ("timeout_style"); + if (entry >= 0) + { + *auto_boot = 0; + return entry; + } + } + + /* If timeout is 0, drawing is pointless (and ugly). */ + if (timeout == 0) + { + *auto_boot = 1; + return default_entry; + } + + current_entry = default_entry; + + refresh: + menu_init (current_entry, menu, nested); + + /* Initialize the time. */ + saved_time = grub_get_time_ms (); + + timeout = grub_menu_get_timeout (); + + if (timeout > 0) + menu_print_timeout (timeout); + else + clear_timeout (); + + while (1) + { + int c; + timeout = grub_menu_get_timeout (); + + if (grub_normal_exit_level) + return -1; + + if (timeout > 0 && has_second_elapsed (&saved_time)) + { + timeout--; + grub_menu_set_timeout (timeout); + menu_print_timeout (timeout); + } + + if (timeout == 0) + { + grub_env_unset ("timeout"); + *auto_boot = 1; + menu_fini (); + return default_entry; + } + + c = grub_getkey_noblock (); + + /* Negative values are returned on error. */ + if ((c != GRUB_TERM_NO_KEY) && (c > 0)) + { + if (timeout >= 0) + { + grub_env_unset ("timeout"); + grub_env_unset ("fallback"); + clear_timeout (); + } + + switch (c) + { + case GRUB_TERM_KEY_HOME: + case GRUB_TERM_CTRL | 'a': + current_entry = 0; + menu_set_chosen_entry (current_entry); + break; + + case GRUB_TERM_KEY_END: + case GRUB_TERM_CTRL | 'e': + current_entry = menu->size - 1; + menu_set_chosen_entry (current_entry); + break; + + case GRUB_TERM_KEY_UP: + case GRUB_TERM_CTRL | 'p': + case '^': + if (current_entry > 0) + current_entry--; + menu_set_chosen_entry (current_entry); + break; + + case GRUB_TERM_CTRL | 'n': + case GRUB_TERM_KEY_DOWN: + case 'v': + if (current_entry < menu->size - 1) + current_entry++; + menu_set_chosen_entry (current_entry); + break; + + case GRUB_TERM_CTRL | 'g': + case GRUB_TERM_KEY_PPAGE: + if (current_entry < GRUB_MENU_PAGE_SIZE) + current_entry = 0; + else + current_entry -= GRUB_MENU_PAGE_SIZE; + menu_set_chosen_entry (current_entry); + break; + + case GRUB_TERM_CTRL | 'c': + case GRUB_TERM_KEY_NPAGE: + if (current_entry + GRUB_MENU_PAGE_SIZE < menu->size) + current_entry += GRUB_MENU_PAGE_SIZE; + else + current_entry = menu->size - 1; + menu_set_chosen_entry (current_entry); + break; + + case '\n': + case '\r': + case GRUB_TERM_KEY_RIGHT: + case GRUB_TERM_CTRL | 'f': + menu_fini (); + *auto_boot = 0; + return current_entry; + + case GRUB_TERM_ESC: + if (nested) + { + menu_fini (); + return -1; + } + break; + + case 'c': + menu_fini (); + grub_cmdline_run (1, 0); + goto refresh; + + case 'e': + menu_fini (); + { + grub_menu_entry_t e = grub_menu_get_entry (menu, current_entry); + if (e) + grub_menu_entry_run (e); + } + goto refresh; + + default: + { + int entry; + + entry = get_entry_index_by_hotkey (menu, c); + if (entry >= 0) + { + menu_fini (); + *auto_boot = 0; + return entry; + } + } + break; + } + } + } + + /* Never reach here. */ +} + +/* Callback invoked immediately before a menu entry is executed. */ +static void +notify_booting (grub_menu_entry_t entry, + void *userdata __attribute__((unused))) +{ + grub_printf (" "); + grub_printf_ (N_("Booting `%s'"), entry->title); + grub_printf ("\n\n"); +} + +/* Callback invoked when a default menu entry executed because of a timeout + has failed and an attempt will be made to execute the next fallback + entry, ENTRY. */ +static void +notify_fallback (grub_menu_entry_t entry, + void *userdata __attribute__((unused))) +{ + grub_printf ("\n "); + grub_printf_ (N_("Falling back to `%s'"), entry->title); + grub_printf ("\n\n"); + grub_millisleep (DEFAULT_ENTRY_ERROR_DELAY_MS); +} + +/* Callback invoked when a menu entry has failed and there is no remaining + fallback entry to attempt. */ +static void +notify_execution_failure (void *userdata __attribute__((unused))) +{ + if (grub_errno != GRUB_ERR_NONE) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + grub_printf ("\n "); + grub_printf_ (N_("Failed to boot both default and fallback entries.\n")); + grub_wait_after_message (); +} + +/* Callbacks used by the text menu to provide user feedback when menu entries + are executed. */ +static struct grub_menu_execute_callback execution_callback = +{ + .notify_booting = notify_booting, + .notify_fallback = notify_fallback, + .notify_failure = notify_execution_failure +}; + +static grub_err_t +show_menu (grub_menu_t menu, int nested, int autobooted) +{ + while (1) + { + int boot_entry; + grub_menu_entry_t e; + int auto_boot; + + boot_entry = run_menu (menu, nested, &auto_boot); + if (boot_entry < 0) + break; + + e = grub_menu_get_entry (menu, boot_entry); + if (! e) + continue; /* Menu is empty. */ + + grub_cls (); + + if (auto_boot) + grub_menu_execute_with_fallback (menu, e, autobooted, + &execution_callback, 0); + else + grub_menu_execute_entry (e, 0); + if (autobooted) + break; + } + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_show_menu (grub_menu_t menu, int nested, int autoboot) +{ + grub_err_t err1, err2; + + while (1) + { + err1 = show_menu (menu, nested, autoboot); + autoboot = 0; + grub_print_error (); + + if (grub_normal_exit_level) + break; + + err2 = grub_auth_check_authentication (NULL); + if (err2) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + continue; + } + + break; + } + + return err1; +} diff --git a/grub-core/normal/menu_entry.c b/grub-core/normal/menu_entry.c new file mode 100644 index 0000000..50eef91 --- /dev/null +++ b/grub-core/normal/menu_entry.c @@ -0,0 +1,1460 @@ +/* + * 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 (); +} diff --git a/grub-core/normal/menu_text.c b/grub-core/normal/menu_text.c new file mode 100644 index 0000000..18240e7 --- /dev/null +++ b/grub-core/normal/menu_text.c @@ -0,0 +1,602 @@ +/* menu_text.c - Basic text menu implementation. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2004,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/loader.h> +#include <grub/mm.h> +#include <grub/time.h> +#include <grub/env.h> +#include <grub/menu_viewer.h> +#include <grub/i18n.h> +#include <grub/charset.h> + +static grub_uint8_t grub_color_menu_normal; +static grub_uint8_t grub_color_menu_highlight; + +struct menu_viewer_data +{ + int first, offset; + struct grub_term_screen_geometry geo; + enum { + TIMEOUT_UNKNOWN, + TIMEOUT_NORMAL, + TIMEOUT_TERSE, + TIMEOUT_TERSE_NO_MARGIN + } timeout_msg; + grub_menu_t menu; + struct grub_term_output *term; +}; + +static inline int +grub_term_cursor_x (const struct grub_term_screen_geometry *geo) +{ + return (geo->first_entry_x + geo->entry_width); +} + +grub_size_t +grub_getstringwidth (grub_uint32_t * str, const grub_uint32_t * last_position, + struct grub_term_output *term) +{ + grub_ssize_t width = 0; + + while (str < last_position) + { + struct grub_unicode_glyph glyph; + glyph.ncomb = 0; + str += grub_unicode_aglomerate_comb (str, last_position - str, &glyph); + width += grub_term_getcharwidth (term, &glyph); + grub_unicode_destroy_glyph (&glyph); + } + return width; +} + +static int +grub_print_message_indented_real (const char *msg, int margin_left, + int margin_right, + struct grub_term_output *term, int dry_run) +{ + grub_uint32_t *unicode_msg; + grub_uint32_t *last_position; + grub_size_t msg_len = grub_strlen (msg) + 2; + int ret = 0; + + unicode_msg = grub_calloc (msg_len, sizeof (grub_uint32_t)); + + if (!unicode_msg) + return 0; + + msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len, + (grub_uint8_t *) msg, -1, 0); + + last_position = unicode_msg + msg_len; + *last_position = 0; + + if (dry_run) + ret = grub_ucs4_count_lines (unicode_msg, last_position, margin_left, + margin_right, term); + else + grub_print_ucs4_menu (unicode_msg, last_position, margin_left, + margin_right, term, 0, -1, 0, 0); + + grub_free (unicode_msg); + + return ret; +} + +void +grub_print_message_indented (const char *msg, int margin_left, int margin_right, + struct grub_term_output *term) +{ + grub_print_message_indented_real (msg, margin_left, margin_right, term, 0); +} + +static void +draw_border (struct grub_term_output *term, const struct grub_term_screen_geometry *geo) +{ + int i; + + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1, + geo->first_entry_y - 1 }); + grub_putcode (GRUB_UNICODE_CORNER_UL, term); + for (i = 0; i < geo->entry_width + 1; i++) + grub_putcode (GRUB_UNICODE_HLINE, term); + grub_putcode (GRUB_UNICODE_CORNER_UR, term); + + for (i = 0; i < geo->num_entries; i++) + { + grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1, + geo->first_entry_y + i }); + grub_putcode (GRUB_UNICODE_VLINE, term); + grub_term_gotoxy (term, + (struct grub_term_coordinate) { geo->first_entry_x + geo->entry_width + 1, + geo->first_entry_y + i }); + grub_putcode (GRUB_UNICODE_VLINE, term); + } + + grub_term_gotoxy (term, + (struct grub_term_coordinate) { geo->first_entry_x - 1, + geo->first_entry_y - 1 + geo->num_entries + 1 }); + grub_putcode (GRUB_UNICODE_CORNER_LL, term); + for (i = 0; i < geo->entry_width + 1; i++) + grub_putcode (GRUB_UNICODE_HLINE, term); + grub_putcode (GRUB_UNICODE_CORNER_LR, term); + + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (term, + (struct grub_term_coordinate) { geo->first_entry_x - 1, + (geo->first_entry_y - 1 + geo->num_entries + + GRUB_TERM_MARGIN + 1) }); +} + +static int +print_message (int nested, int edit, struct grub_term_output *term, int dry_run) +{ + int ret = 0; + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + + if (edit) + { + ret += grub_print_message_indented_real (_("Minimum Emacs-like screen editing is \ +supported. TAB lists completions. Press Ctrl-x or F10 to boot, Ctrl-c or F2 for a \ +command-line or ESC to discard edits and return to the GRUB menu."), + STANDARD_MARGIN, STANDARD_MARGIN, + term, dry_run); + } + else + { + char *msg_translated; + + msg_translated = grub_xasprintf (_("Use the %C and %C keys to select which " + "entry is highlighted."), + GRUB_UNICODE_UPARROW, + GRUB_UNICODE_DOWNARROW); + if (!msg_translated) + return 0; + ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN, + STANDARD_MARGIN, term, dry_run); + + grub_free (msg_translated); + + if (nested) + { + ret += grub_print_message_indented_real + (_("Press enter to boot the selected OS, " + "`e' to edit the commands before booting " + "or `c' for a command-line. ESC to return previous menu."), + STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run); + } + else + { + ret += grub_print_message_indented_real + (_("Press enter to boot the selected OS, " + "`e' to edit the commands before booting " + "or `c' for a command-line."), + STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run); + } + } + return ret; +} + +static void +print_entry (int y, int highlight, grub_menu_entry_t entry, + const struct menu_viewer_data *data) +{ + const char *title; + grub_size_t title_len; + grub_ssize_t len; + grub_uint32_t *unicode_title; + grub_ssize_t i; + grub_uint8_t old_color_normal, old_color_highlight; + + title = entry ? entry->title : ""; + title_len = grub_strlen (title); + unicode_title = grub_calloc (title_len, sizeof (*unicode_title)); + if (! unicode_title) + /* XXX How to show this error? */ + return; + + len = grub_utf8_to_ucs4 (unicode_title, title_len, + (grub_uint8_t *) title, -1, 0); + if (len < 0) + { + /* It is an invalid sequence. */ + grub_free (unicode_title); + return; + } + + old_color_normal = grub_term_normal_color; + old_color_highlight = grub_term_highlight_color; + grub_term_normal_color = grub_color_menu_normal; + grub_term_highlight_color = grub_color_menu_highlight; + grub_term_setcolorstate (data->term, highlight + ? GRUB_TERM_COLOR_HIGHLIGHT + : GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (data->term, (struct grub_term_coordinate) { + data->geo.first_entry_x, y }); + + for (i = 0; i < len; i++) + if (unicode_title[i] == '\n' || unicode_title[i] == '\b' + || unicode_title[i] == '\r' || unicode_title[i] == '\e') + unicode_title[i] = ' '; + + if (data->geo.num_entries > 1) + grub_putcode (highlight ? '*' : ' ', data->term); + + grub_print_ucs4_menu (unicode_title, + unicode_title + len, + 0, + data->geo.right_margin, + data->term, 0, 1, + GRUB_UNICODE_RIGHTARROW, 0); + + grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL); + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + grub_term_cursor_x (&data->geo), y }); + + grub_term_normal_color = old_color_normal; + grub_term_highlight_color = old_color_highlight; + + grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL); + grub_free (unicode_title); +} + +static void +print_entries (grub_menu_t menu, const struct menu_viewer_data *data) +{ + grub_menu_entry_t e; + int i; + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + data->geo.first_entry_x + data->geo.entry_width + + data->geo.border + 1, + data->geo.first_entry_y }); + + if (data->geo.num_entries != 1) + { + if (data->first) + grub_putcode (GRUB_UNICODE_UPARROW, data->term); + else + grub_putcode (' ', data->term); + } + e = grub_menu_get_entry (menu, data->first); + + for (i = 0; i < data->geo.num_entries; i++) + { + print_entry (data->geo.first_entry_y + i, data->offset == i, + e, data); + if (e) + e = e->next; + } + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { data->geo.first_entry_x + data->geo.entry_width + + data->geo.border + 1, + data->geo.first_entry_y + data->geo.num_entries - 1 }); + if (data->geo.num_entries == 1) + { + if (data->first && e) + grub_putcode (GRUB_UNICODE_UPDOWNARROW, data->term); + else if (data->first) + grub_putcode (GRUB_UNICODE_UPARROW, data->term); + else if (e) + grub_putcode (GRUB_UNICODE_DOWNARROW, data->term); + else + grub_putcode (' ', data->term); + } + else + { + if (e) + grub_putcode (GRUB_UNICODE_DOWNARROW, data->term); + else + grub_putcode (' ', data->term); + } + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { grub_term_cursor_x (&data->geo), + data->geo.first_entry_y + data->offset }); +} + +/* Initialize the screen. If NESTED is non-zero, assume that this menu + is run from another menu or a command-line. If EDIT is non-zero, show + a message for the menu entry editor. */ +void +grub_menu_init_page (int nested, int edit, + struct grub_term_screen_geometry *geo, + struct grub_term_output *term) +{ + grub_uint8_t old_color_normal, old_color_highlight; + int msg_num_lines; + int bottom_message = 1; + int empty_lines = 1; + int version_msg = 1; + + geo->border = 1; + geo->first_entry_x = 1 /* margin */ + 1 /* border */; + geo->entry_width = grub_term_width (term) - 5; + + geo->first_entry_y = 2 /* two empty lines*/ + + 1 /* GNU GRUB version text */ + 1 /* top border */; + + geo->timeout_lines = 2; + + /* 3 lines for timeout message and bottom margin. 2 lines for the border. */ + geo->num_entries = grub_term_height (term) - geo->first_entry_y + - 1 /* bottom border */ + - 1 /* empty line before info message*/ + - geo->timeout_lines /* timeout */ + - 1 /* empty final line */; + msg_num_lines = print_message (nested, edit, term, 1); + if (geo->num_entries - msg_num_lines < 3 + || geo->entry_width < 10) + { + geo->num_entries += 4; + geo->first_entry_y -= 2; + empty_lines = 0; + geo->first_entry_x -= 1; + geo->entry_width += 1; + } + if (geo->num_entries - msg_num_lines < 3 + || geo->entry_width < 10) + { + geo->num_entries += 2; + geo->first_entry_y -= 1; + geo->first_entry_x -= 1; + geo->entry_width += 2; + geo->border = 0; + } + + if (geo->entry_width <= 0) + geo->entry_width = 1; + + if (geo->num_entries - msg_num_lines < 3 + && geo->timeout_lines == 2) + { + geo->timeout_lines = 1; + geo->num_entries++; + } + + if (geo->num_entries - msg_num_lines < 3) + { + geo->num_entries += 1; + geo->first_entry_y -= 1; + version_msg = 0; + } + + if (geo->num_entries - msg_num_lines >= 2) + geo->num_entries -= msg_num_lines; + else + bottom_message = 0; + + /* By default, use the same colors for the menu. */ + old_color_normal = grub_term_normal_color; + old_color_highlight = grub_term_highlight_color; + grub_color_menu_normal = grub_term_normal_color; + grub_color_menu_highlight = grub_term_highlight_color; + + /* Then give user a chance to replace them. */ + grub_parse_color_name_pair (&grub_color_menu_normal, + grub_env_get ("menu_color_normal")); + grub_parse_color_name_pair (&grub_color_menu_highlight, + grub_env_get ("menu_color_highlight")); + + if (version_msg) + grub_normal_init_page (term, empty_lines); + else + grub_term_cls (term); + + grub_term_normal_color = grub_color_menu_normal; + grub_term_highlight_color = grub_color_menu_highlight; + if (geo->border) + draw_border (term, geo); + grub_term_normal_color = old_color_normal; + grub_term_highlight_color = old_color_highlight; + geo->timeout_y = geo->first_entry_y + geo->num_entries + + geo->border + empty_lines; + if (bottom_message) + { + grub_term_gotoxy (term, + (struct grub_term_coordinate) { GRUB_TERM_MARGIN, + geo->timeout_y }); + + print_message (nested, edit, term, 0); + geo->timeout_y += msg_num_lines; + } + geo->right_margin = grub_term_width (term) + - geo->first_entry_x + - geo->entry_width - 1; +} + +static void +menu_text_print_timeout (int timeout, void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + char *msg_translated = 0; + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { 0, data->geo.timeout_y }); + + if (data->timeout_msg == TIMEOUT_TERSE + || data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN) + msg_translated = grub_xasprintf (_("%ds"), timeout); + else + msg_translated = grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout); + if (!msg_translated) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + + if (data->timeout_msg == TIMEOUT_UNKNOWN) + { + data->timeout_msg = grub_print_message_indented_real (msg_translated, + 3, 1, data->term, 1) + <= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE; + if (data->timeout_msg == TIMEOUT_TERSE) + { + grub_free (msg_translated); + msg_translated = grub_xasprintf (_("%ds"), timeout); + if (grub_term_width (data->term) < 10) + data->timeout_msg = TIMEOUT_TERSE_NO_MARGIN; + } + } + + grub_print_message_indented (msg_translated, + data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 3, + data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 1, + data->term); + grub_free (msg_translated); + + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + grub_term_cursor_x (&data->geo), + data->geo.first_entry_y + data->offset }); + grub_term_refresh (data->term); +} + +static void +menu_text_set_chosen_entry (int entry, void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + int oldoffset = data->offset; + int complete_redraw = 0; + + data->offset = entry - data->first; + if (data->offset > data->geo.num_entries - 1) + { + data->first = entry - (data->geo.num_entries - 1); + data->offset = data->geo.num_entries - 1; + complete_redraw = 1; + } + if (data->offset < 0) + { + data->offset = 0; + data->first = entry; + complete_redraw = 1; + } + if (complete_redraw) + print_entries (data->menu, data); + else + { + print_entry (data->geo.first_entry_y + oldoffset, 0, + grub_menu_get_entry (data->menu, data->first + oldoffset), + data); + print_entry (data->geo.first_entry_y + data->offset, 1, + grub_menu_get_entry (data->menu, data->first + data->offset), + data); + } + grub_term_refresh (data->term); +} + +static void +menu_text_fini (void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + + grub_term_setcursor (data->term, 1); + grub_term_cls (data->term); + grub_free (data); +} + +static void +menu_text_clear_timeout (void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + int i; + + for (i = 0; i < data->geo.timeout_lines;i++) + { + grub_term_gotoxy (data->term, (struct grub_term_coordinate) { + 0, data->geo.timeout_y + i }); + grub_print_spaces (data->term, grub_term_width (data->term) - 1); + } + if (data->geo.num_entries <= 5 && !data->geo.border) + { + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + data->geo.first_entry_x + data->geo.entry_width + + data->geo.border + 1, + data->geo.first_entry_y + data->geo.num_entries - 1 + }); + grub_putcode (' ', data->term); + + data->geo.timeout_lines = 0; + data->geo.num_entries++; + print_entries (data->menu, data); + } + grub_term_gotoxy (data->term, + (struct grub_term_coordinate) { + grub_term_cursor_x (&data->geo), + data->geo.first_entry_y + data->offset }); + grub_term_refresh (data->term); +} + +grub_err_t +grub_menu_try_text (struct grub_term_output *term, + int entry, grub_menu_t menu, int nested) +{ + struct menu_viewer_data *data; + struct grub_menu_viewer *instance; + + instance = grub_zalloc (sizeof (*instance)); + if (!instance) + return grub_errno; + + data = grub_zalloc (sizeof (*data)); + if (!data) + { + grub_free (instance); + return grub_errno; + } + + data->term = term; + instance->data = data; + instance->set_chosen_entry = menu_text_set_chosen_entry; + instance->print_timeout = menu_text_print_timeout; + instance->clear_timeout = menu_text_clear_timeout; + instance->fini = menu_text_fini; + + data->menu = menu; + + data->offset = entry; + data->first = 0; + + grub_term_setcursor (data->term, 0); + grub_menu_init_page (nested, 0, &data->geo, data->term); + + if (data->offset > data->geo.num_entries - 1) + { + data->first = data->offset - (data->geo.num_entries - 1); + data->offset = data->geo.num_entries - 1; + } + + print_entries (menu, data); + grub_term_refresh (data->term); + grub_menu_register_viewer (instance); + + return GRUB_ERR_NONE; +} diff --git a/grub-core/normal/misc.c b/grub-core/normal/misc.c new file mode 100644 index 0000000..f7e9e3a --- /dev/null +++ b/grub-core/normal/misc.c @@ -0,0 +1,194 @@ +/* misc.c - miscellaneous functions */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2005,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/disk.h> +#include <grub/fs.h> +#include <grub/err.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/datetime.h> +#include <grub/term.h> +#include <grub/i18n.h> +#include <grub/partition.h> + +static const char *grub_human_sizes[3][6] = + { + /* This algorithm in reality would work only up to (2^64) / 100 B = 81 PiB. + Put here all possible suffixes it can produce so no array bounds check + is needed. + */ + /* TRANSLATORS: that's the list of binary unit prefixes. */ + { N_("B"), N_("KiB"), N_("MiB"), N_("GiB"), N_("TiB"), N_("PiB")}, + /* TRANSLATORS: that's the list of binary unit prefixes. */ + { "", N_("K"), N_("M"), N_("G"), N_("T"), N_("P") }, + /* TRANSLATORS: that's the list of binary unit prefixes. */ + { N_("B/s"), N_("KiB/s"), N_("MiB/s"), N_("GiB/s"), N_("TiB/s"), N_("PiB/s"), }, + }; + +const char * +grub_get_human_size (grub_uint64_t size, enum grub_human_size_type type) +{ + grub_uint64_t fsize; + unsigned units = 0; + static char buf[30]; + const char *umsg; + + if (type != GRUB_HUMAN_SIZE_SPEED) + fsize = size * 100ULL; + else + fsize = size; + + /* Since 2^64 / 1024^5 < 102400, this can give at most 5 iterations. + So units <=5, so impossible to go past the end of array. + */ + while (fsize >= 102400) + { + fsize = (fsize + 512) / 1024; + units++; + } + + umsg = _(grub_human_sizes[type][units]); + + if (units || type == GRUB_HUMAN_SIZE_SPEED) + { + grub_uint64_t whole, fraction; + + whole = grub_divmod64 (fsize, 100, &fraction); + grub_snprintf (buf, sizeof (buf), + "%" PRIuGRUB_UINT64_T + ".%02" PRIuGRUB_UINT64_T "%s", whole, fraction, + umsg); + } + else + grub_snprintf (buf, sizeof (buf), "%llu%s", (unsigned long long) size, + umsg); + return buf; +} + +/* Print the information on the device NAME. */ +grub_err_t +grub_normal_print_device_info (const char *name) +{ + grub_device_t dev; + char *p; + + p = grub_strchr (name, ','); + if (p) + { + grub_xputs ("\t"); + grub_printf_ (N_("Partition %s:"), name); + grub_xputs (" "); + } + else + { + grub_printf_ (N_("Device %s:"), name); + grub_xputs (" "); + } + + dev = grub_device_open (name); + if (! dev) + grub_printf ("%s", _("Filesystem cannot be accessed")); + else if (dev->disk) + { + grub_fs_t fs; + + fs = grub_fs_probe (dev); + /* Ignore all errors. */ + grub_errno = 0; + + if (fs) + { + const char *fsname = fs->name; + if (grub_strcmp (fsname, "ext2") == 0) + fsname = "ext*"; + grub_printf_ (N_("Filesystem type %s"), fsname); + if (fs->fs_label) + { + char *label; + (fs->fs_label) (dev, &label); + if (grub_errno == GRUB_ERR_NONE) + { + if (label && grub_strlen (label)) + { + grub_xputs (" "); + grub_printf_ (N_("- Label `%s'"), label); + } + grub_free (label); + } + grub_errno = GRUB_ERR_NONE; + } + if (fs->fs_mtime) + { + grub_int64_t tm; + struct grub_datetime datetime; + (fs->fs_mtime) (dev, &tm); + if (grub_errno == GRUB_ERR_NONE) + { + grub_unixtime2datetime (tm, &datetime); + grub_xputs (" "); + /* TRANSLATORS: Arguments are year, month, day, hour, minute, + second, day of the week (translated). */ + grub_printf_ (N_("- Last modification time %d-%02d-%02d " + "%02d:%02d:%02d %s"), + datetime.year, datetime.month, datetime.day, + datetime.hour, datetime.minute, datetime.second, + grub_get_weekday_name (&datetime)); + + } + grub_errno = GRUB_ERR_NONE; + } + if (fs->fs_uuid) + { + char *uuid; + (fs->fs_uuid) (dev, &uuid); + if (grub_errno == GRUB_ERR_NONE) + { + if (uuid && grub_strlen (uuid)) + grub_printf (", UUID %s", uuid); + grub_free (uuid); + } + grub_errno = GRUB_ERR_NONE; + } + } + else + grub_printf ("%s", _("No known filesystem detected")); + + if (dev->disk->partition) + grub_printf (_(" - Partition start at %llu%sKiB"), + (unsigned long long) (grub_partition_get_start (dev->disk->partition) >> 1), + (grub_partition_get_start (dev->disk->partition) & 1) ? ".5" : "" ); + else + grub_printf_ (N_(" - Sector size %uB"), 1 << dev->disk->log_sector_size); + if (grub_disk_native_sectors (dev->disk) == GRUB_DISK_SIZE_UNKNOWN) + grub_puts_ (N_(" - Total size unknown")); + else + grub_printf (_(" - Total size %llu%sKiB"), + (unsigned long long) (grub_disk_native_sectors (dev->disk) >> 1), + /* TRANSLATORS: Replace dot with appropriate decimal separator for + your language. */ + (grub_disk_native_sectors (dev->disk) & 1) ? _(".5") : ""); + } + + if (dev) + grub_device_close (dev); + + grub_xputs ("\n"); + return grub_errno; +} diff --git a/grub-core/normal/term.c b/grub-core/normal/term.c new file mode 100644 index 0000000..cc8c173 --- /dev/null +++ b/grub-core/normal/term.c @@ -0,0 +1,1098 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,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/term.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/file.h> +#include <grub/dl.h> +#include <grub/env.h> +#include <grub/normal.h> +#include <grub/charset.h> +#include <grub/i18n.h> + +struct term_state +{ + struct term_state *next; + struct grub_unicode_glyph *backlog_glyphs; + const grub_uint32_t *backlog_ucs4; + int backlog_fixed_tab; + grub_size_t backlog_len; + + int bidi_stack_depth; + grub_uint8_t bidi_stack[GRUB_BIDI_MAX_EXPLICIT_LEVEL]; + int invalid_pushes; + + void *free; + int num_lines; + char *term_name; +}; + +static int +print_ucs4_real (const grub_uint32_t * str, + const grub_uint32_t * last_position, + int margin_left, int margin_right, + struct grub_term_output *term, int backlog, + int dry_run, int fixed_tab, unsigned skip_lines, + unsigned max_lines, + grub_uint32_t contchar, int fill_right, + struct grub_term_pos *pos); + +static struct term_state *term_states = NULL; + +/* If the more pager is active. */ +static int grub_more; + +static void +putcode_real (grub_uint32_t code, struct grub_term_output *term, int fixed_tab); + +void +grub_normal_reset_more (void) +{ + static struct term_state *state; + for (state = term_states; state; state = state->next) + state->num_lines = 0; +} + +static void +print_more (void) +{ + char key; + struct grub_term_coordinate *pos; + grub_term_output_t term; + grub_uint32_t *unicode_str, *unicode_last_position; + + /* TRANSLATORS: This has to fit on one line. It's ok to include few + words but don't write poems. */ + grub_utf8_to_ucs4_alloc (_("--MORE--"), &unicode_str, + &unicode_last_position); + + if (!unicode_str) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + pos = grub_term_save_pos (); + + grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT); + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + grub_print_ucs4 (unicode_str, unicode_last_position, 0, 0, term); + } + grub_setcolorstate (GRUB_TERM_COLOR_NORMAL); + + grub_free (unicode_str); + + key = grub_getkey (); + + /* Remove the message. */ + grub_term_restore_pos (pos); + FOR_ACTIVE_TERM_OUTPUTS(term) + grub_print_spaces (term, 8); + grub_term_restore_pos (pos); + grub_free (pos); + + /* Scroll one line or an entire page, depending on the key. */ + + if (key == '\r' || key =='\n') + { + static struct term_state *state; + for (state = term_states; state; state = state->next) + state->num_lines--; + } + else + grub_normal_reset_more (); +} + +void +grub_set_more (int onoff) +{ + grub_more = !!onoff; + grub_normal_reset_more (); +} + +enum + { + GRUB_CP437_UPDOWNARROW = 0x12, + GRUB_CP437_UPARROW = 0x18, + GRUB_CP437_DOWNARROW = 0x19, + GRUB_CP437_RIGHTARROW = 0x1a, + GRUB_CP437_LEFTARROW = 0x1b, + GRUB_CP437_VLINE = 0xb3, + GRUB_CP437_CORNER_UR = 0xbf, + GRUB_CP437_CORNER_LL = 0xc0, + GRUB_CP437_HLINE = 0xc4, + GRUB_CP437_CORNER_LR = 0xd9, + GRUB_CP437_CORNER_UL = 0xda, + }; + +static grub_uint32_t +map_code (grub_uint32_t in, struct grub_term_output *term) +{ + if (in <= 0x7f) + return in; + + switch (term->flags & GRUB_TERM_CODE_TYPE_MASK) + { + case GRUB_TERM_CODE_TYPE_CP437: + switch (in) + { + case GRUB_UNICODE_LEFTARROW: + return GRUB_CP437_LEFTARROW; + case GRUB_UNICODE_UPARROW: + return GRUB_CP437_UPARROW; + case GRUB_UNICODE_UPDOWNARROW: + return GRUB_CP437_UPDOWNARROW; + case GRUB_UNICODE_RIGHTARROW: + return GRUB_CP437_RIGHTARROW; + case GRUB_UNICODE_DOWNARROW: + return GRUB_CP437_DOWNARROW; + case GRUB_UNICODE_HLINE: + return GRUB_CP437_HLINE; + case GRUB_UNICODE_VLINE: + return GRUB_CP437_VLINE; + case GRUB_UNICODE_CORNER_UL: + return GRUB_CP437_CORNER_UL; + case GRUB_UNICODE_CORNER_UR: + return GRUB_CP437_CORNER_UR; + case GRUB_UNICODE_CORNER_LL: + return GRUB_CP437_CORNER_LL; + case GRUB_UNICODE_CORNER_LR: + return GRUB_CP437_CORNER_LR; + } + return '?'; + case GRUB_TERM_CODE_TYPE_ASCII: + /* Better than nothing. */ + switch (in) + { + case GRUB_UNICODE_LEFTARROW: + return '<'; + + case GRUB_UNICODE_UPARROW: + return '^'; + + case GRUB_UNICODE_RIGHTARROW: + return '>'; + + case GRUB_UNICODE_DOWNARROW: + return 'v'; + + case GRUB_UNICODE_HLINE: + return '-'; + + case GRUB_UNICODE_VLINE: + return '|'; + + case GRUB_UNICODE_CORNER_UL: + case GRUB_UNICODE_CORNER_UR: + case GRUB_UNICODE_CORNER_LL: + case GRUB_UNICODE_CORNER_LR: + return '+'; + + } + return '?'; + } + return in; +} + +void +grub_puts_terminal (const char *str, struct grub_term_output *term) +{ + grub_uint32_t *unicode_str, *unicode_last_position; + grub_error_push (); + grub_utf8_to_ucs4_alloc (str, &unicode_str, + &unicode_last_position); + grub_error_pop (); + if (!unicode_str) + { + for (; *str; str++) + { + struct grub_unicode_glyph c = + { + .variant = 0, + .attributes = 0, + .ncomb = 0, + .estimated_width = 1, + .base = *str + }; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + (term->putchar) (term, &c); + } + if (*str == '\n') + { + c.base = '\r'; + FOR_ACTIVE_TERM_OUTPUTS(term) + { + (term->putchar) (term, &c); + } + } + } + return; + } + + print_ucs4_real (unicode_str, unicode_last_position, 0, 0, term, + 0, 0, 0, 0, -1, 0, 0, 0); + grub_free (unicode_str); +} + +struct grub_term_coordinate * +grub_term_save_pos (void) +{ + struct grub_term_output *cur; + unsigned cnt = 0; + struct grub_term_coordinate *ret, *ptr; + + FOR_ACTIVE_TERM_OUTPUTS(cur) + cnt++; + + ret = grub_calloc (cnt, sizeof (ret[0])); + if (!ret) + return NULL; + + ptr = ret; + FOR_ACTIVE_TERM_OUTPUTS(cur) + *ptr++ = grub_term_getxy (cur); + + return ret; +} + +void +grub_term_restore_pos (struct grub_term_coordinate *pos) +{ + struct grub_term_output *cur; + struct grub_term_coordinate *ptr = pos; + + if (!pos) + return; + + FOR_ACTIVE_TERM_OUTPUTS(cur) + { + grub_term_gotoxy (cur, *ptr); + ptr++; + } +} + +static void +grub_terminal_autoload_free (void) +{ + struct grub_term_autoload *cur, *next; + unsigned i; + for (i = 0; i < 2; i++) + for (cur = i ? grub_term_input_autoload : grub_term_output_autoload; + cur; cur = next) + { + next = cur->next; + grub_free (cur->name); + grub_free (cur->modname); + grub_free (cur); + } + grub_term_input_autoload = NULL; + grub_term_output_autoload = NULL; +} + +/* Read the file terminal.lst for auto-loading. */ +void +read_terminal_list (const char *prefix) +{ + char *filename; + grub_file_t file; + char *buf = NULL; + + if (!prefix) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + filename = grub_xasprintf ("%s/" GRUB_TARGET_CPU "-" GRUB_PLATFORM + "/terminal.lst", prefix); + if (!filename) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + file = grub_file_open (filename, GRUB_FILE_TYPE_GRUB_MODULE_LIST); + grub_free (filename); + if (!file) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + /* Override previous terminal.lst. */ + grub_terminal_autoload_free (); + + for (;; grub_free (buf)) + { + char *p, *name; + struct grub_term_autoload *cur; + struct grub_term_autoload **target = NULL; + + buf = grub_file_getline (file); + + if (! buf) + break; + + p = buf; + while (grub_isspace (p[0])) + p++; + + switch (p[0]) + { + case 'i': + target = &grub_term_input_autoload; + break; + + case 'o': + target = &grub_term_output_autoload; + break; + } + if (!target) + continue; + + name = p + 1; + + p = grub_strchr (name, ':'); + if (! p) + continue; + *p = 0; + + p++; + while (*p == ' ' || *p == '\t') + p++; + + cur = grub_malloc (sizeof (*cur)); + if (!cur) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + + cur->name = grub_strdup (name); + if (! cur->name) + { + grub_errno = GRUB_ERR_NONE; + grub_free (cur); + continue; + } + + cur->modname = grub_strdup (p); + if (! cur->modname) + { + grub_errno = GRUB_ERR_NONE; + grub_free (cur->name); + grub_free (cur); + continue; + } + cur->next = *target; + *target = cur; + } + + grub_file_close (file); + + grub_errno = GRUB_ERR_NONE; +} + +static void +putglyph (const struct grub_unicode_glyph *c, struct grub_term_output *term, + int fixed_tab) +{ + struct grub_unicode_glyph c2 = + { + .variant = 0, + .attributes = 0, + .ncomb = 0, + .estimated_width = 1 + }; + + if (c->base == '\t' && fixed_tab) + { + int n; + + n = GRUB_TERM_TAB_WIDTH; + c2.base = ' '; + while (n--) + (term->putchar) (term, &c2); + + return; + } + + if (c->base == '\t' && term->getxy) + { + int n; + + n = GRUB_TERM_TAB_WIDTH - ((term->getxy (term).x) + % GRUB_TERM_TAB_WIDTH); + c2.base = ' '; + while (n--) + (term->putchar) (term, &c2); + + return; + } + + if ((term->flags & GRUB_TERM_CODE_TYPE_MASK) + == GRUB_TERM_CODE_TYPE_UTF8_LOGICAL + || (term->flags & GRUB_TERM_CODE_TYPE_MASK) + == GRUB_TERM_CODE_TYPE_UTF8_VISUAL) + { + int i; + c2.estimated_width = grub_term_getcharwidth (term, c); + for (i = -1; i < (int) c->ncomb; i++) + { + grub_uint8_t u8[20], *ptr; + grub_uint32_t code; + + if (i == -1) + { + code = c->base; + if ((term->flags & GRUB_TERM_CODE_TYPE_MASK) + == GRUB_TERM_CODE_TYPE_UTF8_VISUAL) + { + if ((c->attributes & GRUB_UNICODE_GLYPH_ATTRIBUTE_MIRROR)) + code = grub_unicode_mirror_code (code); + code = grub_unicode_shape_code (code, c->attributes); + } + } + else + code = grub_unicode_get_comb (c) [i].code; + + grub_ucs4_to_utf8 (&code, 1, u8, sizeof (u8)); + + for (ptr = u8; *ptr; ptr++) + { + c2.base = *ptr; + (term->putchar) (term, &c2); + c2.estimated_width = 0; + } + } + c2.estimated_width = 1; + } + else + (term->putchar) (term, c); + + if (c->base == '\n') + { + c2.base = '\r'; + (term->putchar) (term, &c2); + } +} + +static void +putcode_real (grub_uint32_t code, struct grub_term_output *term, int fixed_tab) +{ + struct grub_unicode_glyph c = + { + .variant = 0, + .attributes = 0, + .ncomb = 0, + .estimated_width = 1 + }; + + c.base = map_code (code, term); + putglyph (&c, term, fixed_tab); +} + +/* Put a Unicode character. */ +void +grub_putcode (grub_uint32_t code, struct grub_term_output *term) +{ + /* Combining character by itself? */ + if (grub_unicode_get_comb_type (code) != GRUB_UNICODE_COMB_NONE) + return; + + putcode_real (code, term, 0); +} + +static grub_ssize_t +get_maxwidth (struct grub_term_output *term, + int margin_left, int margin_right) +{ + struct grub_unicode_glyph space_glyph = { + .base = ' ', + .variant = 0, + .attributes = 0, + .ncomb = 0, + }; + return (grub_term_width (term) + - grub_term_getcharwidth (term, &space_glyph) + * (margin_left + margin_right) - 1); +} + +static grub_ssize_t +get_startwidth (struct grub_term_output *term, + int margin_left) +{ + return (term->getxy (term).x) - margin_left; +} + +static void +fill_margin (struct grub_term_output *term, int r) +{ + int sp = (term->getwh (term).x) + - (term->getxy (term).x) - r; + if (sp > 0) + grub_print_spaces (term, sp); +} + +static int +print_ucs4_terminal (const grub_uint32_t * str, + const grub_uint32_t * last_position, + int margin_left, int margin_right, + struct grub_term_output *term, + struct term_state *state, + int dry_run, int fixed_tab, unsigned skip_lines, + unsigned max_lines, + grub_uint32_t contchar, + int primitive_wrap, int fill_right, struct grub_term_pos *pos) +{ + const grub_uint32_t *ptr; + grub_ssize_t startwidth = dry_run ? 0 : get_startwidth (term, margin_left); + grub_ssize_t line_width = startwidth; + grub_ssize_t lastspacewidth = 0; + grub_ssize_t max_width = get_maxwidth (term, margin_left, margin_right); + const grub_uint32_t *line_start = str, *last_space = str - 1; + int lines = 0; + int i; + struct term_state local_state; + + if (!state) + { + grub_memset (&local_state, 0, sizeof (local_state)); + state = &local_state; + } + + for (i = 0; i < state->bidi_stack_depth; i++) + putcode_real (state->bidi_stack[i] | (GRUB_UNICODE_LRE & ~0xff), + term, fixed_tab); + + for (ptr = str; ptr < last_position; ptr++) + { + grub_ssize_t last_width = 0; + + switch (*ptr) + { + case GRUB_UNICODE_LRE: + case GRUB_UNICODE_RLE: + case GRUB_UNICODE_LRO: + case GRUB_UNICODE_RLO: + if (state->bidi_stack_depth >= (int) ARRAY_SIZE (state->bidi_stack)) + state->invalid_pushes++; + else + state->bidi_stack[state->bidi_stack_depth++] = *ptr; + break; + case GRUB_UNICODE_PDF: + if (state->invalid_pushes) + state->invalid_pushes--; + else if (state->bidi_stack_depth) + state->bidi_stack_depth--; + break; + } + if (grub_unicode_get_comb_type (*ptr) == GRUB_UNICODE_COMB_NONE) + { + struct grub_unicode_glyph c = { + .variant = 0, + .attributes = 0, + .ncomb = 0, + }; + c.base = *ptr; + if (pos) + { + pos[ptr - str].x = line_width; + pos[ptr - str].y = lines; + pos[ptr - str].valid = 1; + } + line_width += last_width = grub_term_getcharwidth (term, &c); + } + + if (*ptr == ' ' && !primitive_wrap) + { + lastspacewidth = line_width; + last_space = ptr; + } + + if (line_width > max_width || *ptr == '\n') + { + const grub_uint32_t *ptr2; + int wasn = (*ptr == '\n'); + + if (wasn) + { + state->bidi_stack_depth = 0; + state->invalid_pushes = 0; + } + + if (line_width > max_width && last_space > line_start) + ptr = last_space; + else if (line_width > max_width + && line_start == str && line_width - lastspacewidth < max_width - 5) + { + ptr = str; + lastspacewidth = startwidth; + } + else + lastspacewidth = line_width - last_width; + + lines++; + + if (!skip_lines && !dry_run) + { + for (ptr2 = line_start; ptr2 < ptr; ptr2++) + { + /* Skip combining characters on non-UTF8 terminals. */ + if ((term->flags & GRUB_TERM_CODE_TYPE_MASK) + != GRUB_TERM_CODE_TYPE_UTF8_LOGICAL + && grub_unicode_get_comb_type (*ptr2) + != GRUB_UNICODE_COMB_NONE) + continue; + putcode_real (*ptr2, term, fixed_tab); + } + + if (!wasn && contchar) + putcode_real (contchar, term, fixed_tab); + if (fill_right) + fill_margin (term, margin_right); + + if (!contchar || max_lines != 1) + grub_putcode ('\n', term); + if (state != &local_state && ++state->num_lines + >= (grub_ssize_t) grub_term_height (term) - 2) + { + state->backlog_ucs4 = (ptr == last_space || *ptr == '\n') + ? ptr + 1 : ptr; + state->backlog_len = last_position - state->backlog_ucs4; + state->backlog_fixed_tab = fixed_tab; + return 1; + } + } + + line_width -= lastspacewidth; + if (ptr == last_space || *ptr == '\n') + ptr++; + else if (pos) + { + pos[ptr - str].x = line_width - last_width; + pos[ptr - str].y = lines; + pos[ptr - str].valid = 1; + } + + line_start = ptr; + + if (skip_lines) + skip_lines--; + else if (max_lines != (unsigned) -1) + { + max_lines--; + if (!max_lines) + break; + } + if (!skip_lines && !dry_run) + { + if (!contchar) + grub_print_spaces (term, margin_left); + else + grub_term_gotoxy (term, (struct grub_term_coordinate) + { margin_left, grub_term_getxy (term).y }); + for (i = 0; i < state->bidi_stack_depth; i++) + putcode_real (state->bidi_stack[i] | (GRUB_UNICODE_LRE & ~0xff), + term, fixed_tab); + } + } + } + + if (pos) + { + pos[ptr - str].x = line_width; + pos[ptr - str].y = lines; + pos[ptr - str].valid = 1; + } + + if (line_start < last_position) + lines++; + if (!dry_run && !skip_lines && max_lines) + { + const grub_uint32_t *ptr2; + + for (ptr2 = line_start; ptr2 < last_position; ptr2++) + { + /* Skip combining characters on non-UTF8 terminals. */ + if ((term->flags & GRUB_TERM_CODE_TYPE_MASK) + != GRUB_TERM_CODE_TYPE_UTF8_LOGICAL + && grub_unicode_get_comb_type (*ptr2) + != GRUB_UNICODE_COMB_NONE) + continue; + putcode_real (*ptr2, term, fixed_tab); + } + + if (fill_right) + fill_margin (term, margin_right); + } + return dry_run ? lines : 0; +} + +static struct term_state * +find_term_state (struct grub_term_output *term) +{ + struct term_state *state; + for (state = term_states; state; state = state->next) + if (grub_strcmp (state->term_name, term->name) == 0) + return state; + + state = grub_zalloc (sizeof (*state)); + if (!state) + { + grub_errno = GRUB_ERR_NONE; + return NULL; + } + + state->term_name = grub_strdup (term->name); + state->next = term_states; + term_states = state; + + return state; +} + +static int +put_glyphs_terminal (struct grub_unicode_glyph *visual, + grub_ssize_t visual_len, + int margin_left, int margin_right, + struct grub_term_output *term, + struct term_state *state, int fixed_tab, + grub_uint32_t contchar, + int fill_right) +{ + struct grub_unicode_glyph *visual_ptr; + int since_last_nl = 1; + for (visual_ptr = visual; visual_ptr < visual + visual_len; visual_ptr++) + { + if (visual_ptr->base == '\n' && fill_right) + fill_margin (term, margin_right); + + putglyph (visual_ptr, term, fixed_tab); + since_last_nl++; + if (visual_ptr->base == '\n') + { + since_last_nl = 0; + if (state && ++state->num_lines + >= (grub_ssize_t) grub_term_height (term) - 2) + { + state->backlog_glyphs = visual_ptr + 1; + state->backlog_len = visual_len - (visual_ptr - visual) - 1; + state->backlog_fixed_tab = fixed_tab; + return 1; + } + + if (!contchar) + grub_print_spaces (term, margin_left); + else + grub_term_gotoxy (term, + (struct grub_term_coordinate) + { margin_left, grub_term_getxy (term).y }); + } + grub_unicode_destroy_glyph (visual_ptr); + } + if (fill_right && since_last_nl) + fill_margin (term, margin_right); + + return 0; +} + +static int +print_backlog (struct grub_term_output *term, + int margin_left, int margin_right) +{ + struct term_state *state = find_term_state (term); + + if (!state) + return 0; + + if (state->backlog_ucs4) + { + int ret; + ret = print_ucs4_terminal (state->backlog_ucs4, + state->backlog_ucs4 + state->backlog_len, + margin_left, margin_right, term, state, 0, + state->backlog_fixed_tab, 0, -1, 0, 0, + 0, 0); + if (!ret) + { + grub_free (state->free); + state->free = NULL; + state->backlog_len = 0; + state->backlog_ucs4 = 0; + } + return ret; + } + + if (state->backlog_glyphs) + { + int ret; + ret = put_glyphs_terminal (state->backlog_glyphs, + state->backlog_len, + margin_left, margin_right, term, state, + state->backlog_fixed_tab, 0, 0); + if (!ret) + { + grub_free (state->free); + state->free = NULL; + state->backlog_len = 0; + state->backlog_glyphs = 0; + } + return ret; + } + + return 0; +} + +static grub_size_t +getcharwidth (const struct grub_unicode_glyph *c, void *term) +{ + return grub_term_getcharwidth (term, c); +} + +static int +print_ucs4_real (const grub_uint32_t * str, + const grub_uint32_t * last_position, + int margin_left, int margin_right, + struct grub_term_output *term, int backlog, + int dry_run, int fixed_tab, unsigned skip_lines, + unsigned max_lines, + grub_uint32_t contchar, int fill_right, + struct grub_term_pos *pos) +{ + struct term_state *state = NULL; + + if (!dry_run) + { + struct grub_term_coordinate xy; + if (backlog) + state = find_term_state (term); + + xy = term->getxy (term); + + if (xy.x < margin_left) + { + if (!contchar) + grub_print_spaces (term, margin_left - xy.x); + else + grub_term_gotoxy (term, (struct grub_term_coordinate) {margin_left, + xy.y}); + } + } + + if ((term->flags & GRUB_TERM_CODE_TYPE_MASK) + == GRUB_TERM_CODE_TYPE_VISUAL_GLYPHS + || (term->flags & GRUB_TERM_CODE_TYPE_MASK) + == GRUB_TERM_CODE_TYPE_UTF8_VISUAL) + { + grub_ssize_t visual_len; + struct grub_unicode_glyph *visual; + grub_ssize_t visual_len_show; + struct grub_unicode_glyph *visual_show; + int ret; + struct grub_unicode_glyph *vptr; + + visual_len = grub_bidi_logical_to_visual (str, last_position - str, + &visual, getcharwidth, term, + get_maxwidth (term, + margin_left, + margin_right), + dry_run ? 0 : get_startwidth (term, + margin_left), + contchar, pos, !!contchar); + if (visual_len < 0) + { + grub_print_error (); + return 0; + } + visual_show = visual; + for (; skip_lines && visual_show < visual + visual_len; visual_show++) + if (visual_show->base == '\n') + skip_lines--; + if (max_lines != (unsigned) -1) + { + for (vptr = visual_show; + max_lines && vptr < visual + visual_len; vptr++) + if (vptr->base == '\n') + max_lines--; + + visual_len_show = vptr - visual_show; + } + else + visual_len_show = visual + visual_len - visual_show; + + if (dry_run) + { + ret = 0; + for (vptr = visual_show; vptr < visual_show + visual_len_show; vptr++) + if (vptr->base == '\n') + ret++; + if (visual_len_show && visual[visual_len_show - 1].base != '\n') + ret++; + for (vptr = visual; vptr < visual + visual_len; vptr++) + grub_unicode_destroy_glyph (vptr); + grub_free (visual); + } + else + { + ret = put_glyphs_terminal (visual_show, visual_len_show, margin_left, + margin_right, + term, state, fixed_tab, contchar, fill_right); + + if (!ret) + grub_free (visual); + else + state->free = visual; + } + return ret; + } + return print_ucs4_terminal (str, last_position, margin_left, margin_right, + term, state, dry_run, fixed_tab, skip_lines, + max_lines, contchar, !!contchar, fill_right, pos); +} + +void +grub_print_ucs4_menu (const grub_uint32_t * str, + const grub_uint32_t * last_position, + int margin_left, int margin_right, + struct grub_term_output *term, + int skip_lines, int max_lines, + grub_uint32_t contchar, + struct grub_term_pos *pos) +{ + print_ucs4_real (str, last_position, margin_left, margin_right, + term, 0, 0, 1, skip_lines, max_lines, + contchar, 1, pos); +} + +void +grub_print_ucs4 (const grub_uint32_t * str, + const grub_uint32_t * last_position, + int margin_left, int margin_right, + struct grub_term_output *term) +{ + print_ucs4_real (str, last_position, margin_left, margin_right, + term, 0, 0, 1, 0, -1, 0, 0, 0); +} + +int +grub_ucs4_count_lines (const grub_uint32_t * str, + const grub_uint32_t * last_position, + int margin_left, int margin_right, + struct grub_term_output *term) +{ + return print_ucs4_real (str, last_position, margin_left, margin_right, + term, 0, 1, 1, 0, -1, 0, 0, 0); +} + +void +grub_xnputs (const char *str, grub_size_t msg_len) +{ + grub_uint32_t *unicode_str = NULL, *unicode_last_position; + int backlog = 0; + grub_term_output_t term; + + grub_error_push (); + + unicode_str = grub_calloc (msg_len, sizeof (grub_uint32_t)); + + grub_error_pop (); + + if (!unicode_str) + { + for (; msg_len--; str++, msg_len++) + { + struct grub_unicode_glyph c = + { + .variant = 0, + .attributes = 0, + .ncomb = 0, + .estimated_width = 1, + .base = *str + }; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + (term->putchar) (term, &c); + } + if (*str == '\n') + { + c.base = '\r'; + FOR_ACTIVE_TERM_OUTPUTS(term) + { + (term->putchar) (term, &c); + } + } + } + + return; + } + + msg_len = grub_utf8_to_ucs4 (unicode_str, msg_len, + (grub_uint8_t *) str, -1, 0); + unicode_last_position = unicode_str + msg_len; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + int cur; + cur = print_ucs4_real (unicode_str, unicode_last_position, 0, 0, + term, grub_more, 0, 0, 0, -1, 0, 0, 0); + if (cur) + backlog = 1; + } + while (backlog) + { + print_more (); + backlog = 0; + FOR_ACTIVE_TERM_OUTPUTS(term) + { + int cur; + cur = print_backlog (term, 0, 0); + if (cur) + backlog = 1; + } + } + grub_free (unicode_str); +} + +void +grub_xputs_normal (const char *str) +{ + grub_xnputs (str, grub_strlen (str)); +} + +void +grub_cls (void) +{ + struct grub_term_output *term; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + if ((term->flags & GRUB_TERM_DUMB) || (grub_env_get ("debug"))) + { + grub_putcode ('\n', term); + grub_term_refresh (term); + } + else + (term->cls) (term); + } +} |