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