/*
 *  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_("current directory of syslinux [default is parent directory of input file]."),
     N_("DIR"), ARG_TYPE_STRING},
    {"isolinux",     'i',  0, N_("assume input is an isolinux configuration file."), 0, 0},
    {"pxelinux",     'p',  0, N_("assume input is a pxelinux configuration file."), 0, 0},
    {"syslinux",     's',  0, N_("assume input is a syslinux configuration file."), 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_("Execute syslinux config in same context"),
			    options);
  cmd_configfile
    = grub_register_extcmd ("syslinux_configfile",
			    grub_cmd_syslinux_source, 0,
			    N_("FILE"),
			    N_("Execute syslinux config in new context"),
			    options);
  cmd_source_extract
    = grub_register_extcmd ("extract_syslinux_entries_source",
			    grub_cmd_syslinux_source, 0,
			    N_("FILE"),
			    N_("Execute 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_("Execute 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);
}
