/* handler.c - support handler loading */
/*
 *  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/err.h>
#include <grub/env.h>
#include <grub/misc.h>
#include <grub/command.h>
#include <grub/handler.h>
#include <grub/normal.h>
#include <grub/lib.h>

struct grub_handler_list
{
  struct grub_handler_list *next;
  char *name;
  grub_command_t cmd;
};

static grub_list_t handler_list;

static grub_err_t
grub_handler_cmd (struct grub_command *cmd,
		  int argc __attribute__ ((unused)),
		  char **args __attribute__ ((unused)))
{
  char *p;
  grub_handler_class_t class;
  grub_handler_t handler;

  p = grub_strchr (cmd->name, '.');
  if (! p)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid command name");

  if (cmd->data)
    {
      if (! grub_dl_get (cmd->data))
	{
	  grub_dl_t mod;

	  mod = grub_dl_load (cmd->data);
	  if (mod)
	    grub_dl_ref (mod);
	  else
	    return grub_errno;
	}
      grub_free (cmd->data);
      cmd->data = 0;
    }

  *p = 0;
  class = grub_named_list_find (GRUB_AS_NAMED_LIST (grub_handler_class_list),
				cmd->name);
  *p = '.';

  if (! class)
    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "class not found");


  handler = grub_named_list_find (GRUB_AS_NAMED_LIST (class->handler_list),
				  p + 1);
  if (! handler)
    return grub_error (GRUB_ERR_FILE_NOT_FOUND, "handler not found");

  grub_handler_set_current (class, handler);

  return 0;
}

static void
insert_handler (char *name, char *module)
{
  struct grub_handler_list *item;
  char *data;

  if (grub_command_find (name))
    return;

  item = grub_malloc (sizeof (*item));
  if (! item)
    return;

  item->name = grub_strdup (name);
  if (! item->name)
    {
      grub_free (item);
      return;
    }

  if (module)
    {
      data = grub_strdup (module);
      if (! data)
	{
	  grub_free (item->name);
	  grub_free (item);
	  return;
	}
    }
  else
    data = 0;

  item->cmd = grub_reg_cmd (item->name, grub_handler_cmd, 0,
			    "Set active handler.", 0);
  if (! item->cmd)
    {
      grub_free (data);
      grub_free (item->name);
      grub_free (item);
      return;
    }

  item->cmd->data = data;
  grub_list_push (&handler_list, GRUB_AS_LIST (item));
}

static int
iterate_handler (grub_handler_t handler, void *closure)
{
  const char *class_name = closure;
  char name[grub_strlen (class_name) + grub_strlen (handler->name) + 2];

  grub_strcpy (name, class_name);
  grub_strcat (name, ".");
  grub_strcat (name, handler->name);

  insert_handler (name, 0);

  return 0;
}

static int
iterate_class (grub_handler_class_t class,
	       void *closure __attribute ((unused)))
{
  grub_list_iterate (GRUB_AS_LIST (class->handler_list),
		     (grub_list_hook_t) iterate_handler, (char *) class->name);

  return 0;
}

/* Read the file handler.lst for auto-loading.  */
void
read_handler_list (void)
{
  const char *prefix;
  static int first_time = 1;

  /* Make sure that this function does not get executed twice.  */
  if (! first_time)
    return;
  first_time = 0;

#ifdef GRUB_MACHINE_EMU
  (void) prefix;
#else
  prefix = grub_env_get ("prefix");
  if (prefix)
    {
      char *filename;

      filename = grub_xasprintf ("%s/handler.lst", prefix);
      if (filename)
	{
	  grub_file_t file;

	  file = grub_file_open (filename);
	  if (file)
	    {
	      char *buf = NULL;
	      for (;; grub_free (buf))
		{
		  char *p;

		  buf = grub_getline (file);

		  if (! buf)
		    break;

		  if (! grub_isgraph (buf[0]))
		    continue;

		  p = grub_strchr (buf, ':');
		  if (! p)
		    continue;

		  *p = '\0';
		  while (*++p == ' ')
		    ;

		  insert_handler (buf, p);
		}
	      grub_file_close (file);
	    }
	  grub_free (filename);
	}
    }
#endif

  grub_list_iterate (GRUB_AS_LIST (grub_handler_class_list),
		     (grub_list_hook_t) iterate_class, 0);

  /* Ignore errors.  */
  grub_errno = GRUB_ERR_NONE;
}

void
free_handler_list (void)
{
  struct grub_handler_list *item;

  while ((item = grub_list_pop (&handler_list)) != 0)
    {
      grub_free (item->cmd->data);
      grub_unregister_command (item->cmd);
      grub_free (item->name);
      grub_free (item);
    }
}
