From 041d1ea37802bf7178a31a53f96c26efa6b8fb7b Mon Sep 17 00:00:00 2001 From: James Date: Fri, 16 Nov 2012 10:41:01 +0000 Subject: fish --- grub-core/normal/auth.c | 273 ++++++++ grub-core/normal/autofs.c | 136 ++++ grub-core/normal/charset.c | 1274 ++++++++++++++++++++++++++++++++++++ grub-core/normal/cmdline.c | 650 ++++++++++++++++++ grub-core/normal/color.c | 150 +++++ grub-core/normal/completion.c | 518 +++++++++++++++ grub-core/normal/context.c | 209 ++++++ grub-core/normal/crypto.c | 151 +++++ grub-core/normal/datetime.c | 100 +++ grub-core/normal/dyncmd.c | 182 ++++++ grub-core/normal/main.c | 535 +++++++++++++++ grub-core/normal/menu.c | 773 ++++++++++++++++++++++ grub-core/normal/menu_entry.c | 1447 +++++++++++++++++++++++++++++++++++++++++ grub-core/normal/menu_text.c | 491 ++++++++++++++ grub-core/normal/misc.c | 128 ++++ grub-core/normal/term.c | 888 +++++++++++++++++++++++++ 16 files changed, 7905 insertions(+) create mode 100644 grub-core/normal/auth.c create mode 100644 grub-core/normal/autofs.c create mode 100644 grub-core/normal/charset.c create mode 100644 grub-core/normal/cmdline.c create mode 100644 grub-core/normal/color.c create mode 100644 grub-core/normal/completion.c create mode 100644 grub-core/normal/context.c create mode 100644 grub-core/normal/crypto.c create mode 100644 grub-core/normal/datetime.c create mode 100644 grub-core/normal/dyncmd.c create mode 100644 grub-core/normal/main.c create mode 100644 grub-core/normal/menu.c create mode 100644 grub-core/normal/menu_entry.c create mode 100644 grub-core/normal/menu_text.c create mode 100644 grub-core/normal/misc.c create mode 100644 grub-core/normal/term.c (limited to 'grub-core/normal') diff --git a/grub-core/normal/auth.c b/grub-core/normal/auth.c new file mode 100644 index 0000000..8e19568 --- /dev/null +++ b/grub-core/normal/auth.c @@ -0,0 +1,273 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct grub_auth_user +{ + struct grub_auth_user *next; + 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_P (&users), 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_P (&users), 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 == '\e') + { + cur_len = 0; + break; + } + + if (key == '\b') + { + cur_len--; + grub_printf ("\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 != '\e'); +} + +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_("Authenticate users")); + +} + +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..e1d4c01 --- /dev/null +++ b/grub-core/normal/autofs.c @@ -0,0 +1,136 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include + +/* 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; + + while ((p = fs_module_list) != NULL) + { + if (! grub_dl_get (p->name) && grub_dl_load (p->name)) + return 1; + + if (grub_errno) + grub_print_error (); + + fs_module_list = p->next; + grub_free (p->name); + grub_free (p); + } + + return 0; +} + +/* Read the file fs.lst for auto-loading. */ +void +read_fs_list (const char *prefix) +{ + if (prefix) + { + char *filename; + + filename = grub_xasprintf ("%s/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); + 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) + continue; + + fs_mod = grub_malloc (sizeof (*fs_mod)); + if (! fs_mod) + continue; + + fs_mod->name = grub_strdup (p); + 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..85ead53 --- /dev/null +++ b/grub-core/normal/charset.c @@ -0,0 +1,1274 @@ +/* + * 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 . + */ + +/* + 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 + */ + +/* Convert a (possibly null-terminated) UTF-8 string of at most SRCSIZE + bytes (if SRCSIZE is -1, it is ignored) in length to a UTF-16 string. + Return the number of characters converted. DEST must be able to hold + at least DESTSIZE characters. If an invalid sequence is found, return -1. + If SRCEND is not NULL, then *SRCEND is set to the next byte after the + last byte used in SRC. */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNIFONT_WIDTHSPEC +#include "widthspec.h" +#endif + +grub_ssize_t +grub_utf8_to_utf16 (grub_uint16_t *dest, grub_size_t destsize, + const grub_uint8_t *src, grub_size_t srcsize, + const grub_uint8_t **srcend) +{ + grub_uint16_t *p = dest; + int count = 0; + grub_uint32_t code = 0; + + if (srcend) + *srcend = src; + + while (srcsize && destsize) + { + grub_uint32_t c = *src++; + if (srcsize != (grub_size_t)-1) + srcsize--; + if (count) + { + if ((c & GRUB_UINT8_2_LEADINGBITS) != GRUB_UINT8_1_LEADINGBIT) + { + /* invalid */ + return -1; + } + else + { + code <<= 6; + code |= (c & GRUB_UINT8_6_TRAILINGBITS); + count--; + } + } + else + { + if (c == 0) + break; + + if ((c & GRUB_UINT8_1_LEADINGBIT) == 0) + code = c; + else if ((c & GRUB_UINT8_3_LEADINGBITS) == GRUB_UINT8_2_LEADINGBITS) + { + count = 1; + code = c & GRUB_UINT8_5_TRAILINGBITS; + } + else if ((c & GRUB_UINT8_4_LEADINGBITS) == GRUB_UINT8_3_LEADINGBITS) + { + count = 2; + code = c & GRUB_UINT8_4_TRAILINGBITS; + } + else if ((c & GRUB_UINT8_5_LEADINGBITS) == GRUB_UINT8_4_LEADINGBITS) + { + count = 3; + code = c & GRUB_UINT8_3_TRAILINGBITS; + } + else + return -1; + } + + if (count == 0) + { + if (destsize < 2 && code >= GRUB_UCS2_LIMIT) + break; + if (code >= GRUB_UCS2_LIMIT) + { + *p++ = GRUB_UTF16_UPPER_SURROGATE (code); + *p++ = GRUB_UTF16_LOWER_SURROGATE (code); + destsize -= 2; + } + else + { + *p++ = code; + destsize--; + } + } + } + + if (srcend) + *srcend = src; + return p - dest; +} + +/* Convert UCS-4 to UTF-8. */ +void +grub_ucs4_to_utf8 (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; + + while (size-- && dest < destend) + { + grub_uint32_t code = *src++; + + if (code <= 0x007F) + *dest++ = code; + else if (code <= 0x07FF) + { + if (dest + 1 >= destend) + break; + *dest++ = (code >> 6) | 0xC0; + *dest++ = (code & 0x3F) | 0x80; + } + else if ((code >= 0xDC00 && code <= 0xDFFF) + || (code >= 0xD800 && code <= 0xDBFF)) + { + /* No surrogates in UCS-4... */ + *dest++ = '?'; + } + else if (code < 0x10000) + { + if (dest + 2 >= destend) + break; + *dest++ = (code >> 12) | 0xE0; + *dest++ = ((code >> 6) & 0x3F) | 0x80; + *dest++ = (code & 0x3F) | 0x80; + } + else + { + if (dest + 3 >= destend) + break; + *dest++ = (code >> 18) | 0xF0; + *dest++ = ((code >> 12) & 0x3F) | 0x80; + *dest++ = ((code >> 6) & 0x3F) | 0x80; + *dest++ = (code & 0x3F) | 0x80; + } + } + *dest = 0; +} + +/* Convert UCS-4 to UTF-8. */ +char * +grub_ucs4_to_utf8_alloc (grub_uint32_t *src, grub_size_t size) +{ + grub_size_t remaining; + grub_uint32_t *ptr; + grub_size_t cnt = 0; + grub_uint8_t *ret; + + 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; + } + cnt++; + + 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) +{ + grub_uint32_t code = 0; + int count = 0; + + while (srcsize) + { + grub_uint32_t c = *src++; + if (srcsize != (grub_size_t)-1) + srcsize--; + if (count) + { + if ((c & 0xc0) != 0x80) + { + /* invalid */ + return 0; + } + else + { + code <<= 6; + code |= (c & 0x3f); + count--; + } + } + else + { + if (c == 0) + break; + + if ((c & 0x80) == 0x00) + code = c; + else if ((c & 0xe0) == 0xc0) + { + count = 1; + code = c & 0x1f; + } + else if ((c & 0xf0) == 0xe0) + { + count = 2; + code = c & 0x0f; + } + else if ((c & 0xf8) == 0xf0) + { + count = 3; + code = c & 0x07; + } + else + return 0; + } + } + + return 1; +} + +int +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_malloc (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) + { + grub_uint32_t c = *src++; + if (srcsize != (grub_size_t)-1) + srcsize--; + if (count) + { + if ((c & 0xc0) != 0x80) + { + /* invalid */ + code = '?'; + /* Character c may be valid, don't eat it. */ + src--; + if (srcsize != (grub_size_t)-1) + srcsize++; + count = 0; + } + else + { + code <<= 6; + code |= (c & 0x3f); + count--; + } + } + else + { + if (c == 0) + break; + + if ((c & 0x80) == 0x00) + code = c; + else if ((c & 0xe0) == 0xc0) + { + count = 1; + code = c & 0x1f; + } + else if ((c & 0xf0) == 0xe0) + { + count = 2; + code = c & 0x0f; + } + else if ((c & 0xf8) == 0xf0) + { + count = 3; + code = c & 0x07; + } + else + { + /* invalid */ + code = '?'; + count = 0; + } + } + + if (count == 0) + { + *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->end; cur++) + for (i = cur->start; i <= cur->end + && 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->end; cur++) + for (i = cur->start; i <= cur->end + && 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->end; cur++) + if (cur->start <= c && c <= cur->end) + 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->end; cur++) + if (cur->start <= c && c <= cur->end) + 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->end; cur++) + if (cur->start <= c && c <= cur->end) + 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->end; cur++) + for (i = cur->start; i <= cur->end + && 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->end; cur++) + if (cur->start <= c && c <= cur->end) + return cur->comb_type; + + return GRUB_UNICODE_COMB_NONE; +} + +#ifdef HAVE_UNIFONT_WIDTHSPEC + +grub_ssize_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)); + + 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; + + 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; + n = grub_realloc (out->combining, + sizeof (n[0]) * (out->ncomb + 1)); + if (!n) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + out->combining = n; + + for (j = last_comb_pointer; j < out->ncomb; j++) + if (is_type_after (out->combining[j].type, comb_type)) + break; + grub_memmove (out->combining + j + 1, + out->combining + j, + (out->ncomb - j) + * sizeof (out->combining[0])); + out->combining = n; + out->combining[j].code = *ptr; + out->combining[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; + out->combining = NULL; + } + return ptr - in; +} + +static grub_ssize_t +bidi_line_wrap (struct grub_unicode_glyph *visual_out, + struct grub_unicode_glyph *visual, + grub_size_t visual_len, unsigned *levels, + grub_ssize_t (*getcharwidth) (const struct grub_unicode_glyph *visual), + grub_size_t maxwidth, grub_size_t startwidth) +{ + struct grub_unicode_glyph *outptr = visual_out; + unsigned line_start = 0; + grub_ssize_t line_width = startwidth; + unsigned k; + grub_ssize_t last_space = -1; + grub_ssize_t last_space_width = 0; + + auto void revert (unsigned start, unsigned end); + void revert (unsigned start, unsigned end) + { + struct grub_unicode_glyph t; + unsigned i, tl; + for (i = 0; i <= (end - start) / 2; i++) + { + t = visual[start + i]; + visual[start + i] = visual[end - i]; + visual[end - i] = t; + tl = levels[start + i]; + levels[start + i] = levels[end - i]; + levels[end - i] = tl; + } + } + + if (!visual_len) + return 0; + + for (k = 0; k <= visual_len; k++) + { + grub_ssize_t last_width = 0; + + if (getcharwidth && k != visual_len) + line_width += last_width = getcharwidth (&visual[k]); + + if (k != visual_len && visual[k].base == ' ') + { + 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; + + if (k != visual_len && last_space > (signed) line_start) + k = last_space; + else if (k != visual_len && line_start == 0 && startwidth != 0) + { + k = 0; + last_space_width = startwidth; + } + else + last_space_width = line_width - last_width; + + { + unsigned i; + for (i = line_start; i < k; i++) + { + if (levels[i] > max_level) + max_level = levels[i]; + if (levels[i] < min_odd_level && (levels[i] & 1)) + min_odd_level = levels[i]; + } + } + + { + unsigned j; + /* FIXME: can be optimized. */ + for (j = max_level; j >= min_odd_level; j--) + { + unsigned in = 0; + unsigned i; + for (i = line_start; i < k; i++) + { + if (i != line_start && levels[i] >= j && levels[i-1] < j) + in = i; + if (levels[i] >= j && (i + 1 == k || levels[i+1] < j)) + revert (in, i); + } + } + } + + { + unsigned i; + for (i = line_start; i < k; i++) + { + if (is_mirrored (visual[i].base) && levels[i]) + visual[i].attributes |= GRUB_UNICODE_GLYPH_ATTRIBUTE_MIRROR; + if ((visual[i].attributes & GRUB_UNICODE_GLYPH_ATTRIBUTES_JOIN) + && levels[i]) + { + 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 < k; 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 = k - 1; i >= (signed) 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], + (k - line_start) * sizeof (visual[0])); + outptr += k - line_start; + if (k != visual_len) + { + grub_memset (outptr, 0, sizeof (visual[0])); + outptr->base = '\n'; + outptr++; + } + + if ((signed) k == last_space) + k++; + + line_start = k; + line_width -= last_space_width; + } + } + + 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_ssize_t (*getcharwidth) (const struct grub_unicode_glyph *visual), + grub_size_t maxwidth, grub_size_t startwidth) +{ + enum grub_bidi_type type = GRUB_BIDI_TYPE_L; + enum override_status {OVERRIDE_NEUTRAL = 0, OVERRIDE_R, OVERRIDE_L}; + unsigned *levels; + enum grub_bidi_type *resolved_types; + 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; + + auto void push_stack (unsigned new_override, unsigned new_level); + void push_stack (unsigned new_override, unsigned new_level) + { + if (new_level > GRUB_BIDI_MAX_EXPLICIT_LEVEL) + { + invalid_pushes++; + return; + } + stack_level[stack_depth] = cur_level; + stack_override[stack_depth] = cur_override; + stack_depth++; + cur_level = new_level; + cur_override = new_override; + } + + auto void pop_stack (void); + void pop_stack (void) + { + if (invalid_pushes) + { + invalid_pushes--; + return; + } + if (!stack_depth) + return; + stack_depth--; + cur_level = stack_level[stack_depth]; + cur_override = stack_override[stack_depth]; + } + + levels = grub_malloc (sizeof (levels[0]) * logical_len); + if (!levels) + return -1; + + resolved_types = grub_malloc (sizeof (resolved_types[0]) * logical_len); + if (!resolved_types) + { + grub_free (levels); + return -1; + } + + visual = grub_malloc (sizeof (visual[0]) * logical_len); + if (!visual) + { + grub_free (resolved_types); + grub_free (levels); + 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; + } + + p = grub_unicode_aglomerate_comb (lptr, logical + logical_len - lptr, + &visual[visual_len]); + + 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; + 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; + + levels[visual_len] = cur_level; + if (cur_override != OVERRIDE_NEUTRAL) + resolved_types[visual_len] = + (cur_override == OVERRIDE_L) ? GRUB_BIDI_TYPE_L + : GRUB_BIDI_TYPE_R; + else + resolved_types[visual_len] = 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 && + levels[run_end] == levels[run_start]; run_end++); + if (run_start == 0) + prev_level = base_level; + else + prev_level = levels[run_start - 1]; + if (run_end == visual_len) + next_level = base_level; + else + next_level = levels[run_end]; + cur_run_level = levels[run_start]; + 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 (resolved_types[i]) + { + case GRUB_BIDI_TYPE_NSM: + resolved_types[i] = last_type; + break; + case GRUB_BIDI_TYPE_EN: + if (last_strong_type == GRUB_BIDI_TYPE_AL) + resolved_types[i] = GRUB_BIDI_TYPE_AN; + break; + case GRUB_BIDI_TYPE_L: + case GRUB_BIDI_TYPE_R: + last_strong_type = resolved_types[i]; + break; + case GRUB_BIDI_TYPE_ES: + if (last_type == GRUB_BIDI_TYPE_EN + && i + 1 < run_end + && resolved_types[i + 1] == GRUB_BIDI_TYPE_EN) + resolved_types[i] = GRUB_BIDI_TYPE_EN; + else + resolved_types[i] = GRUB_BIDI_TYPE_ON; + break; + case GRUB_BIDI_TYPE_ET: + { + unsigned j; + if (last_type == GRUB_BIDI_TYPE_EN) + { + resolved_types[i] = GRUB_BIDI_TYPE_EN; + break; + } + for (j = i; j < run_end + && resolved_types[j] == GRUB_BIDI_TYPE_ET; j++); + if (j != run_end && resolved_types[j] == GRUB_BIDI_TYPE_EN) + { + for (; i < run_end + && resolved_types[i] == GRUB_BIDI_TYPE_ET; i++) + resolved_types[i] = GRUB_BIDI_TYPE_EN; + i--; + break; + } + for (; i < run_end + && resolved_types[i] == GRUB_BIDI_TYPE_ET; i++) + resolved_types[i] = GRUB_BIDI_TYPE_ON; + i--; + break; + } + break; + case GRUB_BIDI_TYPE_CS: + if (last_type == GRUB_BIDI_TYPE_EN + && i + 1 < run_end + && resolved_types[i + 1] == GRUB_BIDI_TYPE_EN) + { + resolved_types[i] = GRUB_BIDI_TYPE_EN; + break; + } + if (last_type == GRUB_BIDI_TYPE_AN + && i + 1 < run_end + && (resolved_types[i + 1] == GRUB_BIDI_TYPE_AN + || (resolved_types[i + 1] == GRUB_BIDI_TYPE_EN + && last_strong_type == GRUB_BIDI_TYPE_AL))) + { + resolved_types[i] = GRUB_BIDI_TYPE_EN; + break; + } + resolved_types[i] = GRUB_BIDI_TYPE_ON; + break; + case GRUB_BIDI_TYPE_AL: + last_strong_type = resolved_types[i]; + resolved_types[i] = GRUB_BIDI_TYPE_R; + break; + default: /* Make GCC happy. */ + break; + } + last_type = resolved_types[i]; + if (resolved_types[i] == GRUB_BIDI_TYPE_EN + && last_strong_type == GRUB_BIDI_TYPE_L) + resolved_types[i] = 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 && + (resolved_types[j] == GRUB_BIDI_TYPE_B + || resolved_types[j] == GRUB_BIDI_TYPE_S + || resolved_types[j] == GRUB_BIDI_TYPE_WS + || resolved_types[j] == GRUB_BIDI_TYPE_ON); j++); + if (j == i) + { + if (resolved_types[i] == 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 (resolved_types[j] == 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++) + resolved_types[i] = last_type; + else + for (; i < j; i++) + resolved_types[i] = (cur_run_level & 1) ? GRUB_BIDI_TYPE_R + : GRUB_BIDI_TYPE_L; + } + } + + for (i = 0; i < visual_len; i++) + { + if (!(levels[i] & 1) && resolved_types[i] == GRUB_BIDI_TYPE_R) + { + levels[i]++; + continue; + } + if (!(levels[i] & 1) && (resolved_types[i] == GRUB_BIDI_TYPE_AN + || resolved_types[i] == GRUB_BIDI_TYPE_EN)) + { + levels[i] += 2; + continue; + } + if ((levels[i] & 1) && (resolved_types[i] == GRUB_BIDI_TYPE_L + || resolved_types[i] == GRUB_BIDI_TYPE_AN + || resolved_types[i] == GRUB_BIDI_TYPE_EN)) + { + levels[i]++; + continue; + } + } + } + else + { + for (i = 0; i < visual_len; i++) + levels[i] = 0; + } + grub_free (resolved_types); + + { + grub_ssize_t ret; + ret = bidi_line_wrap (visual_out, visual, visual_len, levels, + getcharwidth, maxwidth, startwidth); + grub_free (levels); + grub_free (visual); + return ret; + } +} + +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_ssize_t (*getcharwidth) (const struct grub_unicode_glyph *visual), + grub_size_t max_length, grub_size_t startwidth) +{ + const grub_uint32_t *line_start = logical, *ptr; + struct grub_unicode_glyph *visual_ptr; + *visual_out = visual_ptr = grub_malloc (2 * sizeof (visual_ptr[0]) + * logical_len); + if (!visual_ptr) + return -1; + for (ptr = logical; ptr <= logical + logical_len; ptr++) + { + if (ptr == logical + logical_len || *ptr == '\n') + { + grub_ssize_t ret; + ret = grub_bidi_line_logical_to_visual (line_start, + ptr - line_start, + visual_ptr, + getcharwidth, + max_length, + startwidth); + startwidth = 0; + + if (ret < 0) + { + grub_free (*visual_out); + return ret; + } + visual_ptr += ret; + 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; +} diff --git a/grub-core/normal/cmdline.c b/grub-core/normal/cmdline.c new file mode 100644 index 0000000..09f2271 --- /dev/null +++ b/grub-core/normal/cmdline.c @@ -0,0 +1,650 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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_malloc (sizeof (grub_uint32_t *) * newsize); + + /* 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) + { + int i; + int delsize = hist_used - newsize; + hist_used = newsize; + + for (i = 1; i <= delsize; i++) + { + int 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 (int 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_malloc ((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 (int 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: + 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 +{ + unsigned xpos, ypos, ystart, width, height; + struct grub_term_output *term; +}; + +/* 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) +{ + grub_size_t lpos, llen; + grub_size_t plen; + grub_uint32_t *buf; + grub_size_t max_len = 256; + int key; + int histpos = 0; + auto void cl_insert (const grub_uint32_t *str); + auto void cl_delete (unsigned len); + auto inline void __attribute__ ((always_inline)) cl_print (struct cmdline_term *cl_term, int pos, + grub_uint32_t c); + auto void cl_set_pos (struct cmdline_term *cl_term); + auto void cl_print_all (int pos, grub_uint32_t c); + auto void cl_set_pos_all (void); + auto void init_clterm (struct cmdline_term *cl_term_cur); + auto void init_clterm_all (void); + const char *prompt_translated = _(prompt); + struct cmdline_term *cl_terms; + char *ret; + unsigned nterms; + + void cl_set_pos (struct cmdline_term *cl_term) + { + cl_term->xpos = (plen + lpos) % (cl_term->width - 1); + cl_term->ypos = cl_term->ystart + (plen + lpos) / (cl_term->width - 1); + grub_term_gotoxy (cl_term->term, cl_term->xpos, cl_term->ypos); + } + + void cl_set_pos_all (void) + { + unsigned i; + for (i = 0; i < nterms; i++) + cl_set_pos (&cl_terms[i]); + } + + inline void __attribute__ ((always_inline)) cl_print (struct cmdline_term *cl_term, int pos, grub_uint32_t c) + { + grub_uint32_t *p; + + for (p = buf + pos; p < buf + llen; p++) + { + if (c) + grub_putcode (c, cl_term->term); + else + grub_putcode (*p, cl_term->term); + cl_term->xpos++; + if (cl_term->xpos >= cl_term->width - 1) + { + cl_term->xpos = 0; + if (cl_term->ypos >= (unsigned) (cl_term->height - 1)) + cl_term->ystart--; + else + cl_term->ypos++; + grub_putcode ('\n', cl_term->term); + } + } + } + + void cl_print_all (int pos, grub_uint32_t c) + { + unsigned i; + for (i = 0; i < nterms; i++) + cl_print (&cl_terms[i], pos, c); + } + + void cl_insert (const grub_uint32_t *str) + { + grub_size_t len = strlen_ucs4 (str); + + if (len + llen >= max_len) + { + grub_uint32_t *nbuf; + max_len *= 2; + nbuf = grub_realloc (buf, sizeof (grub_uint32_t) * max_len); + if (nbuf) + buf = nbuf; + else + { + 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_print_all (lpos, 0); + lpos += len; + cl_set_pos_all (); + } + } + + void cl_delete (unsigned len) + { + if (lpos + len <= llen) + { + grub_size_t saved_lpos = lpos; + + lpos = llen - len; + cl_set_pos_all (); + cl_print_all (lpos, ' '); + lpos = saved_lpos; + cl_set_pos_all (); + + grub_memmove (buf + lpos, buf + lpos + len, + sizeof (grub_uint32_t) * (llen - lpos + 1)); + llen -= len; + cl_print_all (lpos, 0); + cl_set_pos_all (); + } + } + + void init_clterm (struct cmdline_term *cl_term_cur) + { + cl_term_cur->xpos = plen; + cl_term_cur->ypos = (grub_term_getxy (cl_term_cur->term) & 0xFF); + cl_term_cur->ystart = cl_term_cur->ypos; + cl_term_cur->width = grub_term_width (cl_term_cur->term); + cl_term_cur->height = grub_term_height (cl_term_cur->term); + } + + void init_clterm_all (void) + { + unsigned i; + for (i = 0; i < nterms; i++) + init_clterm (&cl_terms[i]); + } + + buf = grub_malloc (max_len * sizeof (grub_uint32_t)); + if (!buf) + return 0; + + plen = grub_strlen (prompt_translated) + sizeof (" ") - 1; + lpos = llen = 0; + buf[0] = '\0'; + + { + grub_term_output_t term; + + FOR_ACTIVE_TERM_OUTPUTS(term) + if ((grub_term_getxy (term) >> 8) != 0) + grub_putcode ('\n', term); + } + grub_printf ("%s ", prompt_translated); + grub_normal_reset_more (); + + { + struct cmdline_term *cl_term_cur; + struct grub_term_output *cur; + nterms = 0; + FOR_ACTIVE_TERM_OUTPUTS(cur) + nterms++; + + cl_terms = grub_malloc (sizeof (cl_terms[0]) * nterms); + if (!cl_terms) + return 0; + cl_term_cur = cl_terms; + FOR_ACTIVE_TERM_OUTPUTS(cur) + { + cl_term_cur->term = cur; + init_clterm (cl_term_cur); + cl_term_cur++; + } + } + + 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 (); + break; + + case GRUB_TERM_CTRL | 'b': + case GRUB_TERM_KEY_LEFT: + if (lpos > 0) + { + lpos--; + cl_set_pos_all (); + } + break; + + case GRUB_TERM_CTRL | 'e': + case GRUB_TERM_KEY_END: + lpos = llen; + cl_set_pos_all (); + break; + + case GRUB_TERM_CTRL | 'f': + case GRUB_TERM_KEY_RIGHT: + if (lpos < llen) + { + lpos++; + cl_set_pos_all (); + } + 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) + { + /* Restore the prompt. */ + grub_printf ("\n%s ", prompt_translated); + init_clterm_all (); + cl_print_all (0, 0); + } + + if (insertu8) + { + grub_size_t insertlen; + grub_ssize_t t; + grub_uint32_t *insert; + + insertlen = grub_strlen (insertu8); + insert = grub_malloc ((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 (insert); + lpos++; + } + else + { + insert[t] = 0; + cl_insert (insert); + } + } + + grub_free (insertu8); + grub_free (insert); + } + cl_set_pos_all (); + } + break; + + case GRUB_TERM_CTRL | 'k': + if (lpos < llen) + { + if (kill_buf) + 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 (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 (llen); + hist = grub_history_get (histpos); + cl_insert (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 (llen); + hist = grub_history_get (histpos); + + cl_insert (hist); + } + break; + + case GRUB_TERM_CTRL | 'u': + if (lpos > 0) + { + grub_size_t n = lpos; + + if (kill_buf) + grub_free (kill_buf); + + kill_buf = grub_malloc (n + 1); + if (grub_errno) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + } + if (kill_buf) + { + grub_memcpy (kill_buf, buf, n); + kill_buf[n] = '\0'; + } + + lpos = 0; + cl_set_pos_all (); + cl_delete (n); + } + break; + + case GRUB_TERM_CTRL | 'y': + if (kill_buf) + cl_insert (kill_buf); + break; + + case '\e': + grub_free (cl_terms); + return 0; + + case '\b': + if (lpos > 0) + { + lpos--; + cl_set_pos_all (); + } + else + break; + /* fall through */ + + case GRUB_TERM_CTRL | 'd': + case GRUB_TERM_KEY_DC: + if (lpos < llen) + cl_delete (1); + break; + + default: + if (grub_isprint (key)) + { + grub_uint32_t str[2]; + + str[0] = key; + str[1] = '\0'; + cl_insert (str); + } + break; + } + + grub_refresh (); + } + + grub_xputs ("\n"); + grub_refresh (); + + /* Remove leading spaces. */ + lpos = 0; + while (buf[lpos] == ' ') + lpos++; + + 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 + lpos, llen - lpos + 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..2e6c80b --- /dev/null +++ b/grub-core/normal/color.c @@ -0,0 +1,150 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include + +/* Borrowed from GRUB Legacy */ +static 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 < sizeof (color_list) / sizeof (*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 grub_uint8_t color_normal, color_highlight; + +static void +set_colors (void) +{ + struct grub_term_output *term; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + /* Reloads terminal `normal' and `highlight' colors. */ + grub_term_setcolor (term, color_normal, color_highlight); + + /* 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 (&color_normal, 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 (&color_highlight, 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..67676df --- /dev/null +++ b/grub-core/normal/completion.c @@ -0,0 +1,518 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The current word. */ +static 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 = '\0'; + } + break; + } + } + + return 0; +} + +static int +iterate_partition (grub_disk_t disk, const grub_partition_t p) +{ + 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) +{ + 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) +{ + grub_device_t dev; + + /* Complete the partition part. */ + dev = grub_device_open (devname); + + if (dev) + { + char tmp[grub_strlen (devname) + sizeof (",")]; + + grub_memcpy (tmp, devname, grub_strlen (devname)); + + if (grub_strcmp (devname, current_word) == 0) + { + if (add_completion (devname, ")", GRUB_COMPLETION_TYPE_PARTITION)) + return 1; + + if (dev->disk) + if (grub_partition_iterate (dev->disk, iterate_partition)) + return 1; + } + else + { + grub_memcpy (tmp + grub_strlen (devname), "", sizeof ("")); + if (add_completion (tmp, "", GRUB_COMPLETION_TYPE_DEVICE)) + return 1; + } + } + + 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)) + 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)) + { + 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, '/'); + dirfile[1] = '\0'; + + /* Iterate the directory. */ + (fs->dir) (dev, dir, iterate_dir); + + 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; + char **argv; + + /* Initialize variables. */ + match = 0; + num_found = 0; + suffix = ""; + print_func = hook; + + *restore = 1; + + if (grub_parser_split_cmdline (buf, 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_PRIO_LIST_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_strcat (ret, 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..5813166 --- /dev/null +++ b/grub-core/normal/context.c @@ -0,0 +1,209 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +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) + 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; + 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) + { + 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, + "no environment variable specified"); + + 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..19dafd8 --- /dev/null +++ b/grub-core/normal/crypto.c @@ -0,0 +1,151 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include + +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; + + 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; + } +} + +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/crypto.lst", prefix); + if (!filename) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + file = grub_file_open (filename); + 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; + + p = grub_strchr (name, ':'); + if (! p) + continue; + + *p = '\0'; + while (*++p == ' ') + ; + + cur = grub_malloc (sizeof (*cur)); + if (!cur) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + + cur->name = grub_strdup (name); + if (! 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); + grub_free (cur->name); + 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/datetime.c b/grub-core/normal/datetime.c new file mode 100644 index 0000000..44791e1 --- /dev/null +++ b/grub-core/normal/datetime.c @@ -0,0 +1,100 @@ +/* datetime.c - Module for common datetime function. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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 . + */ + +#include + +static char *grub_weekday_names[] = +{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +}; + +int +grub_get_weekday (struct grub_datetime *datetime) +{ + int a, y, m; + + a = (14 - datetime->month) / 12; + y = datetime->year - a; + m = datetime->month + 12 * a - 2; + + return (datetime->day + y + y / 4 - y / 100 + y / 400 + (31 * m / 12)) % 7; +} + +char * +grub_get_weekday_name (struct grub_datetime *datetime) +{ + return grub_weekday_names[grub_get_weekday (datetime)]; +} + +#define SECPERMIN 60 +#define SECPERHOUR (60*SECPERMIN) +#define SECPERDAY (24*SECPERHOUR) +#define SECPERYEAR (365*SECPERDAY) +#define SECPER4YEARS (4*SECPERYEAR+SECPERDAY) + + +void +grub_unixtime2datetime (grub_int32_t nix, struct grub_datetime *datetime) +{ + int i; + int div; + grub_uint8_t months[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + /* In the period of validity of unixtime all years divisible by 4 + are bissextile*/ + /* Convenience: let's have 3 consecutive non-bissextile years + at the beginning of the epoch. So count from 1973 instead of 1970 */ + nix -= 3*SECPERYEAR + SECPERDAY; + /* Transform C divisions and modulos to mathematical ones */ + div = nix / SECPER4YEARS; + if (nix < 0) + div--; + datetime->year = 1973 + 4 * div; + nix -= div * SECPER4YEARS; + + /* On 31st December of bissextile years 365 days from the beginning + of the year elapsed but year isn't finished yet */ + if (nix / SECPERYEAR == 4) + { + datetime->year += 3; + nix -= 3*SECPERYEAR; + } + else + { + datetime->year += nix / SECPERYEAR; + nix %= SECPERYEAR; + } + for (i = 0; i < 12 + && nix >= ((grub_int32_t) (i==1 && datetime->year % 4 == 0 + ? 29 : months[i]))*SECPERDAY; i++) + nix -= ((grub_int32_t) (i==1 && datetime->year % 4 == 0 + ? 29 : months[i]))*SECPERDAY; + datetime->month = i + 1; + datetime->day = 1 + (nix / SECPERDAY); + nix %= SECPERDAY; + datetime->hour = (nix / SECPERHOUR); + nix %= SECPERHOUR; + datetime->minute = nix / SECPERMIN; + datetime->second = nix % SECPERMIN; +} diff --git a/grub-core/normal/dyncmd.c b/grub-core/normal/dyncmd.c new file mode 100644 index 0000000..ed98855 --- /dev/null +++ b/grub-core/normal/dyncmd.c @@ -0,0 +1,182 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + + modname = extcmd->data; + mod = grub_dl_load (modname); + if (mod) + { + char *name; + + 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); + } + else + ret = grub_errno; + + 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/command.lst", prefix); + if (filename) + { + grub_file_t file; + + file = grub_file_open (filename); + 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); + grub_free (ptr->data); /* extcmd struct */ + } + 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; + if (*name == '*') + { + name++; + prio++; + } + + if (! grub_isgraph (name[0])) + continue; + + p = grub_strchr (name, ':'); + if (! p) + continue; + + *p = '\0'; + while (*++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_("not 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..837fcb9 --- /dev/null +++ b/grub-core/normal/main.c @@ -0,0 +1,535 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define GRUB_DEFAULT_HISTORY_SIZE 50 + +static int nested_level = 0; +int grub_normal_exit_level = 0; + +/* Read a line from the file FILE. */ +char * +grub_file_getline (grub_file_t file) +{ + char c; + int pos = 0; + int literal = 0; + char *cmdline; + int max_len = 64; + + /* Initially locate some space. */ + cmdline = grub_malloc (max_len); + if (! cmdline) + return 0; + + while (1) + { + if (grub_file_read (file, &c, 1) != 1) + break; + + /* Skip all carriage returns. */ + if (c == '\r') + continue; + + /* Replace tabs with spaces. */ + if (c == '\t') + c = ' '; + + /* The previous is a backslash, then... */ + if (literal) + { + /* If it is a newline, replace it with a space and continue. */ + if (c == '\n') + { + c = ' '; + + /* Go back to overwrite the backslash. */ + if (pos > 0) + pos--; + } + + literal = 0; + } + + if (c == '\\') + literal = 1; + + if (pos == 0) + { + if (! grub_isspace (c)) + cmdline[pos++] = c; + } + else + { + if (pos >= max_len) + { + char *old_cmdline = cmdline; + max_len = max_len * 2; + cmdline = grub_realloc (cmdline, max_len); + if (! cmdline) + { + grub_free (old_cmdline); + return 0; + } + } + + if (c == '\n') + break; + + cmdline[pos++] = c; + } + } + + cmdline[pos] = '\0'; + + /* If the buffer is empty, don't return anything at all. */ + if (pos == 0) + { + grub_free (cmdline); + cmdline = 0; + } + + return cmdline; +} + +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_free ((void *) entry->title); + grub_free ((void *) entry->sourcecode); + entry = next_entry; + } + + grub_free (menu); + grub_env_unset_menu (); +} + +static grub_menu_t +read_config_file (const char *config) +{ + grub_file_t file; + + auto grub_err_t getline (char **line, int cont); + grub_err_t getline (char **line, int cont __attribute__ ((unused))) + { + 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; + } + + 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. */ + file = grub_file_open (config); + if (! file) + return 0; + + while (1) + { + char *line; + + /* Print an error, if any. */ + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + + if ((getline (&line, 0)) || (! line)) + break; + + grub_normal_parse_line (line, getline); + grub_free (line); + } + + grub_file_close (file); + + return newmenu; +} + +/* Initialize the screen. */ +void +grub_normal_init_page (struct grub_term_output *term) +{ + int msg_len; + int posx; + const char *msg = _("GNU GRUB version %s"); + char *msg_formatted; + grub_uint32_t *unicode_msg; + grub_uint32_t *last_position; + + grub_term_cls (term); + + msg_formatted = grub_xasprintf (msg, 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 = (grub_term_width (term) - posx) / 2; + grub_term_gotoxy (term, posx, 1); + + 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_autoload) + { + read_command_list (val); + read_fs_list (val); + read_crypto_list (val); + read_terminal_list (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_command_execute ("parser.grub", 0, 0); + } + + if (config) + { + menu = read_config_file (config); + + /* Ignore any error. */ + grub_errno = GRUB_ERR_NONE; + } + + if (! batch) + { + if (menu && menu->size) + { + 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) +{ + nested_level++; + grub_normal_execute (config, 0, 0); + grub_cmdline_run (0); + nested_level--; + if (grub_normal_exit_level) + grub_normal_exit_level--; +} + +/* 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) + { + config = grub_xasprintf ("%s/grub.cfg", prefix); + if (! config) + goto quit; + + 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 = _("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"); + const char *msg_esc = _("ESC at any time exits."); + char *msg_formatted; + + msg_formatted = grub_xasprintf (msg, nested ? msg_esc : ""); + if (!msg_formatted) + return grub_errno; + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + grub_normal_init_page (term); + grub_term_setcursor (term, 1); + + grub_print_message_indented (msg_formatted, 3, STANDARD_MARGIN, 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) + prompt = ">"; + else + prompt = "grub>"; + + if (!prompt) + return grub_errno; + + while (1) + { + *line = grub_cmdline_get (prompt); + if (*line) + break; + + if (cont || nested) + { + grub_free (*line); + *line = 0; + return grub_errno; + } + } + + return 0; +} + +static grub_err_t +grub_normal_read_line (char **line, int cont) +{ + return grub_normal_read_line_real (line, cont, 0); +} + +void +grub_cmdline_run (int nested) +{ + grub_err_t err = GRUB_ERR_NONE; + + err = grub_auth_check_authentication (NULL); + + if (err) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + + grub_normal_reader_init (nested); + + while (1) + { + char *line; + + 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); + 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); + +GRUB_MOD_INIT(normal) +{ + /* Previously many modules depended on gzio. Be nice to user and load it. */ + grub_dl_load ("gzio"); + + 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", "white/black"); + grub_env_set ("color_highlight", "black/white"); +} + +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..5844cb2 --- /dev/null +++ b/grub-core/normal/menu.c @@ -0,0 +1,773 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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; + +/* 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) + if (grub_checkkey () >= 0) + { + grub_getkey (); + break; + } + + 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; +} + +/* Return the current timeout. If the variable "timeout" is not set or + invalid, return -1. */ +int +grub_menu_get_timeout (void) +{ + 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) +{ + char *val; + char *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->title; *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->title; *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++) + { + if (ptr[0] == '>' && ptr[1] == '>') + { + ptr++; + continue; + } + if (ptr[0] == '>') + break; + } + if (ptr[0] && ptr[1]) + grub_env_set ("default", ptr + 1); + else + grub_env_unset ("default"); + grub_script_execute_sourcecode (entry->sourcecode, entry->argc, entry->args); + + 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 (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 (grub_strcmp (term->name, "gfxterm") == 0) + { + 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, "no gfxmenu found"); + grub_print_error (); + grub_wait_after_message (); + } + grub_errno = GRUB_ERR_NONE; + grub_gfxterm_fullscreen (); + break; + } + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + grub_err_t err; + + if (grub_strcmp (term->name, "gfxterm") == 0 && gfxmenu) + break; + + 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 *title, const char *spec) +{ + const char *ptr1, *ptr2; + ptr1 = title; + 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)) + { + 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; +} + +#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; + + 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; + + /* If timeout is 0, drawing is pointless (and ugly). */ + if (grub_menu_get_timeout () == 0) + { + *auto_boot = 1; + return default_entry; + } + + current_entry = default_entry; + + /* Initialize the time. */ + saved_time = grub_get_time_ms (); + + refresh: + menu_init (current_entry, menu, nested); + + 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) + { + grub_uint64_t current_time; + + current_time = grub_get_time_ms (); + if (current_time - saved_time >= 1000) + { + timeout--; + grub_menu_set_timeout (timeout); + saved_time = current_time; + menu_print_timeout (timeout); + } + } + + if (timeout == 0) + { + grub_env_unset ("timeout"); + *auto_boot = 1; + menu_fini (); + return default_entry; + } + + if (grub_checkkey () >= 0 || timeout < 0) + { + c = grub_getkey (); + + 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 '\e': + if (nested) + { + menu_fini (); + return -1; + } + break; + + case 'c': + menu_fini (); + grub_cmdline_run (1); + 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: + { + grub_menu_entry_t entry; + int i; + for (i = 0, entry = menu->entry_list; i < menu->size; + i++, entry = entry->next) + if (entry->hotkey == c) + { + menu_fini (); + *auto_boot = 0; + return i; + } + } + break; + } + } + } + + /* Never reach here. */ + return -1; +} + +/* 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..dc5ab52 --- /dev/null +++ b/grub-core/normal/menu_entry.c @@ -0,0 +1,1447 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum update_mode + { + NO_LINE, + SINGLE_LINE, + ALL_LINES + }; + +struct line +{ + /* The line buffer. */ + char *buf; + /* The length of the line. */ + int len; + /* The maximum length of the line. */ + int max_len; +}; + +struct per_term_screen +{ + struct grub_term_output *term; + /* The X coordinate. */ + int x; + /* The Y coordinate. */ + int y; + /* Number of entries. */ + int num_entries; +}; + +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 line completion_buffer; +static int completion_type; + +/* Initialize a line. */ +static int +init_line (struct line *linep) +{ + linep->len = 0; + linep->max_len = 80; /* XXX */ + linep->buf = grub_malloc (linep->max_len + 1); + if (! linep->buf) + 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) + { + linep->max_len = linep->len + extra + 80; /* XXX */ + linep->buf = grub_realloc (linep->buf, linep->max_len + 1); + if (! linep->buf) + return 0; + } + + 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) +{ + return (linep->len / grub_term_entry_width (term_screen->term)) + 1; +} + +/* Print a line. */ +static void +print_line (struct line *linep, int offset, int start, int y, + struct per_term_screen *term_screen) +{ + grub_term_gotoxy (term_screen->term, + GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + start + 1, + y + GRUB_TERM_FIRST_ENTRY_Y); + + if (linep->len >= offset + grub_term_entry_width (term_screen->term)) + { + char *p, c; + p = linep->buf + offset + grub_term_entry_width (term_screen->term); + c = *p; + *p = 0; + grub_puts_terminal (linep->buf + offset + start, term_screen->term); + *p = c; + grub_putcode ('\\', term_screen->term); + } + else + { + int i; + char *p, c; + + p = linep->buf + linep->len; + c = *p; + *p = 0; + grub_puts_terminal (linep->buf + offset + start, term_screen->term); + *p = c; + + for (i = 0; + i <= grub_term_entry_width (term_screen->term) - linep->len + offset; + i++) + grub_putcode (' ', term_screen->term); + } +} + +/* 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, + GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1, + y + GRUB_TERM_FIRST_ENTRY_Y); + + for (i = 0; i < grub_term_entry_width (term_screen->term) + 1; i++) + 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, GRUB_TERM_LEFT_BORDER_X + + grub_term_border_width (term_screen->term), + GRUB_TERM_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, GRUB_TERM_LEFT_BORDER_X + + grub_term_border_width (term_screen->term), + GRUB_TERM_TOP_BORDER_Y + + term_screen->num_entries); + + 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, + int up, int down, enum update_mode mode) +{ + int up_flag = 0; + int down_flag = 0; + int y; + int i; + struct line *linep; + + /* Check if scrolling is necessary. */ + if (term_screen->y < 0 || term_screen->y >= term_screen->num_entries) + { + if (term_screen->y < 0) + term_screen->y = 0; + else + term_screen->y = term_screen->num_entries - 1; + + region_start = 0; + region_column = 0; + up = 1; + down = 1; + mode = ALL_LINES; + } + + if (mode != NO_LINE) + { + /* Draw lines. This code is tricky, because this must calculate logical + positions. */ + y = term_screen->y - screen->column + / grub_term_entry_width (term_screen->term); + i = screen->line; + linep = screen->lines + i; + while (y > 0) + { + i--; + linep--; + y -= get_logical_num_lines (linep, term_screen); + } + + if (y < 0 || i > 0) + up_flag = 1; + + do + { + int column; + + if (linep >= screen->lines + screen->num_lines) + break; + + for (column = 0; + column <= linep->len + && y < term_screen->num_entries; + column += grub_term_entry_width (term_screen->term), y++) + { + if (y < 0) + continue; + + if (i == region_start) + { + if (region_column >= column + && region_column + < (column + + grub_term_entry_width (term_screen->term))) + print_line (linep, column, region_column - column, y, + term_screen); + else if (region_column < column) + print_line (linep, column, 0, y, term_screen); + } + else if (i > region_start && mode == ALL_LINES) + print_line (linep, column, 0, y, term_screen); + } + + if (y == term_screen->num_entries) + { + if (column <= linep->len || i + 1 < screen->num_lines) + down_flag = 1; + } + + linep++; + i++; + + if (mode == ALL_LINES && i == screen->num_lines) + for (; y < term_screen->num_entries; y++) + print_empty_line (y, term_screen); + + } + while (y < term_screen->num_entries); + + /* Draw up and down arrows. */ + if (up) + print_up (up_flag, term_screen); + if (down) + print_down (down_flag, term_screen); + } + + /* Place the cursor. */ + grub_term_gotoxy (term_screen->term, + GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1 + + term_screen->x, + GRUB_TERM_FIRST_ENTRY_Y + term_screen->y); + + 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, char *s, int update) +{ + int region_start = screen->num_lines; + int region_column = 0; + int down[screen->nterms]; + enum update_mode mode[screen->nterms]; + unsigned i; + + for (i = 0; i < screen->nterms; i++) + { + down[i] = 0; + mode[i] = 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; + + /* Scroll down. */ + 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->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); + current_linep->len = screen->column; + 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++) + { + mode[i] = ALL_LINES; + down[i] = 1; /* XXX not optimal. */ + } + + /* Move the cursor. */ + screen->column = screen->real_column = 0; + screen->line++; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].x = 0; + screen->terms[i].y++; + } + s++; + } + else + { + /* All but LF. */ + char *p; + struct line *current_linep; + int size; + int orig_num[screen->nterms], new_num[screen->nterms]; + + /* 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; + size = p - s; + if (! ensure_space (current_linep, size)) + return 0; + + grub_memmove (current_linep->buf + screen->column + size, + current_linep->buf + screen->column, + current_linep->len - screen->column); + grub_memmove (current_linep->buf + screen->column, + s, + size); + for (i = 0; i < screen->nterms; i++) + orig_num[i] = get_logical_num_lines (current_linep, + &screen->terms[i]); + current_linep->len += size; + for (i = 0; i < screen->nterms; i++) + new_num[i] = get_logical_num_lines (current_linep, + &screen->terms[i]); + + /* Update the dirty region. */ + if (region_start > screen->line) + { + region_start = screen->line; + region_column = screen->column; + } + + for (i = 0; i < screen->nterms; i++) + if (orig_num[i] != new_num[i]) + { + mode[i] = ALL_LINES; + down[i] = 1; /* XXX not optimal. */ + } + else if (mode[i] != ALL_LINES) + mode[i] = SINGLE_LINE; + + /* Move the cursor. */ + screen->column += size; + screen->real_column = screen->column; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].x += size; + screen->terms[i].y += screen->terms[i].x + / grub_term_entry_width (screen->terms[i].term); + screen->terms[i].x + %= grub_term_entry_width (screen->terms[i].term); + } + s = p; + } + } + + if (update) + for (i = 0; i < screen->nterms; i++) + update_screen (screen, &screen->terms[i], + region_start, region_column, 0, down[i], mode[i]); + + 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) + grub_free (linep->buf); + } + + 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->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].x = 0; + screen->terms[i].y = 0; + } + + return screen; + + fail: + destroy_screen (screen); + return 0; +} + +static int +forward_char (struct screen *screen, int update) +{ + struct line *linep; + unsigned i; + + linep = screen->lines + screen->line; + if (screen->column < linep->len) + { + screen->column++; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].x++; + if (screen->terms[i].x + == grub_term_entry_width (screen->terms[i].term)) + { + screen->terms[i].x = 0; + screen->terms[i].y++; + } + } + } + else if (screen->num_lines > screen->line + 1) + { + screen->column = 0; + screen->line++; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].x = 0; + screen->terms[i].y++; + } + } + + 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) +{ + unsigned i; + + if (screen->column > 0) + { + screen->column--; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].x--; + if (screen->terms[i].x == -1) + { + screen->terms[i].x + = grub_term_entry_width (screen->terms[i].term) - 1; + screen->terms[i].y--; + } + } + } + else if (screen->line > 0) + { + struct line *linep; + + screen->line--; + linep = screen->lines + screen->line; + screen->column = linep->len; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].x = screen->column + % grub_term_entry_width (screen->terms[i].term); + screen->terms[i].y--; + } + } + + 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) +{ + unsigned i; + + if (screen->line > 0) + { + struct line *linep; + int col; + + /* How many physical lines from the current position + to the first physical line? */ + col = screen->column; + + screen->line--; + + linep = screen->lines + screen->line; + if (linep->len < screen->real_column) + screen->column = linep->len; + else + screen->column = screen->real_column; + + for (i = 0; i < screen->nterms; i++) + { + int dy; + dy = col / grub_term_entry_width (screen->terms[i].term); + + /* How many physical lines from the current position + to the last physical line? */ + dy += (linep->len / grub_term_entry_width (screen->terms[i].term) + - screen->column + / grub_term_entry_width (screen->terms[i].term)); + + screen->terms[i].y -= dy + 1; + screen->terms[i].x + = screen->column % grub_term_entry_width (screen->terms[i].term); + } + } + else + { + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].y + -= screen->column / grub_term_entry_width (screen->terms[i].term); + screen->terms[i].x = 0; + } + 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) +{ + unsigned i; + + if (screen->line < screen->num_lines - 1) + { + struct line *linep; + int l1, c1; + + /* How many physical lines from the current position + to the last physical line? */ + linep = screen->lines + screen->line; + l1 = linep->len; + c1 = screen->column; + + screen->line++; + + linep++; + if (linep->len < screen->real_column) + screen->column = linep->len; + else + screen->column = screen->real_column; + + for (i = 0; i < screen->nterms; i++) + { + int dy; + dy = l1 / grub_term_entry_width (screen->terms[i].term) + - c1 / grub_term_entry_width (screen->terms[i].term); + /* How many physical lines from the current position + to the first physical line? */ + dy += screen->column / grub_term_entry_width (screen->terms[i].term); + screen->terms[i].y += dy + 1; + screen->terms[i].x = screen->column + % grub_term_entry_width (screen->terms[i].term); + } + } + else + { + struct line *linep; + int l, s; + + linep = screen->lines + screen->line; + l = linep->len; + s = screen->column; + screen->column = linep->len; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].y + += (l / grub_term_entry_width (screen->terms[i].term) + - s / grub_term_entry_width (screen->terms[i].term)); + screen->terms[i].x + = screen->column % grub_term_entry_width (screen->terms[i].term); + } + } + + 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) +{ + unsigned i; + int col; + + col = screen->column; + screen->column = screen->real_column = 0; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].x = 0; + screen->terms[i].y -= col / grub_term_entry_width (screen->terms[i].term); + } + + 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) +{ + struct line *linep; + unsigned i; + int col; + + linep = screen->lines + screen->line; + col = screen->column; + screen->column = screen->real_column = linep->len; + for (i = 0; i < screen->nterms; i++) + { + screen->terms[i].y + += (linep->len / grub_term_entry_width (screen->terms->term) + - col / grub_term_entry_width (screen->terms->term)); + screen->terms[i].x + = screen->column % grub_term_entry_width (screen->terms->term); + } + + 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) + { + int orig_num[screen->nterms], new_num; + unsigned i; + + for (i = 0; i < screen->nterms; i++) + orig_num[i] = get_logical_num_lines (linep, &screen->terms[i]); + + grub_memmove (linep->buf + screen->column, + linep->buf + screen->column + 1, + linep->len - screen->column - 1); + linep->len--; + + start = screen->line; + column = screen->column; + + screen->real_column = screen->column; + + if (update) + { + for (i = 0; i < screen->nterms; i++) + { + new_num = get_logical_num_lines (linep, &screen->terms[i]); + if (orig_num[i] != 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; + + 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); + linep->len += next_linep->len; + + 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) + { + int orig_num[screen->nterms], new_num; + 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 - 1] = '\0'; + + screen->killed_text = p; + + for (i = 0; i < screen->nterms; i++) + orig_num[i] = get_logical_num_lines (linep, &screen->terms[i]); + linep->len = screen->column; + + if (update) + { + new_num = get_logical_num_lines (linep, &screen->terms[i]); + for (i = 0; i < screen->nterms; i++) + { + if (orig_num[i] != 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) +{ + int saved_y[screen->nterms]; + unsigned i; + + for (i = 0; i < screen->nterms; i++) + saved_y[i] = screen->terms[i].y; + + if (! insert_string (screen, "\n", 0)) + return 0; + + if (! backward_char (screen, 0)) + return 0; + + for (i = 0; i < screen->nterms; i++) + screen->terms[i].y = saved_y[i]; + + 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) +{ + char saved_char; + 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; + + if (continuous) + count++; + else + count = 0; + + completion_buffer.buf = 0; + completion_buffer.len = 0; + completion_buffer.max_len = 0; + + linep = screen->lines + screen->line; + saved_char = linep->buf[screen->column]; + linep->buf[screen->column] = '\0'; + + insert = grub_normal_do_completion (linep->buf, &restore, store_completion); + + linep->buf[screen->column] = saved_char; + + if (completion_buffer.buf) + { + buflen = grub_strlen (completion_buffer.buf); + ucs4 = grub_malloc (sizeof (grub_uint32_t) * (buflen + 1)); + + 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++) + { + int num_sections = ((completion_buffer.len + + grub_term_width (screen->terms[i].term) + - 8 - 1) + / (grub_term_width (screen->terms[i].term) + - 8)); + grub_uint32_t *endp; + grub_uint16_t pos; + grub_uint32_t *p = ucs4; + + pos = grub_term_getxy (screen->terms[i].term); + grub_term_gotoxy (screen->terms[i].term, 0, + grub_term_height (screen->terms[i].term) - 3); + + screen->completion_shown = 1; + + grub_term_gotoxy (screen->terms[i].term, 0, + grub_term_height (screen->terms[i].term) - 3); + 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 += (count % num_sections) + * (grub_term_width (screen->terms[i].term) - 8); + endp = p + (grub_term_width (screen->terms[i].term) - 8); + + 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 >> 8, pos & 0xFF); + } + } + + 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) +{ + grub_uint16_t pos; + unsigned i, j; + + pos = grub_term_getxy (term_screen->term); + grub_term_gotoxy (term_screen->term, 0, + grub_term_height (term_screen->term) - 3); + + for (i = 0; i < 2; i++) + { + for (j = 0; j < grub_term_width (term_screen->term) - 1; j++) + grub_putcode (' ', term_screen->term); + grub_putcode ('\n', term_screen->term); + } + + grub_term_gotoxy (term_screen->term, pos >> 8, pos & 0xFF); + 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 }; + + auto char * editor_getsource (void); + char * editor_getsource (void) + { + int i; + int size = 0; + char *source; + + for (i = 0; i < screen->num_lines; i++) + size += screen->lines[i].len + 1; + + source = grub_malloc (size + 1); + if (! source) + return NULL; + + size = 0; + for (i = 0; i < screen->num_lines; i++) + { + grub_memcpy (source + size, screen->lines[i].buf, screen->lines[i].len); + size += screen->lines[i].len; + source[size++] = '\n'; + } + source[size] = '\0'; + return source; + } + + 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. */ + script = editor_getsource (); + if (! script) + return 0; + grub_script_execute_sourcecode (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++; + screen->terms = grub_malloc (screen->nterms * sizeof (screen->terms[0])); + if (!screen->terms) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + i = 0; + FOR_ACTIVE_TERM_OUTPUTS(term) + { + screen->terms[i].term = term; + screen->terms[i].x = 0; + screen->terms[i].y = 0; + i++; + } + /* Draw the screen. */ + for (i = 0; i < screen->nterms; i++) + grub_menu_init_page (0, 1, &screen->terms[i].num_entries, + 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 '\e': + destroy_screen (screen); + return; + + case GRUB_TERM_CTRL | 'c': + case GRUB_TERM_KEY_F2: + grub_cmdline_run (1); + 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..93f0492 --- /dev/null +++ b/grub-core/normal/menu_text.c @@ -0,0 +1,491 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static grub_uint8_t grub_color_menu_normal; +static grub_uint8_t grub_color_menu_highlight; + +struct menu_viewer_data +{ + int first, offset; + /* The number of entries shown at a time. */ + int num_entries; + grub_menu_t menu; + struct grub_term_output *term; +}; + +static inline int +grub_term_cursor_x (struct grub_term_output *term) +{ + return (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term) + - GRUB_TERM_MARGIN - 1); +} + +grub_ssize_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; + str += grub_unicode_aglomerate_comb (str, last_position - str, &glyph); + width += grub_term_getcharwidth (term, &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; + + int msg_len; + int ret = 0; + + msg_len = grub_utf8_to_ucs4_alloc (msg, &unicode_msg, &last_position); + + if (msg_len < 0) + { + return 0; + } + + if (dry_run) + ret = grub_ucs4_count_lines (unicode_msg, last_position, margin_left, + margin_right, term); + else + grub_print_ucs4 (unicode_msg, last_position, margin_left, + margin_right, term); + + 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, int num_entries) +{ + unsigned i; + + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (term, GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y); + grub_putcode (GRUB_UNICODE_CORNER_UL, term); + for (i = 0; i < (unsigned) grub_term_border_width (term) - 2; i++) + grub_putcode (GRUB_UNICODE_HLINE, term); + grub_putcode (GRUB_UNICODE_CORNER_UR, term); + + for (i = 0; i < (unsigned) num_entries; i++) + { + grub_term_gotoxy (term, GRUB_TERM_MARGIN, GRUB_TERM_TOP_BORDER_Y + i + 1); + grub_putcode (GRUB_UNICODE_VLINE, term); + grub_term_gotoxy (term, GRUB_TERM_MARGIN + grub_term_border_width (term) + - 1, + GRUB_TERM_TOP_BORDER_Y + i + 1); + grub_putcode (GRUB_UNICODE_VLINE, term); + } + + grub_term_gotoxy (term, GRUB_TERM_MARGIN, + GRUB_TERM_TOP_BORDER_Y + num_entries + 1); + grub_putcode (GRUB_UNICODE_CORNER_LL, term); + for (i = 0; i < (unsigned) grub_term_border_width (term) - 2; 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, GRUB_TERM_MARGIN, + (GRUB_TERM_TOP_BORDER_Y + 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) + { + if(dry_run) + ret++; + else + grub_putcode ('\n', term); + 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 + { + const char *msg = _("Use the %C and %C keys to select which " + "entry is highlighted.\n"); + char *msg_translated; + + msg_translated = grub_xasprintf (msg, GRUB_UNICODE_UPARROW, + GRUB_UNICODE_DOWNARROW); + if (!msg_translated) + return 0; + if(dry_run) + ret++; + else + grub_putcode ('\n', term); + 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.\n"), + 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.\n"), + STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run); + } + } + return ret; +} + +static void +print_entry (int y, int highlight, grub_menu_entry_t entry, + struct grub_term_output *term) +{ + int x; + 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_malloc (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; + } + + grub_term_getcolor (term, &old_color_normal, &old_color_highlight); + grub_term_setcolor (term, grub_color_menu_normal, grub_color_menu_highlight); + grub_term_setcolorstate (term, highlight + ? GRUB_TERM_COLOR_HIGHLIGHT + : GRUB_TERM_COLOR_NORMAL); + + grub_term_gotoxy (term, GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN, y); + + int last_printed = 0; + for (x = GRUB_TERM_LEFT_BORDER_X + GRUB_TERM_MARGIN + 1, i = 0; + x < (int) (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term) + - GRUB_TERM_MARGIN);) + { + if (i < len + && x <= (int) (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term) + - GRUB_TERM_MARGIN - 1)) + { + grub_ssize_t width; + struct grub_unicode_glyph glyph; + + i += grub_unicode_aglomerate_comb (unicode_title + i, + len - i, &glyph); + + width = grub_term_getcharwidth (term, &glyph); + grub_free (glyph.combining); + + if (x + width <= (int) (GRUB_TERM_LEFT_BORDER_X + + grub_term_border_width (term) + - GRUB_TERM_MARGIN - 1)) + last_printed = i; + x += width; + } + else + break; + } + + grub_print_ucs4 (unicode_title, + unicode_title + last_printed, 0, 0, term); + + if (last_printed != len) + { + grub_putcode (GRUB_UNICODE_RIGHTARROW, term); + struct grub_unicode_glyph pseudo_glyph = { + .base = GRUB_UNICODE_RIGHTARROW, + .variant = 0, + .attributes = 0, + .ncomb = 0, + .combining = 0, + .estimated_width = 1 + }; + x += grub_term_getcharwidth (term, &pseudo_glyph); + } + + for (; x < (int) (GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (term) + - GRUB_TERM_MARGIN); x++) + grub_putcode (' ', term); + + grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL); + grub_putcode (' ', term); + + grub_term_gotoxy (term, grub_term_cursor_x (term), y); + + grub_term_setcolor (term, old_color_normal, old_color_highlight); + grub_term_setcolorstate (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, + GRUB_TERM_LEFT_BORDER_X + grub_term_border_width (data->term), + GRUB_TERM_FIRST_ENTRY_Y); + + 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->num_entries; i++) + { + print_entry (GRUB_TERM_FIRST_ENTRY_Y + i, data->offset == i, + e, data->term); + if (e) + e = e->next; + } + + grub_term_gotoxy (data->term, GRUB_TERM_LEFT_BORDER_X + + grub_term_border_width (data->term), + GRUB_TERM_TOP_BORDER_Y + data->num_entries); + + if (e) + grub_putcode (GRUB_UNICODE_DOWNARROW, data->term); + else + grub_putcode (' ', data->term); + + grub_term_gotoxy (data->term, grub_term_cursor_x (data->term), + GRUB_TERM_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, int *num_entries, + struct grub_term_output *term) +{ + grub_uint8_t old_color_normal, old_color_highlight; + + /* 3 lines for timeout message and bottom margin. 2 lines for the border. */ + *num_entries = grub_term_height (term) - GRUB_TERM_TOP_BORDER_Y + - (print_message (nested, edit, term, 1) + 3) - 2; + + grub_term_getcolor (term, &old_color_normal, &old_color_highlight); + + /* By default, use the same colors for the menu. */ + grub_color_menu_normal = old_color_normal; + grub_color_menu_highlight = old_color_highlight; + + /* 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")); + + grub_normal_init_page (term); + grub_term_setcolor (term, grub_color_menu_normal, grub_color_menu_highlight); + draw_border (term, *num_entries); + grub_term_setcolor (term, old_color_normal, old_color_highlight); + print_message (nested, edit, term, 0); +} + +static void +menu_text_print_timeout (int timeout, void *dataptr) +{ + const char *msg = + _("The highlighted entry will be executed automatically in %ds."); + struct menu_viewer_data *data = dataptr; + char *msg_translated; + int posx; + + grub_term_gotoxy (data->term, 0, grub_term_height (data->term) - 3); + + msg_translated = grub_xasprintf (msg, timeout); + if (!msg_translated) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + return; + } + + grub_print_message_indented (msg_translated, 3, 0, data->term); + + posx = grub_term_getxy (data->term) >> 8; + grub_print_spaces (data->term, grub_term_width (data->term) - posx - 1); + + grub_term_gotoxy (data->term, + grub_term_cursor_x (data->term), + GRUB_TERM_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->num_entries - 1) + { + data->first = entry - (data->num_entries - 1); + data->offset = data->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 (GRUB_TERM_FIRST_ENTRY_Y + oldoffset, 0, + grub_menu_get_entry (data->menu, data->first + oldoffset), + data->term); + print_entry (GRUB_TERM_FIRST_ENTRY_Y + data->offset, 1, + grub_menu_get_entry (data->menu, data->first + data->offset), + data->term); + } + 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); + +} + +static void +menu_text_clear_timeout (void *dataptr) +{ + struct menu_viewer_data *data = dataptr; + + grub_term_gotoxy (data->term, 0, grub_term_height (data->term) - 3); + grub_print_spaces (data->term, grub_term_width (data->term) - 1); + grub_term_gotoxy (data->term, grub_term_cursor_x (data->term), + GRUB_TERM_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->num_entries, data->term); + + if (data->offset > data->num_entries - 1) + { + data->first = data->offset - (data->num_entries - 1); + data->offset = data->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..4a7e6a3 --- /dev/null +++ b/grub-core/normal/misc.c @@ -0,0 +1,128 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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) + { + grub_printf_ (N_("Filesystem type %s"), fs->name); + if (fs->label) + { + char *label; + (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->mtime) + { + grub_int32_t tm; + struct grub_datetime datetime; + (fs->mtime) (dev, &tm); + if (grub_errno == GRUB_ERR_NONE) + { + grub_unixtime2datetime (tm, &datetime); + grub_xputs (" "); + 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->uuid) + { + char *uuid; + (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", _("Not a known filesystem")); + + if (dev->disk->partition) + grub_printf (_(" - Partition start at %llu"), + (unsigned long long) grub_partition_get_start (dev->disk->partition)); + if (grub_disk_get_size (dev->disk) == GRUB_DISK_SIZE_UNKNOWN) + grub_puts_ (" - Total size unknown"); + else + grub_printf (_(" - Total size %llu sectors"), + (unsigned long long) grub_disk_get_size (dev->disk)); + + 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..a8b9e66 --- /dev/null +++ b/grub-core/normal/term.c @@ -0,0 +1,888 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct term_state +{ + struct term_state *next; + const struct grub_unicode_glyph *backlog_glyphs; + const grub_uint32_t *backlog_ucs4; + grub_size_t backlog_len; + + void *free; + int num_lines; + char *term_name; +}; + +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); + +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; + grub_uint16_t *pos; + grub_term_output_t term; + grub_uint32_t *unicode_str, *unicode_last_position; + + pos = grub_term_save_pos (); + + grub_utf8_to_ucs4_alloc ("--MORE--", &unicode_str, + &unicode_last_position); + + if (!unicode_str) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + 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) +{ + if (onoff == 1) + grub_more++; + else + grub_more--; + grub_normal_reset_more (); +} + +enum + { + 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_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, + .combining = 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; + } + + grub_print_ucs4 (unicode_str, unicode_last_position, 0, 0, term); + grub_free (unicode_str); +} + +grub_uint16_t * +grub_term_save_pos (void) +{ + struct grub_term_output *cur; + unsigned cnt = 0; + grub_uint16_t *ret, *ptr; + + FOR_ACTIVE_TERM_OUTPUTS(cur) + cnt++; + + ret = grub_malloc (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 (grub_uint16_t *pos) +{ + struct grub_term_output *cur; + grub_uint16_t *ptr = pos; + + if (!pos) + return; + + FOR_ACTIVE_TERM_OUTPUTS(cur) + { + grub_term_gotoxy (cur, (*ptr & 0xff00) >> 8, *ptr & 0xff); + 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/terminal.lst", prefix); + if (!filename) + { + grub_errno = GRUB_ERR_NONE; + return; + } + + file = grub_file_open (filename); + 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; + + switch (buf[0]) + { + case 'i': + target = &grub_term_input_autoload; + break; + + case 'o': + target = &grub_term_output_autoload; + break; + } + if (!target) + continue; + + name = buf + 1; + + p = grub_strchr (name, ':'); + if (! p) + continue; + + *p = '\0'; + while (*++p == ' ') + ; + + cur = grub_malloc (sizeof (*cur)); + if (!cur) + { + grub_errno = GRUB_ERR_NONE; + continue; + } + + cur->name = grub_strdup (name); + if (! 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) +{ + struct grub_unicode_glyph c2 = + { + .variant = 0, + .attributes = 0, + .ncomb = 0, + .combining = 0, + .estimated_width = 1 + }; + + if (c->base == '\t' && term->getxy) + { + int n; + + n = 8 - ((term->getxy (term) >> 8) & 7); + 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 = c->combining[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) +{ + struct grub_unicode_glyph c = + { + .variant = 0, + .attributes = 0, + .ncomb = 0, + .combining = 0, + .estimated_width = 1 + }; + + c.base = map_code (code, term); + putglyph (&c, term); +} + +/* 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); +} + +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, + .combining = 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) >> 8) & 0xff) - margin_left; +} + +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) +{ + 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; + + for (ptr = str; ptr < last_position; ptr++) + { + grub_ssize_t last_width = 0; + if (grub_unicode_get_comb_type (*ptr) == GRUB_UNICODE_COMB_NONE) + { + struct grub_unicode_glyph c = { + .variant = 0, + .attributes = 0, + .ncomb = 0, + .combining = 0 + }; + c.base = *ptr; + line_width += last_width = grub_term_getcharwidth (term, &c); + } + + if (*ptr == ' ') + { + lastspacewidth = line_width; + last_space = ptr; + } + + if (line_width > max_width || *ptr == '\n') + { + const grub_uint32_t *ptr2; + + 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 (!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); + } + + grub_print_spaces (term, margin_right); + grub_putcode ('\n', term); + if (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; + return 1; + } + } + + line_width -= lastspacewidth; + if (!dry_run) + grub_print_spaces (term, margin_left); + if (ptr == last_space || *ptr == '\n') + ptr++; + line_start = ptr; + } + } + + if (line_start < last_position) + lines++; + if (!dry_run) + { + 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); + } + } + 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 (const 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) +{ + const struct grub_unicode_glyph *visual_ptr; + for (visual_ptr = visual; visual_ptr < visual + visual_len; visual_ptr++) + { + if (visual_ptr->base == '\n') + grub_print_spaces (term, margin_right); + putglyph (visual_ptr, term); + if (visual_ptr->base == '\n') + { + 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; + return 1; + } + + grub_print_spaces (term, margin_left); + } + grub_free (visual_ptr->combining); + } + 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); + 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); + if (!ret) + { + grub_free (state->free); + state->free = NULL; + state->backlog_len = 0; + state->backlog_glyphs = 0; + } + return ret; + } + + return 0; +} + +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) +{ + struct term_state *state = NULL; + + if (!dry_run) + { + if (backlog) + state = find_term_state (term); + + if (((term->getxy (term) >> 8) & 0xff) < margin_left) + grub_print_spaces (term, margin_left - ((term->getxy (term) >> 8) & 0xff)); + } + + 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; + int ret; + + auto grub_ssize_t getcharwidth (const struct grub_unicode_glyph *c); + grub_ssize_t getcharwidth (const struct grub_unicode_glyph *c) + { + return grub_term_getcharwidth (term, c); + } + + visual_len = grub_bidi_logical_to_visual (str, last_position - str, + &visual, getcharwidth, + get_maxwidth (term, + margin_left, + margin_right), + get_startwidth (term, + margin_left)); + if (visual_len < 0) + { + grub_print_error (); + return 0; + } + if (dry_run) + { + struct grub_unicode_glyph *vptr; + ret = 0; + for (vptr = visual; vptr < visual + visual_len; vptr++) + if (vptr->base == '\n') + ret++; + if (visual_len && visual[visual_len - 1].base != '\n') + ret++; + grub_free (visual); + } + else + { + ret = put_glyphs_terminal (visual, visual_len, margin_left, + margin_right, term, state); + 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); +} + +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); +} + +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); +} + +void +grub_xputs_normal (const char *str) +{ + grub_uint32_t *unicode_str = NULL, *unicode_last_position; + int backlog = 0; + grub_term_output_t term; + + 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, + .combining = 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; + } + + FOR_ACTIVE_TERM_OUTPUTS(term) + { + int cur; + cur = print_ucs4_real (unicode_str, unicode_last_position, 0, 0, + term, grub_more, 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_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); + } +} -- cgit v1.2.3