Implement syslinux parser.
authorVladimir Serbinenko <phcoder@gmail.com>
Wed, 18 Dec 2013 04:28:05 +0000 (05:28 +0100)
committerVladimir Serbinenko <phcoder@gmail.com>
Wed, 18 Dec 2013 04:28:05 +0000 (05:28 +0100)
ChangeLog
Makefile.util.def
docs/man/grub-syslinux2cfg.h2m [new file with mode: 0644]
grub-core/Makefile.core.def
grub-core/commands/syslinuxcfg.c [new file with mode: 0644]
grub-core/lib/getline.c [new file with mode: 0644]
grub-core/lib/syslinux_parse.c [new file with mode: 0644]
grub-core/normal/main.c
include/grub/syslinux_parse.h [new file with mode: 0644]
util/grub-syslinux2cfg.c [new file with mode: 0644]

index 2081bc7..ee3ffce 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2013-12-17  Vladimir Serbinenko  <phcoder@gmail.com>
+
+       Implement syslinux parser.
+
 2013-12-17  Vladimir Serbinenko  <phcoder@gmail.com>
 
        * grub-core/commands/legacycfg.c: Use 32-bit Linux protocol on non-BIOS.
index 4ea1c35..27c48e5 100644 (file)
@@ -1235,6 +1235,24 @@ program = {
   ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
 };
 
+program = {
+  name = grub-syslinux2cfg;
+  mansection = 1;
+  common = util/grub-syslinux2cfg.c;
+  common = grub-core/lib/syslinux_parse.c;
+  common = grub-core/lib/getline.c;
+  common = grub-core/osdep/init.c;
+  common = grub-core/kern/emu/hostfs.c;
+  common = grub-core/disk/host.c;
+  common = grub-core/kern/emu/argp_common.c;
+
+  ldadd = libgrubmods.a;
+  ldadd = libgrubgcry.a;
+  ldadd = libgrubkern.a;
+  ldadd = grub-core/gnulib/libgnu.a;
+  ldadd = '$(LIBINTL) $(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
+};
+
 program = {
   name = grub-glue-efi;
   mansection = 1;
diff --git a/docs/man/grub-syslinux2cfg.h2m b/docs/man/grub-syslinux2cfg.h2m
new file mode 100644 (file)
index 0000000..ad25c8a
--- /dev/null
@@ -0,0 +1,4 @@
+[NAME]
+grub-syslinux2cfg \- transform syslinux config into grub.cfg
+[SEE ALSO]
+.BR grub-menulst2cfg (8)
index f320981..7192a8e 100644 (file)
@@ -1761,6 +1761,7 @@ module = {
   common = normal/term.c;
   common = normal/context.c;
   common = normal/charset.c;
+  common = lib/getline.c;
 
   common = script/main.c;
   common = script/script.c;
@@ -2142,6 +2143,12 @@ module = {
   enable = xen;
 };
 
+module = {
+  name = syslinuxcfg;
+  common = lib/syslinux_parse.c;
+  common = commands/syslinuxcfg.c;
+};
+
 module = {
   name = test_blockarg;
   common = tests/test_blockarg.c;
diff --git a/grub-core/commands/syslinuxcfg.c b/grub-core/commands/syslinuxcfg.c
new file mode 100644 (file)
index 0000000..da753e2
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2013  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/types.h>
+#include <grub/misc.h>
+#include <grub/extcmd.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/file.h>
+#include <grub/normal.h>
+#include <grub/script_sh.h>
+#include <grub/i18n.h>
+#include <grub/term.h>
+#include <grub/syslinux_parse.h>
+#include <grub/crypto.h>
+#include <grub/auth.h>
+#include <grub/disk.h>
+#include <grub/partition.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Helper for syslinux_file.  */
+static grub_err_t
+syslinux_file_getline (char **line, int cont __attribute__ ((unused)),
+                    void *data __attribute__ ((unused)))
+{
+  *line = 0;
+  return GRUB_ERR_NONE;
+}
+
+static const struct grub_arg_option options[] =
+  {
+    {"root",  'r', 0,
+     N_("root directory of the syslinux disk (default /)."),
+     N_("DIR"), ARG_TYPE_STRING},
+    {"cwd",  'c', 0,
+     N_("home directory of the syslinux config (default directory of configfile)."),
+     N_("DIR"), ARG_TYPE_STRING},
+    {"isolinux",     'i',  0, N_("assume isolinux."), 0, 0},
+    {"pxelinux",     'p',  0, N_("assume pxelinux."), 0, 0},
+    {"syslinux",     's',  0, N_("assume syslinux."), 0, 0},
+    {0, 0, 0, 0, 0, 0}
+  };
+
+enum
+  {
+    OPTION_ROOT,
+    OPTION_CWD,
+    OPTION_ISOLINUX,
+    OPTION_PXELINUX,
+    OPTION_SYSLINUX
+  };
+
+static grub_err_t
+syslinux_file (grub_extcmd_context_t ctxt, const char *filename)
+{
+  char *result;
+  const char *root = ctxt->state[OPTION_ROOT].set ? ctxt->state[OPTION_ROOT].arg : "/";
+  const char *cwd = ctxt->state[OPTION_CWD].set ? ctxt->state[OPTION_CWD].arg : NULL;
+  grub_syslinux_flavour_t flav = GRUB_SYSLINUX_UNKNOWN;
+  char *cwdf = NULL;
+  grub_menu_t menu;
+
+  if (ctxt->state[OPTION_ISOLINUX].set)
+    flav = GRUB_SYSLINUX_ISOLINUX;
+  if (ctxt->state[OPTION_PXELINUX].set)
+    flav = GRUB_SYSLINUX_PXELINUX;
+  if (ctxt->state[OPTION_SYSLINUX].set)
+    flav = GRUB_SYSLINUX_SYSLINUX;
+
+  if (!cwd)
+    {
+      char *p;
+      cwdf = grub_strdup (filename);
+      if (!cwdf)
+       return grub_errno;
+      p = grub_strrchr (cwdf, '/');
+      if (!p)
+       {
+         grub_free (cwdf);
+         cwd = "/";
+         cwdf = NULL;
+       }
+      else
+       {
+         *p = '\0';
+         cwd = cwdf;
+       }
+    }
+
+  grub_dprintf ("syslinux",
+               "transforming syslinux config %s, root = %s, cwd = %s\n",
+               filename, root, cwd);
+
+  result = grub_syslinux_config_file (root, root, cwd, cwd, filename, flav);
+  if (!result)
+    return grub_errno;
+
+  grub_dprintf ("syslinux", "syslinux config transformed\n");
+
+  menu = grub_env_get_menu ();
+  if (! menu)
+    {
+      menu = grub_zalloc (sizeof (*menu));
+      if (! menu)
+       return grub_errno;
+
+      grub_env_set_menu (menu);
+    }
+
+  grub_normal_parse_line (result, syslinux_file_getline, NULL);
+  grub_print_error ();
+  grub_free (result);
+  grub_free (cwdf);
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cmd_syslinux_source (grub_extcmd_context_t ctxt,
+                         int argc, char **args)
+{
+  int new_env, extractor;
+  grub_err_t ret;
+
+  if (argc != 1)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
+
+  extractor = (ctxt->extcmd->cmd->name[0] == 'e');
+  new_env = (ctxt->extcmd->cmd->name[extractor ? (sizeof ("extract_syslinux_entries_") - 1)
+                            : (sizeof ("syslinux_") - 1)] == 'c');
+
+  if (new_env)
+    grub_cls ();
+
+  if (new_env && !extractor)
+    grub_env_context_open ();
+  if (extractor)
+    grub_env_extractor_open (!new_env);
+
+  ret = syslinux_file (ctxt, args[0]);
+
+  if (new_env)
+    {
+      grub_menu_t menu;
+      menu = grub_env_get_menu ();
+      if (menu && menu->size)
+       grub_show_menu (menu, 1, 0);
+      if (!extractor)
+       grub_env_context_close ();
+    }
+  if (extractor)
+    grub_env_extractor_close (!new_env);
+
+  return ret;
+}
+
+
+static grub_extcmd_t cmd_source, cmd_configfile;
+static grub_extcmd_t cmd_source_extract, cmd_configfile_extract;
+
+GRUB_MOD_INIT(syslinuxcfg)
+{
+  cmd_source
+    = grub_register_extcmd ("syslinux_source",
+                           grub_cmd_syslinux_source, 0,
+                           N_("FILE"),
+                           /* TRANSLATORS: "syslinux config" means
+                              "config as used by syslinux".  */
+                           N_("Parse syslinux config in same context"),
+                           options);
+  cmd_configfile
+    = grub_register_extcmd ("syslinux_configfile",
+                           grub_cmd_syslinux_source, 0,
+                           N_("FILE"),
+                           N_("Parse syslinux config in new context"),
+                           options);
+  cmd_source_extract
+    = grub_register_extcmd ("extract_syslinux_entries_source",
+                           grub_cmd_syslinux_source, 0,
+                           N_("FILE"),
+                           N_("Parse syslinux config in same context taking only menu entries"),
+                           options);
+  cmd_configfile_extract
+    = grub_register_extcmd ("extract_syslinux_entries_configfile",
+                           grub_cmd_syslinux_source, 0,
+                           N_("FILE"),
+                           N_("Parse syslinux config in new context taking only menu entries"),
+                           options);
+}
+
+GRUB_MOD_FINI(syslinuxcfg)
+{
+  grub_unregister_extcmd (cmd_source);
+  grub_unregister_extcmd (cmd_configfile);
+  grub_unregister_extcmd (cmd_source_extract);
+  grub_unregister_extcmd (cmd_configfile_extract);
+}
diff --git a/grub-core/lib/getline.c b/grub-core/lib/getline.c
new file mode 100644 (file)
index 0000000..edb8e9f
--- /dev/null
@@ -0,0 +1,92 @@
+/* main.c - the normal mode main routine */
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2000,2001,2002,2003,2005,2006,2007,2008,2009,2013  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/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>
+
+/* Read a line from the file FILE.  */
+char *
+grub_file_getline (grub_file_t file)
+{
+  char c;
+  grub_size_t pos = 0;
+  char *cmdline;
+  int have_newline = 0;
+  grub_size_t max_len = 64;
+
+  /* Initially locate some space.  */
+  cmdline = grub_malloc (max_len);
+  if (! cmdline)
+    return 0;
+
+  while (1)
+    {
+      if (grub_file_read (file, &c, 1) != 1)
+       break;
+
+      /* Skip all carriage returns.  */
+      if (c == '\r')
+       continue;
+
+
+      if (pos + 1 >= max_len)
+       {
+         char *old_cmdline = cmdline;
+         max_len = max_len * 2;
+         cmdline = grub_realloc (cmdline, max_len);
+         if (! cmdline)
+           {
+             grub_free (old_cmdline);
+             return 0;
+           }
+       }
+
+      if (c == '\n')
+       {
+         have_newline = 1;
+         break;
+       }
+
+      cmdline[pos++] = c;
+    }
+
+  cmdline[pos] = '\0';
+
+  /* If the buffer is empty, don't return anything at all.  */
+  if (pos == 0 && !have_newline)
+    {
+      grub_free (cmdline);
+      cmdline = 0;
+    }
+
+  return cmdline;
+}
diff --git a/grub-core/lib/syslinux_parse.c b/grub-core/lib/syslinux_parse.c
new file mode 100644 (file)
index 0000000..ebd6a97
--- /dev/null
@@ -0,0 +1,1501 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2013 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/file.h>
+#include <grub/normal.h>
+#include <grub/syslinux_parse.h>
+
+struct syslinux_say
+{
+  struct syslinux_say *next;
+  struct syslinux_say *prev;
+  char msg[0];
+};
+
+struct initrd_list
+{
+  struct initrd_list *next;
+  char *file;
+};
+
+struct syslinux_menuentry
+{
+  struct syslinux_menuentry *next;
+  struct syslinux_menuentry *prev;
+  char *label;
+  char *extlabel;
+  char *kernel_file;
+  struct initrd_list *initrds;
+  struct initrd_list *initrds_last;
+  char *append;
+  char *argument;
+  char *help;
+  char *comments;
+  grub_size_t commentslen;
+  char hotkey;
+  int make_default;
+  struct syslinux_say *say;
+  
+  enum { KERNEL_NO_KERNEL, KERNEL_LINUX, KERNEL_CHAINLOADER, 
+        KERNEL_BIN, KERNEL_PXE, KERNEL_CHAINLOADER_BPB,
+        KERNEL_COM32, KERNEL_COM, KERNEL_IMG, KERNEL_CONFIG, LOCALBOOT }
+    entry_type;
+};
+
+struct syslinux_menu
+{
+  struct syslinux_menu *parent;
+  struct syslinux_menuentry *entries;
+  char *def;
+  char *comments;
+  char *background;
+  const char *root_read_directory;
+  const char *root_target_directory;
+  const char *current_read_directory;
+  const char *current_target_directory;
+  const char *filename;
+  grub_size_t commentslen;
+  int timeout;
+  struct syslinux_say *say;
+  grub_syslinux_flavour_t flavour;
+};
+
+struct output_buffer
+{
+  grub_size_t alloc;
+  grub_size_t ptr;
+  char *buf;
+};
+
+static grub_err_t
+syslinux_parse_real (struct syslinux_menu *menu);
+static grub_err_t
+config_file (struct output_buffer *outbuf,
+            const char *root, const char *target_root,
+            const char *cwd, const char *target_cwd,
+            const char *fname, struct syslinux_menu *parent,
+            grub_syslinux_flavour_t flav);
+static grub_err_t
+print_entry (struct output_buffer *outbuf,
+            struct syslinux_menu *menu,
+            const char *str);
+
+static grub_err_t
+ensure_space (struct output_buffer *outbuf, grub_size_t len)
+{
+  grub_size_t newlen;
+  char *newbuf;
+  if (len < outbuf->alloc - outbuf->ptr)
+    return GRUB_ERR_NONE;
+  newlen = (outbuf->ptr + len + 10) * 2;
+  newbuf = grub_realloc (outbuf->buf, newlen);
+  if (!newbuf)
+    return grub_errno;
+  outbuf->alloc = newlen;
+  outbuf->buf = newbuf;
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+print (struct output_buffer *outbuf, const char *str, grub_size_t len)
+{
+  grub_err_t err;
+  err = ensure_space (outbuf, len);
+  if (err)
+    return err;
+  grub_memcpy (&outbuf->buf[outbuf->ptr], str, len);
+  outbuf->ptr += len;
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+add_comment (struct syslinux_menu *menu, const char *comment, int nl)
+{
+  if (menu->entries)
+    {
+      if (menu->entries->commentslen == 0 && *comment == 0)
+       return GRUB_ERR_NONE;
+      menu->entries->comments = grub_realloc (menu->entries->comments,
+                                             menu->entries->commentslen
+                                             + 2 + grub_strlen (comment));
+      if (!menu->entries->comments)
+       return grub_errno;
+      menu->entries->commentslen
+       += grub_stpcpy (menu->entries->comments + menu->entries->commentslen,
+                       comment)
+       - (menu->entries->comments + menu->entries->commentslen);
+      if (nl)
+       menu->entries->comments[menu->entries->commentslen++] = '\n';
+      menu->entries->comments[menu->entries->commentslen] = '\0';
+    }
+  else
+    {
+      if (menu->commentslen == 0 && *comment == 0)
+       return GRUB_ERR_NONE;
+      menu->comments = grub_realloc (menu->comments, menu->commentslen
+                                    + 2 + grub_strlen (comment));
+      if (!menu->comments)
+       return grub_errno;
+      menu->commentslen += grub_stpcpy (menu->comments + menu->commentslen,
+                                       comment)
+       - (menu->comments + menu->commentslen);
+      if (nl)
+       menu->comments[menu->commentslen++] = '\n';
+      menu->comments[menu->commentslen] = '\0';
+    }
+  return GRUB_ERR_NONE;
+}
+
+
+#define print_string(x) do { err = print (outbuf, x, sizeof (x) - 1); if (err) return err; } while (0)
+
+static grub_err_t
+print_num (struct output_buffer *outbuf, int n)
+{
+  char buf[20];
+  grub_snprintf (buf, sizeof (buf), "%d", n);
+  return print (outbuf, buf, grub_strlen (buf)); 
+}
+
+static grub_err_t
+label (const char *line, struct syslinux_menu *menu)
+{
+  struct syslinux_menuentry *entry;
+
+  entry = grub_malloc (sizeof (*entry));
+  if (!entry)
+    return grub_errno;
+  grub_memset (entry, 0, sizeof (*entry));
+  entry->label = grub_strdup (line);
+  if (!entry->label)
+    {
+      grub_free (entry);
+      return grub_errno;
+    }
+  entry->next = menu->entries;
+  entry->prev = NULL;
+  if (menu->entries)
+    menu->entries->prev = entry;
+  menu->entries = entry;
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+kernel (const char *line, struct syslinux_menu *menu)
+{
+  const char *end = line + grub_strlen (line);
+
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+
+  menu->entries->entry_type = KERNEL_LINUX;
+
+  if (end - line >= 2 && grub_strcmp (end - 2, ".0") == 0)
+    menu->entries->entry_type = KERNEL_PXE;
+
+  if (end - line >= 4 && grub_strcasecmp (end - 4, ".bin") == 0)
+    menu->entries->entry_type = KERNEL_BIN;
+
+  if (end - line >= 3 && grub_strcasecmp (end - 3, ".bs") == 0)
+    menu->entries->entry_type = KERNEL_CHAINLOADER;
+
+  if (end - line >= 4 && grub_strcasecmp (end - 4, ".bss") == 0)
+    menu->entries->entry_type = KERNEL_CHAINLOADER_BPB;
+
+  if (end - line >= 4 && grub_strcasecmp (end - 4, ".c32") == 0)
+    menu->entries->entry_type = KERNEL_COM32;
+
+  if (end - line >= 4 && grub_strcasecmp (end - 4, ".cbt") == 0)
+    menu->entries->entry_type = KERNEL_COM;
+
+  if (end - line >= 4 && grub_strcasecmp (end - 4, ".com") == 0)
+    menu->entries->entry_type = KERNEL_COM;
+
+  if (end - line >= 4 && grub_strcasecmp (end - 4, ".img") == 0)
+    menu->entries->entry_type = KERNEL_IMG;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_linux (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = KERNEL_LINUX;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_boot (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = KERNEL_CHAINLOADER;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_bss (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = KERNEL_CHAINLOADER_BPB;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_pxe (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = KERNEL_PXE;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_fdimage (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = KERNEL_IMG;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_comboot (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = KERNEL_COM;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_com32 (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = KERNEL_COM32;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_config (const char *line, struct syslinux_menu *menu)
+{
+  const char *space;
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  for (space = line; *space && !grub_isspace (*space); space++);
+  menu->entries->kernel_file = grub_strndup (line, space - line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  for (; *space && grub_isspace (*space); space++);
+  if (*space)
+    {
+      menu->entries->argument = grub_strdup (space);
+      if (!menu->entries->argument)
+       return grub_errno;
+    }
+  menu->entries->entry_type = KERNEL_CONFIG;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_append (const char *line, struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->append = grub_strdup (line);
+  if (!menu->entries->append)
+    return grub_errno;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_initrd (const char *line, struct syslinux_menu *menu)
+{
+  struct initrd_list *ninitrd;
+  const char *comma;
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  while (*line)
+    {
+      for (comma = line; *comma && *comma != ','; comma++);
+
+      ninitrd = grub_malloc (sizeof (*ninitrd));
+      if (!ninitrd)
+       return grub_errno;
+      ninitrd->file = grub_strndup (line, comma - line);
+      if (!ninitrd->file)
+       {
+         grub_free (ninitrd);
+         return grub_errno;
+       }
+      ninitrd->next = NULL;
+      if (menu->entries->initrds_last)
+       menu->entries->initrds_last->next = ninitrd;
+      else
+       {
+         menu->entries->initrds_last = ninitrd;
+         menu->entries->initrds = ninitrd;
+       }
+
+      line = comma;
+      while (*line == ',')
+       line++;
+    }
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_default (const char *line, struct syslinux_menu *menu)
+{
+  menu->def = grub_strdup (line);
+  if (!menu->def)
+    return grub_errno;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_timeout (const char *line, struct syslinux_menu *menu)
+{
+  menu->timeout = grub_strtoul (line, NULL, 0);
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_menudefault (const char *line __attribute__ ((unused)),
+                struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->make_default = 1; 
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_menubackground (const char *line,
+                   struct syslinux_menu *menu)
+{
+  menu->background = grub_strdup (line);
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_localboot (const char *line,
+              struct syslinux_menu *menu)
+{
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->kernel_file = grub_strdup (line);
+  if (!menu->entries->kernel_file)
+    return grub_errno;
+  menu->entries->entry_type = LOCALBOOT;
+  
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+cmd_extlabel (const char *line, struct syslinux_menu *menu)
+{
+  const char *in;
+  char *out;
+
+  if (!menu->entries)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "kernel without label");
+
+  menu->entries->extlabel = grub_malloc (grub_strlen (line) + 1);
+  if (!menu->entries->extlabel)
+    return grub_errno;
+  in = line;
+  out = menu->entries->extlabel;
+  while (*in)
+    {
+      if (in[0] == '^' && in[1])
+       {
+         menu->entries->hotkey = grub_tolower (in[1]);
+         in++;
+       }
+      *out++ = *in++;
+    }
+  *out = 0;
+  
+  return GRUB_ERR_NONE;
+}
+
+
+static grub_err_t
+cmd_say (const char *line, struct syslinux_menu *menu)
+{
+  struct syslinux_say *nsay;
+  nsay = grub_malloc (sizeof (*nsay) + grub_strlen (line) + 1);
+  if (!nsay)
+    return grub_errno;
+  nsay->prev = NULL;
+  if (menu->entries)
+    {
+      nsay->next = menu->entries->say;
+      menu->entries->say = nsay;
+    }
+  else
+    {
+      nsay->next = menu->say;
+      menu->say = nsay;
+    }
+
+  if (nsay->next)
+    nsay->next->prev = nsay;
+
+  grub_memcpy (nsay->msg, line, grub_strlen (line) + 1);
+  return GRUB_ERR_NONE;
+}
+
+static char *
+get_read_filename (struct syslinux_menu *menu,
+                  const char *filename)
+{
+  return grub_xasprintf ("%s/%s",
+                        filename[0] == '/' ? menu->root_read_directory
+                        : menu->current_read_directory, filename);
+}
+
+static char *
+get_target_filename (struct syslinux_menu *menu,
+                  const char *filename)
+{
+  return grub_xasprintf ("%s/%s",
+                        filename[0] == '/' ? menu->root_target_directory
+                        : menu->current_target_directory, filename);
+}
+
+static grub_err_t
+syslinux_parse (const char *filename,
+               struct syslinux_menu *menu)
+{
+  const char *old_filename = menu->filename;
+  grub_err_t ret;
+  char *nf;
+  nf = get_read_filename (menu, filename);
+  if (!nf)
+    return grub_errno;
+  menu->filename = nf;
+  ret = syslinux_parse_real (menu);
+  if (ret == GRUB_ERR_FILE_NOT_FOUND
+      || ret == GRUB_ERR_BAD_FILENAME)
+    {  
+      grub_errno = ret = GRUB_ERR_NONE;
+      add_comment (menu, "# File ", 0);
+      add_comment (menu, nf, 0);
+      add_comment (menu, " not found", 1);
+    }
+  grub_free (nf);
+  menu->filename = old_filename;
+  return ret;
+}
+
+struct
+{
+  const char *name1;
+  const char *name2;
+  grub_err_t (*parse) (const char *line, struct syslinux_menu *menu);
+} commands[] = {
+  /* FIXME: support tagname.  */
+  {"include", NULL, syslinux_parse},
+  {"menu", "include", syslinux_parse},
+  {"label",   NULL, label},
+  {"kernel",  NULL, kernel},
+  {"linux",  NULL, cmd_linux},
+  {"boot",  NULL, cmd_boot},
+  {"bss",  NULL, cmd_bss},
+  {"pxe",  NULL, cmd_pxe},
+  {"fdimage",  NULL, cmd_fdimage},
+  {"comboot",  NULL, cmd_comboot},
+  {"com32",  NULL, cmd_com32},
+  {"config",  NULL, cmd_config},
+  {"append",  NULL, cmd_append},
+  /* FIXME: ipappend not supported.  */
+  {"localboot",  NULL, cmd_localboot},
+  {"initrd",  NULL, cmd_initrd},
+  {"default",  NULL, cmd_default},
+  {"menu", "label", cmd_extlabel},
+  /* FIXME: MENU LABEL not supported.  */
+  /* FIXME: MENU HIDDEN not supported.  */
+  /* FIXME: MENU SEPARATOR not supported.  */
+  /* FIXME: MENU INDENT not supported.  */
+  /* FIXME: MENU DISABLE not supported.  */
+  /* FIXME: MENU HIDE not supported.  */
+  {"menu", "default", cmd_menudefault},
+  /* FIXME: MENU PASSWD not supported.  */
+  /* FIXME: MENU MASTER PASSWD not supported.  */
+  {"menu", "background", cmd_menubackground},
+  /* FIXME: MENU BEGIN not supported.  */
+  /* FIXME: MENU GOTO not supported.  */
+  /* FIXME: MENU EXIT not supported.  */
+  /* FIXME: MENU QUIT not supported.  */
+  /* FIXME: MENU START not supported.  */
+  /* FIXME: MENU AUTOBOOT not supported.  */
+  /* FIXME: MENU TABMSG not supported.  */
+  /* FIXME: MENU NOTABMSG not supported.  */
+  /* FIXME: MENU PASSPROMPT not supported.  */
+  /* FIXME: MENU COLOR not supported.  */
+  /* FIXME: MENU MSGCOLOR not supported.  */
+  /* FIXME: MENU WIDTH not supported.  */
+  /* FIXME: MENU MARGIN not supported.  */
+  /* FIXME: MENU PASSWORDMARGIN not supported.  */
+  /* FIXME: MENU ROWS not supported.  */
+  /* FIXME: MENU TABMSGROW not supported.  */
+  /* FIXME: MENU CMDLINEROW not supported.  */
+  /* FIXME: MENU ENDROW not supported.  */
+  /* FIXME: MENU PASSWORDROW not supported.  */
+  /* FIXME: MENU TIMEOUTROW not supported.  */
+  /* FIXME: MENU HELPMSGROW not supported.  */
+  /* FIXME: MENU HELPMSGENDROW not supported.  */
+  /* FIXME: MENU HIDDENROW not supported.  */
+  /* FIXME: MENU HSHIFT not supported.  */
+  /* FIXME: MENU VSHIFT not supported.  */
+  {"timeout", NULL, cmd_timeout},
+  /* FIXME: TOTALTIMEOUT not supported.  */
+  /* FIXME: ONTIMEOUT not supported.  */
+  /* FIXME: ONERROR not supported.  */
+  /* FIXME: SERIAL not supported.  */
+  /* FIXME: CONSOLE not supported.  */
+  /* FIXME: FONT not supported.  */
+  /* FIXME: KBDMAP not supported.  */
+  {"say", NULL, cmd_say},
+  /* FIXME: DISPLAY not supported.  */
+  /* FIXME: F* not supported.  */
+
+  /* Commands to control interface behaviour which aren't needed with GRUB.
+     If they are important in your environment please contact GRUB team.
+   */
+  {"prompt",       NULL, NULL},
+  {"nocomplete",   NULL, NULL},
+  {"noescape",     NULL, NULL},
+  {"implicit",     NULL, NULL},
+  {"allowoptions", NULL, NULL}
+};
+
+static grub_err_t
+helptext (const char *line, grub_file_t file, struct syslinux_menu *menu)
+{
+  char *help;
+  char *buf = NULL;
+  grub_size_t helplen, alloclen = 0;
+
+  help = grub_strdup (line);
+  helplen = grub_strlen (line);
+  while ((grub_free (buf), buf = grub_file_getline (file)))
+    {
+      char *ptr;
+      grub_size_t needlen;
+      for (ptr = buf; *ptr && grub_isspace (*ptr); ptr++);
+      if (grub_strncasecmp (ptr, "endtext", sizeof ("endtext") - 1) == 0)
+       {
+         ptr += sizeof ("endtext") - 1;
+         for (; *ptr && (grub_isspace (*ptr) || *ptr == '\n' || *ptr == '\r');
+              ptr++);
+         if (!*ptr)
+           {
+             menu->entries->help = help;
+             grub_free (buf);
+             return GRUB_ERR_NONE;
+           }
+       }
+      needlen = helplen + 1 + grub_strlen (buf);
+      if (alloclen < needlen)
+       {
+         alloclen = 2 * needlen;
+         help = grub_realloc (help, alloclen);
+         if (!help)
+           {
+             grub_free (buf);
+             return grub_errno;
+           }
+       }
+      helplen += grub_stpcpy (help + helplen, buf) - (help + helplen);
+    }
+
+  grub_free (buf);
+  return grub_errno;
+}
+
+
+static grub_err_t
+syslinux_parse_real (struct syslinux_menu *menu)
+{
+  grub_file_t file;
+  char *buf = NULL;
+  grub_err_t err = GRUB_ERR_NONE;
+
+  file = grub_file_open (menu->filename);
+  if (!file)
+    return grub_errno;
+  while ((grub_free (buf), buf = grub_file_getline (file)))
+    {
+      const char *ptr1, *ptr2, *ptr3, *ptr4, *ptr5;
+      char *end;
+      unsigned i;
+      end = buf + grub_strlen (buf);
+      while (end > buf && (end[-1] == '\n' || end[-1] == '\r'))
+       end--;
+      *end = 0;
+      for (ptr1 = buf; *ptr1 && grub_isspace (*ptr1); ptr1++);
+      if (*ptr1 == '#' || *ptr1 == 0)
+       {
+         err = add_comment (menu, ptr1, 1);
+         if (err)
+           goto fail;
+         continue;
+       }
+      for (ptr2 = ptr1; !grub_isspace (*ptr2) && *ptr2; ptr2++);
+      for (ptr3 = ptr2;  grub_isspace (*ptr3) && *ptr3; ptr3++);
+      for (ptr4 = ptr3; !grub_isspace (*ptr4) && *ptr4; ptr4++);
+      for (ptr5 = ptr4;  grub_isspace (*ptr5) && *ptr5; ptr5++);
+      for (i = 0; i < sizeof (commands) / sizeof (commands[0]); i++)
+       if (grub_strlen (commands[i].name1) == (grub_size_t) (ptr2 - ptr1)
+           && grub_strncasecmp (commands[i].name1, ptr1, ptr2 - ptr1) == 0
+           && (commands[i].name2 == NULL
+               || (grub_strlen (commands[i].name2)
+                   == (grub_size_t) (ptr4 - ptr3)
+                   && grub_strncasecmp (commands[i].name2, ptr3, ptr4 - ptr3)
+                   == 0)))
+         break;
+      if (i == sizeof (commands) / sizeof (commands[0]))
+       {
+         if (sizeof ("text") - 1 == ptr2 - ptr1
+             && grub_strncasecmp ("text", ptr1, ptr2 - ptr1) == 0
+             && (sizeof ("help") - 1 == ptr4 - ptr3
+                 && grub_strncasecmp ("help", ptr3, ptr4 - ptr3) == 0))
+           {
+             if (helptext (ptr5, file, menu))
+               return 1;
+             continue;
+           }
+
+         add_comment (menu, "  # UNSUPPORTED command '", 0);
+         add_comment (menu, ptr1, 0);
+         add_comment (menu, "'", 1);
+
+         continue;
+       }
+      if (commands[i].parse)
+       {
+         err = commands[i].parse (commands[i].name2
+                                  ? ptr5 : ptr3, menu);
+         if (err)
+           goto fail;
+       }
+    }
+ fail:
+  grub_file_close (file);
+  return err;
+}
+
+static grub_err_t
+print_escaped (struct output_buffer *outbuf, 
+              const char *from, const char *to)
+{
+  const char *ptr;
+  grub_err_t err;
+  if (!to)
+    to = from + grub_strlen (from);
+  err = ensure_space (outbuf, (to - from) * 4 + 2);
+  if (err)
+    return err;
+  outbuf->buf[outbuf->ptr++] = '\'';
+  for (ptr = from; *ptr; ptr++)
+    {
+      if (*ptr == '\'')
+       {
+         outbuf->buf[outbuf->ptr++] = '\'';
+         outbuf->buf[outbuf->ptr++] = '\\';
+         outbuf->buf[outbuf->ptr++] = '\'';
+         outbuf->buf[outbuf->ptr++] = '\'';
+       }
+      else
+       outbuf->buf[outbuf->ptr++] = *ptr;
+    }
+  outbuf->buf[outbuf->ptr++] = '\'';
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+print_file (struct output_buffer *outbuf,
+           struct syslinux_menu *menu, const char *from, const char *to)
+{
+  grub_err_t err;
+  if (!to)
+    to = from + grub_strlen (from);
+  err = print_escaped (outbuf, from[0] == '/'
+                      ? menu->root_target_directory
+                      : menu->current_target_directory, NULL);
+  if (err)
+    return err;
+
+  err = print (outbuf, "/", 1);
+  if (err)
+    return err;
+  return print_escaped (outbuf, from, to);
+}
+
+static void
+simplify_filename (char *str)
+{
+  char *iptr, *optr = str;
+  for (iptr = str; *iptr; iptr++)
+    {
+      if (*iptr == '/' && optr != str && optr[-1] == '/')
+       continue;
+      if (iptr[0] == '/' && iptr[1] == '.' && iptr[2] == '/')
+       {
+         iptr += 2;
+         continue;
+       }
+      if (iptr[0] == '/' && iptr[1] == '.' && iptr[2] == '.'
+         && iptr[3] == '/')
+       {
+         iptr += 3;
+         while (optr >= str && *optr != '/')
+           optr--;
+         if (optr < str)
+           {
+             str[0] = '/';
+             optr = str;
+           }
+         optr++;
+         continue;
+       }
+      *optr++ = *iptr;
+    }
+  *optr = '\0';
+}
+
+static grub_err_t
+write_entry (struct output_buffer *outbuf,
+            struct syslinux_menu *menu,
+            struct syslinux_menuentry *curentry)
+{
+  grub_err_t err;
+  if (curentry->comments)
+    print (outbuf, curentry->comments, grub_strlen (curentry->comments));
+  {
+    struct syslinux_say *say;
+    for (say = curentry->say; say && say->next; say = say->next);
+    for (; say && say->prev; say = say->prev)
+      {
+       print_string ("echo ");
+       if (print_escaped (outbuf, say->msg, NULL)) return grub_errno;
+       print_string ("\n");
+      }
+  }
+
+  /* FIXME: support help text.  */
+  switch (curentry->entry_type)
+    {
+    case KERNEL_LINUX:
+      {
+       char *ptr;
+       char *cmdline;
+       char *initrd = NULL;
+       for (ptr = curentry->append; ptr && *ptr; ptr++)
+         if ((ptr == curentry->append || grub_isspace (ptr[-1]))
+             && grub_strncasecmp (ptr, "initrd=", sizeof ("initrd=") - 1)
+             == 0)
+           break;
+       if (ptr && *ptr)
+         {
+           char *ptr2;
+           grub_size_t totlen = grub_strlen (curentry->append);
+           initrd = ptr + sizeof ("initrd=") - 1;
+           for (ptr2 = ptr; *ptr2 && !grub_isspace (*ptr2); ptr2++);
+           if (*ptr2)
+             {
+               *ptr2 = 0;
+               ptr2++;
+             }
+           cmdline = grub_malloc (totlen + 1 - (ptr2 - ptr));
+           if (!cmdline)
+             return grub_errno;
+           grub_memcpy (cmdline, curentry->append, ptr - curentry->append);
+           grub_memcpy (cmdline + (ptr - curentry->append),
+                        ptr2, totlen - (ptr2 - curentry->append));
+           *(cmdline + totlen - (ptr2 - ptr)) = 0;
+         }
+       else
+         cmdline = curentry->append;
+       print_string (" if test x$grub_platform = xpc; then "
+                     "linux_suffix=16; else linux_suffix= ; fi\n");
+       print_string ("  linux$linux_suffix ");
+       print_file (outbuf, menu, curentry->kernel_file, NULL);
+       print_string (" ");
+       if (cmdline)
+         print (outbuf, cmdline, grub_strlen (cmdline));
+       print_string ("\n");
+       if (initrd || curentry->initrds)
+         {
+           struct initrd_list *lst;
+           print_string ("  initrd$linux_suffix ");
+           if (initrd)
+             {
+               print_file (outbuf, menu, initrd, NULL);
+               print_string (" ");
+             }
+           for (lst = curentry->initrds; lst; lst = lst->next)
+             {
+               print_file (outbuf, menu, lst->file, NULL);
+               print_string (" ");
+             }
+
+           print_string ("\n");
+         }
+      }
+      break;
+    case KERNEL_CHAINLOADER:
+      print_string ("  chainloader ");
+      print_file (outbuf, menu, curentry->kernel_file, NULL);
+      print_string ("\n");
+      break;
+    case KERNEL_CHAINLOADER_BPB:
+      print_string ("  chainloader --bpb ");
+      print_file (outbuf, menu, curentry->kernel_file, NULL);
+      print_string ("\n");
+      break;
+    case LOCALBOOT:
+      /* FIXME: support -1.  */
+      /* FIXME: PXELINUX.  */
+      {
+       int n = grub_strtol (curentry->kernel_file, NULL, 0);
+       if (n >= 0 && n <= 0x02)
+         {
+           print_string ("  root=fd");
+           if (print_num (outbuf, n))
+             return grub_errno;
+           print_string (";\n  chainloader +1;\n");
+
+           break;
+         }
+       if (n >= 0x80 && n < 0x8a)
+         {
+           print_string ("  root=hd");
+           if (print_num (outbuf, n - 0x80))
+             return grub_errno;
+           print_string (";\n  chainloader +1;\n");
+           break;
+         }
+       print_string ("  # UNSUPPORTED localboot type ");
+       if (print_num (outbuf, n))
+         return grub_errno;
+       print_string ("\n");
+       break;
+      }
+    case KERNEL_COM32:
+    case KERNEL_COM:
+      {
+       char *basename = NULL;
+       
+       {
+         char *ptr;
+         for (ptr = curentry->kernel_file; *ptr; ptr++)
+           if (*ptr == '/' || *ptr == '\\')
+             basename = ptr;
+       }
+       if (!basename)
+         basename = curentry->kernel_file;
+       else
+         basename++;
+       if (grub_strcasecmp (basename, "chain.c32") == 0)
+         {
+           char *file = NULL;
+           int is_fd = -1, devn;
+           int part = -1;
+           int swap = 0;
+           char *ptr;
+           for (ptr = curentry->append; *ptr; )
+             {
+               while (grub_isspace (*ptr))
+                 ptr++;
+               /* FIXME: support mbr: and boot.  */
+               if (ptr[0] == 'h' && ptr[1] == 'd')
+                 {
+                   is_fd = 0;
+                   devn = grub_strtoul (ptr + 2, &ptr, 0);
+                   continue;
+                 }
+               if (grub_strncasecmp (ptr, "file=", 5) == 0)
+                 {
+                   file = ptr + 5;
+                   for (ptr = file; *ptr && !grub_isspace (*ptr); ptr++);
+                   if (*ptr)
+                     {
+                       *ptr = 0;
+                       ptr++;
+                     }
+                   continue;
+                 }
+               if (grub_strncasecmp (ptr, "swap", sizeof ("swap") - 1) == 0)
+                 {
+                   swap = 1;
+                   ptr += sizeof ("swap") - 1;
+                   continue;
+                 }
+
+               if (ptr[0] == 'f' && ptr[1] == 'd')
+                 {
+                   is_fd = 1;
+                   devn = grub_strtoul (ptr + 2, &ptr, 0);
+                   continue;
+                 }
+               if (grub_isdigit (ptr[0]))
+                 {
+                   part = grub_strtoul (ptr, &ptr, 0);
+                   continue;
+                 }
+               /* FIXME: isolinux, ntldr, cmldr, *dos, seg, hide
+                  FIXME: sethidden.  */
+               print_string ("  # UNSUPPORTED option ");
+               if (print (outbuf, ptr, grub_strlen (ptr)))
+                 return 0;
+               print_string ("\n");
+               break;
+             }
+           if (is_fd == -1)
+             {
+               print_string ("  # no drive specified\n");
+               break;
+             }
+           if (!*ptr)
+             {
+               print_string (is_fd ? " root=fd": " root=hd");
+               if (print_num (outbuf, devn))
+                 return grub_errno;
+               if (part != -1)
+                 {
+                   print_string (",");
+                   if (print_num (outbuf, part + 1))
+                     return grub_errno;
+                 }
+               print_string (";\n");
+               if (file)
+                 {
+                   print_string ("  chainloader ");
+                   print_file (outbuf, menu, file, NULL);
+                   print_string (";\n");
+                 }
+               else
+                 print_string (" chainloader +1;\n");
+               if (swap)
+                 print_string (" drivemap -s hd0 \"root\";\n");
+             }
+           break;
+         }
+
+       if (grub_strcasecmp (basename, "mboot.c32") == 0)
+         {
+           char *ptr;
+           int first = 1;
+           int is_kernel = 1;
+           for (ptr = curentry->append; *ptr; )
+             {
+               char *ptrr = ptr;
+               while (*ptr && !grub_isspace (*ptr))
+                 ptr++;
+               if (ptrr + 2 == ptr && ptrr[0] == '-' && ptrr[1] == '-')
+                 {
+                   print_string ("\n");
+                   first = 1;
+                   continue;
+                 }
+               if (first)
+                 {
+                   if (is_kernel)
+                     print_string ("  multiboot ");
+                   else
+                     print_string ("  module ");
+                   first = 0;
+                   is_kernel = 0;
+                   if (print_file (outbuf, menu, ptrr, ptr))
+                     return grub_errno;
+                   continue;
+                 }
+               if (print_escaped (outbuf, ptrr, ptr))
+                 return grub_errno;
+             }
+           break;
+         }
+
+       if (grub_strcasecmp (basename, "ifcpu64.c32") == 0)
+         {
+           char *lm, *lme, *pae = 0, *paee = 0, *i386s = 0, *i386e = 0;
+           char *ptr;
+           ptr = curentry->append;
+           while (grub_isspace (*ptr))
+             ptr++;
+           lm = ptr;
+           while (*ptr && !grub_isspace (*ptr))
+             ptr++;
+           lme = ptr;
+           while (grub_isspace (*ptr))
+             ptr++;
+           if (ptr[0] == '-' && ptr[1] == '-')
+             {
+               ptr += 2;
+               while (grub_isspace (*ptr))
+                 ptr++;
+               pae = ptr;
+               while (*ptr && !grub_isspace (*ptr))
+                 ptr++;
+               paee = ptr;
+             }
+           while (grub_isspace (*ptr))
+             ptr++;
+           if (ptr[0] == '-' && ptr[1] == '-')
+             {
+               ptr += 2;
+               while (grub_isspace (*ptr))
+                 ptr++;
+               i386s = ptr;
+               while (*ptr && !grub_isspace (*ptr))
+                 ptr++;
+               i386e = ptr;
+             }
+           if (lme)
+             *lme = '\0';
+           if (paee)
+             *paee = '\0';
+           if (i386e)
+             *i386e = '\0';
+           if (!i386s)
+             {
+               i386s = pae;
+               pae = 0;
+             }
+           print_string ("if cpuid --long-mode; then true;\n");
+           if (print_entry (outbuf, menu, lm))
+             return grub_errno;
+           if (pae)
+             {
+               print_string ("elif cpuid --pae; then true;\n");
+               if (print_entry (outbuf, menu, pae))
+                 return grub_errno;
+             }
+           print_string ("else\n");
+           if (print_entry (outbuf, menu, i386s))
+             return grub_errno;
+           print_string ("fi\n");
+           break;
+         }
+
+       if (grub_strcasecmp (basename, "reboot.c32") == 0)
+         {
+           print_string ("  reboot\n");
+           break;
+         }
+
+       if (grub_strcasecmp (basename, "poweroff.com") == 0)
+         {
+           print_string ("  halt\n");
+           break;
+         }
+
+       if (grub_strcasecmp (basename, "whichsys.c32") == 0)
+         {
+           grub_syslinux_flavour_t flavour = GRUB_SYSLINUX_ISOLINUX;
+           const char *flav[] = 
+             { 
+               [GRUB_SYSLINUX_ISOLINUX] = "iso",
+               [GRUB_SYSLINUX_PXELINUX] = "pxe",
+               [GRUB_SYSLINUX_SYSLINUX] = "sys"
+             };
+           char *ptr;
+           for (ptr = curentry->append; *ptr; )
+             {
+               char *bptr, c;
+               while (grub_isspace (*ptr))
+                 ptr++;
+               if (grub_strncasecmp (ptr, "-iso-", 5) == 0)
+                 {
+                   ptr += sizeof ("-iso-") - 1;
+                   flavour = GRUB_SYSLINUX_ISOLINUX;
+                   continue;
+                 }
+               if (grub_strncasecmp (ptr, "-pxe-", 5) == 0)
+                 {
+                   ptr += sizeof ("-pxe-") - 1;
+                   flavour = GRUB_SYSLINUX_PXELINUX;
+                   continue;
+                 }
+               if (grub_strncasecmp (ptr, "-sys-", 5) == 0)
+                 {
+                   ptr += sizeof ("-sys-") - 1;
+                   flavour = GRUB_SYSLINUX_SYSLINUX;
+                   continue;
+                 }
+               bptr = ptr;
+               while (*ptr && !grub_isspace (*ptr))
+                 ptr++;
+               c = *ptr;
+               *ptr = '\0';
+               if (menu->flavour == GRUB_SYSLINUX_UNKNOWN
+                   && flavour == GRUB_SYSLINUX_ISOLINUX)
+                 {
+                   print_string ("if [ x$syslinux_flavour = xiso -o x$syslinux_flavour = x ]; then true;\n");
+                   menu->flavour = GRUB_SYSLINUX_ISOLINUX;
+                   print_entry (outbuf, menu, bptr);
+                   menu->flavour = GRUB_SYSLINUX_UNKNOWN;
+                   print_string ("fi\n");
+                 }
+               else if (menu->flavour == GRUB_SYSLINUX_UNKNOWN)
+                 {
+                   print_string ("if [ x$syslinux_flavour = x");
+                   err = print (outbuf, flav[flavour], grub_strlen (flav[flavour]));
+                   if (err)
+                     return err;
+                   print_string (" ]; then true;\n");
+                   menu->flavour = flavour;
+                   print_entry (outbuf, menu, bptr);
+                   menu->flavour = GRUB_SYSLINUX_UNKNOWN;
+                   print_string ("fi\n");
+                 }
+               if (menu->flavour != GRUB_SYSLINUX_UNKNOWN
+                   && menu->flavour == flavour)
+                 print_entry (outbuf, menu, bptr);
+               *ptr = c;
+             }
+           break;
+         }
+
+       /* FIXME: gdb, GFXBoot, Hdt, Ifcpu, Ifplop, Kbdmap,
+          FIXME: Linux, Lua, Meminfo, rosh, Sanbboot  */
+
+       print_string ("  # UNSUPPORTED com(32) ");
+       err = print (outbuf, basename, grub_strlen (basename));
+       if (err)
+         return err;
+       print_string ("\ntrue;\n");
+       break;
+      }
+    case KERNEL_CONFIG:
+      {
+       char *new_cwd, *new_target_cwd;
+       const char *ap;
+       ap = curentry->append;
+       if (!ap)
+         ap = curentry->argument;
+       if (!ap)
+         ap = "";
+       new_cwd = get_read_filename (menu, ap);
+       if (!new_cwd)
+         return grub_errno;
+       new_target_cwd = get_target_filename (menu, ap);
+       if (!new_target_cwd)
+         return grub_errno;
+
+       struct syslinux_menu *menuptr;
+       char *newname;
+       int depth = 0;
+       
+       newname = get_read_filename (menu, curentry->kernel_file);
+       if (!newname)
+         return grub_errno;
+       simplify_filename (newname);
+
+       print_string ("#");
+       print_file (outbuf, menu, curentry->kernel_file, NULL);
+       print_string (" ");
+       print (outbuf, newname, grub_strlen (newname));
+       print_string (":\n");
+
+       for (menuptr = menu; menuptr; menuptr = menuptr->parent, depth++)
+         if (grub_strcmp (menuptr->filename, newname) == 0
+             || depth > 20)
+           break;
+       if (menuptr)
+         {
+           print_string ("  syslinux_configfile -r ");
+           print_file (outbuf, menu, "/", NULL);
+           print_string (" -c ");
+           print_file (outbuf, menu, ap, NULL);
+           print_string (" ");
+           print_file (outbuf, menu, curentry->kernel_file, NULL);
+           print_string ("\n");
+         }
+       else
+         {
+           err = config_file (outbuf, menu->root_read_directory,
+                              menu->root_target_directory, new_cwd, new_target_cwd,
+                              newname, menu, menu->flavour);
+           if (err == GRUB_ERR_FILE_NOT_FOUND
+               || err == GRUB_ERR_BAD_FILENAME)
+             {
+               grub_errno = err = GRUB_ERR_NONE;
+               print_string ("# File ");
+               err = print (outbuf, newname, grub_strlen (newname));
+               if (err)
+                 return err;
+               print_string (" not found\n");
+             }
+           if (err)
+             return err;
+         }
+       grub_free (newname);
+       grub_free (new_cwd);
+       grub_free (new_target_cwd);
+      }
+      break;
+    case KERNEL_NO_KERNEL:
+      /* FIXME: support this.  */
+    case KERNEL_BIN:
+    case KERNEL_PXE:
+    case KERNEL_IMG:
+      print_string ("  # UNSUPPORTED entry type ");
+      if (print_num (outbuf, curentry->entry_type))
+       return grub_errno;
+      print_string ("\ntrue;\n");
+      break;
+    }
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+print_entry (struct output_buffer *outbuf,
+            struct syslinux_menu *menu,
+            const char *str)
+{
+  struct syslinux_menuentry *curentry;
+  for (curentry = menu->entries; curentry; curentry = curentry->next)
+    if (grub_strcasecmp (curentry->label, str) == 0)
+      {
+       grub_err_t err;
+       err = write_entry (outbuf, menu, curentry);
+       if (err)
+         return err;
+      }
+  return GRUB_ERR_NONE;
+}
+
+static void
+free_menu (struct syslinux_menu *menu)
+{
+  struct syslinux_say *say, *nsay;
+  struct syslinux_menuentry *entry, *nentry;
+
+  grub_free (menu->def);
+  grub_free (menu->comments);
+  grub_free (menu->background);
+  for (say = menu->say; say ; say = nsay)
+    {
+      nsay = say->next;
+      grub_free (say->msg);
+      grub_free (say);
+    }
+
+  for (entry = menu->entries; entry ; entry = nentry)
+    {
+      nentry = entry->next;
+      struct initrd_list *initrd, *ninitrd;
+
+      for (initrd = entry->initrds; initrd ; initrd = ninitrd)
+       {
+         ninitrd = initrd->next;
+         grub_free (initrd->file);
+         grub_free (initrd);
+       }
+      grub_free (entry->comments);
+      grub_free (entry->kernel_file);
+      grub_free (entry->label);
+      grub_free (entry->extlabel);
+      grub_free (entry->append);
+      grub_free (entry->help);
+      grub_free (entry);
+    }
+}
+
+static grub_err_t
+config_file (struct output_buffer *outbuf,
+            const char *root, const char *target_root,
+            const char *cwd, const char *target_cwd,
+            const char *fname, struct syslinux_menu *parent,
+            grub_syslinux_flavour_t flav)
+{
+  grub_err_t err;
+  struct syslinux_menu menu;
+  struct syslinux_menuentry *curentry, *lentry;
+  struct syslinux_say *say;
+
+  grub_memset (&menu, 0, sizeof (menu));
+  menu.flavour = flav;
+  menu.root_read_directory = root;
+  menu.root_target_directory = target_root;
+  menu.current_read_directory = cwd;
+  menu.current_target_directory = target_cwd;
+
+  menu.filename = fname;
+  menu.parent = parent;
+  err = syslinux_parse_real (&menu);
+  if (err)
+    return err;
+
+  for (say = menu.say; say && say->next; say = say->next);
+  for (; say && say->prev; say = say->prev)
+    {
+      print_string ("echo ");
+      err = print_escaped (outbuf, say->msg, NULL);
+      if (err)
+       return err;
+      print_string ("\n");
+    }
+
+  if (menu.background)
+    {
+      print_string ("  background_image ");
+      err = print_file (outbuf, &menu, menu.background, NULL);
+      if (err)
+       return err;
+      print_string ("\n");
+    }
+
+  if (menu.timeout == 0 && menu.entries && menu.def)
+    {
+      err = print_entry (outbuf, &menu, menu.def);
+      if (err)
+       return err;
+    }
+  else if (menu.entries)
+    {
+      for (curentry = menu.entries; curentry->next; curentry = curentry->next);
+      lentry = curentry;
+
+      print_string ("set timeout='");
+      err = print_num (outbuf, (menu.timeout + 9) / 10);
+      if (err)
+       return err;
+      print_string ("\n");
+      if (menu.comments)
+       {
+         err = print (outbuf, menu.comments, grub_strlen (menu.comments));
+         if (err)
+           return err;
+       }
+
+      if (menu.def)
+       {
+         print_string (" default=");
+         err = print_escaped (outbuf, menu.def, NULL);
+         if (err)
+           return err;
+         print_string ("\n");
+       }
+      for (curentry = lentry; curentry; curentry = curentry->prev)
+       {      
+         print_string ("menuentry ");
+         err = print_escaped (outbuf,
+                              curentry->extlabel ? : curentry->label, NULL);
+         if (err)
+           return err;
+         if (curentry->hotkey)
+           {
+             char hk[] = { curentry->hotkey, '\0' };
+             print_string (" --hotkey '");
+             print_string (hk);
+             print_string ("'");
+           }
+         print_string (" --id ");
+         err = print_escaped (outbuf, curentry->label, NULL);
+         if (err)
+           return err;
+         print_string (" {\n");
+
+         err = write_entry (outbuf, &menu, curentry);
+         if (err)
+           return err;
+
+         print_string ("}\n");
+       }
+    }
+  free_menu (&menu);
+  return GRUB_ERR_NONE;
+}
+
+char *
+grub_syslinux_config_file (const char *base, const char *target_base,
+                          const char *cwd, const char *target_cwd,
+                          const char *fname, grub_syslinux_flavour_t flav)
+{
+  struct output_buffer outbuf = { 0, 0, 0 };
+  grub_err_t err;
+  err = config_file (&outbuf, base, target_base, cwd, target_cwd,
+                    fname, NULL, flav);
+  if (err)
+    return NULL;
+  err = print (&outbuf, "\0", 1);
+  if (err)
+    return NULL;
+  return outbuf.buf;
+}
index 84df360..4c57e09 100644 (file)
@@ -40,64 +40,6 @@ GRUB_MOD_LICENSE ("GPLv3+");
 static int nested_level = 0;
 int grub_normal_exit_level = 0;
 
-/* Read a line from the file FILE.  */
-char *
-grub_file_getline (grub_file_t file)
-{
-  char c;
-  grub_size_t pos = 0;
-  char *cmdline;
-  int have_newline = 0;
-  grub_size_t max_len = 64;
-
-  /* Initially locate some space.  */
-  cmdline = grub_malloc (max_len);
-  if (! cmdline)
-    return 0;
-
-  while (1)
-    {
-      if (grub_file_read (file, &c, 1) != 1)
-       break;
-
-      /* Skip all carriage returns.  */
-      if (c == '\r')
-       continue;
-
-
-      if (pos + 1 >= max_len)
-       {
-         char *old_cmdline = cmdline;
-         max_len = max_len * 2;
-         cmdline = grub_realloc (cmdline, max_len);
-         if (! cmdline)
-           {
-             grub_free (old_cmdline);
-             return 0;
-           }
-       }
-
-      if (c == '\n')
-       {
-         have_newline = 1;
-         break;
-       }
-
-      cmdline[pos++] = c;
-    }
-
-  cmdline[pos] = '\0';
-
-  /* If the buffer is empty, don't return anything at all.  */
-  if (pos == 0 && !have_newline)
-    {
-      grub_free (cmdline);
-      cmdline = 0;
-    }
-
-  return cmdline;
-}
-
 void
 grub_normal_free_menu (grub_menu_t menu)
 {
diff --git a/include/grub/syslinux_parse.h b/include/grub/syslinux_parse.h
new file mode 100644 (file)
index 0000000..3595763
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2013  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/>.
+ */
+
+#ifndef GRUB_SYSLINUX_PARSE_HEADER
+#define GRUB_SYSLINUX_PARSE_HEADER 1
+
+#include <grub/types.h>
+
+typedef enum
+  {
+    GRUB_SYSLINUX_UNKNOWN,
+    GRUB_SYSLINUX_ISOLINUX,
+    GRUB_SYSLINUX_PXELINUX,
+    GRUB_SYSLINUX_SYSLINUX,
+  } grub_syslinux_flavour_t;
+
+char *
+grub_syslinux_config_file (const char *root, const char *target_root,
+                          const char *cwd, const char *target_cwd,
+                          const char *fname, grub_syslinux_flavour_t flav);
+
+#endif
diff --git a/util/grub-syslinux2cfg.c b/util/grub-syslinux2cfg.c
new file mode 100644 (file)
index 0000000..54add8c
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2010,2012,2013 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 <config.h>
+
+#include <grub/util/misc.h>
+#include <grub/i18n.h>
+#include <grub/term.h>
+#include <grub/font.h>
+#include <grub/emu/hostdisk.h>
+
+#define _GNU_SOURCE    1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <argp.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grub/err.h>
+#include <grub/types.h>
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/syslinux_parse.h>
+
+#include "progname.h"
+
+struct arguments
+{
+  char *input;
+  char *root;
+  char *target_root;
+  char *cwd;
+  char *target_cwd;
+  char *output;
+  int verbosity;
+  grub_syslinux_flavour_t flav;
+};
+
+static struct argp_option options[] = {
+  {"target-root",  't', N_("DIR"), 0,
+   N_("root directory as it will be seen on runtime (default /)."), 0},
+  {"root",  'r', N_("DIR"), 0,
+   N_("root directory of the syslinux disk (default /)."), 0},
+  {"target-cwd",  'T', N_("DIR"), 0,
+   N_("current directory as it will be seen on runtime (default $pwd)."), 0},
+  {"cwd",  'c', N_("DIR"), 0,
+   N_("current directory of the syslinux disk (default $pwd)."), 0},
+
+  {"output",  'o', N_("FILE"), 0, N_("write output to FILE (default stdout)."), 0},
+  {"isolinux",     'i', 0,      0, N_("assume isolinux."), 0},
+  {"pxelinux",     'p', 0,      0, N_("assume pxelinux."), 0},
+  {"syslinux",     's', 0,      0, N_("assume syslinux."), 0},
+  {"verbose",     'v', 0,      0, N_("print verbose messages."), 0},
+  { 0, 0, 0, 0, 0, 0 }
+};
+
+static error_t
+argp_parser (int key, char *arg, struct argp_state *state)
+{
+  /* Get the input argument from argp_parse, which we
+     know is a pointer to our arguments structure. */
+  struct arguments *arguments = state->input;
+
+  switch (key)
+    {
+    case 't':
+      free (arguments->target_root);
+      arguments->target_root = xstrdup (arg);
+      break;
+
+    case 'T':
+      free (arguments->target_cwd);
+      arguments->target_cwd = xstrdup (arg);
+      break;
+
+    case 'c':
+      free (arguments->cwd);
+      arguments->cwd = xstrdup (arg);
+      break;
+
+    case 'o':
+      free (arguments->output);
+      arguments->output = xstrdup (arg);
+      break;
+
+    case ARGP_KEY_ARG:
+      if (!arguments->input)
+       {
+         arguments->input = xstrdup (arg);
+         return 0;
+       }
+      return ARGP_ERR_UNKNOWN;
+
+    case 'r':
+      free (arguments->root);
+      arguments->root = xstrdup (arg);
+      return 0;
+
+    case 'i':
+      arguments->flav = GRUB_SYSLINUX_ISOLINUX;
+      break;
+
+    case 's':
+      arguments->flav = GRUB_SYSLINUX_SYSLINUX;
+      break;
+    case 'p':
+      arguments->flav = GRUB_SYSLINUX_PXELINUX;
+      break;
+
+    case 'v':
+      arguments->verbosity++;
+      break;
+
+    default:
+      return ARGP_ERR_UNKNOWN;
+    }
+
+  return 0;
+}
+
+static struct argp argp = {
+  options, argp_parser, N_("[OPTIONS] FILE ROOT"),
+  N_("Transform syslinux config into GRUB one."),
+  NULL, NULL, NULL
+};
+
+int
+main (int argc, char *argv[])
+{
+  struct arguments arguments;
+
+  grub_util_host_init (&argc, &argv);
+
+  /* Check for options.  */
+  memset (&arguments, 0, sizeof (struct arguments));
+  if (argp_parse (&argp, argc, argv, 0, 0, &arguments) != 0)
+    {
+      fprintf (stderr, "%s", _("Error in parsing command line arguments\n"));
+      exit(1);
+    }
+
+  if (!arguments.input)
+    {
+      fprintf (stderr, "%s", _("Missing arguments\n"));
+      exit(1);
+    }
+
+  grub_init_all ();
+  grub_hostfs_init ();
+  grub_host_init ();
+
+  char *t, *inpfull, *rootfull, *res;
+  t = canonicalize_file_name (arguments.input);
+  if (!t)
+    {
+      grub_util_error (_("cannot open `%s': %s"), arguments.input,
+                      strerror (errno));
+    }  
+
+  inpfull = xasprintf ("(host)/%s", t);
+  free (t);
+
+  t = canonicalize_file_name (arguments.root ? : "/");
+  if (!t)
+    {
+      grub_util_error (_("cannot open `%s': %s"), arguments.root,
+                      strerror (errno));
+    }  
+
+  rootfull = xasprintf ("(host)/%s", t);
+  free (t);
+
+  char *cwd = xstrdup (arguments.input);
+  char *p = strrchr (cwd, '/');
+  char *cwdfull;
+  if (p)
+    *p = '\0';
+  else
+    {
+      free (cwd);
+      cwd = xstrdup (".");
+    }
+
+  t = canonicalize_file_name (arguments.cwd ? : cwd);
+  if (!t)
+    {
+      grub_util_error (_("cannot open `%s': %s"), arguments.root,
+                      strerror (errno));
+    }  
+
+  cwdfull = xasprintf ("(host)/%s", t);
+  free (t);
+
+  res = grub_syslinux_config_file (rootfull, arguments.target_root ? : "/",
+                                  cwdfull, arguments.target_cwd ? : cwd,
+                                  inpfull, arguments.flav);
+  if (!res)
+    grub_util_error ("%s", grub_errmsg);
+  if (arguments.output)
+    {
+      FILE *f = grub_util_fopen (arguments.output, "wb");
+      if (!f)
+       grub_util_error (_("cannot open `%s': %s"), arguments.output,
+                        strerror (errno));
+      fwrite (res, 1, strlen (res), f); 
+      fclose (f);
+    }
+  else
+    printf ("%s\n", res);
+  free (res);
+  free (rootfull);
+  free (inpfull);
+  free (arguments.root);
+  free (arguments.output);
+  free (arguments.target_root);
+  free (arguments.input);
+  free (cwdfull);
+  free (cwd);
+
+  return 0;
+}