/* regexp.c -- The regexp command.  */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2005,2007  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/misc.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/env.h>
#include <grub/extcmd.h>
#include <grub/i18n.h>
#include <grub/script_sh.h>
#include <regex.h>

static const struct grub_arg_option options[] =
  {
    { "set", 's', GRUB_ARG_OPTION_REPEATABLE,
      N_("Variable names to update with matches."),
      N_("[NUMBER:]VARNAME"), ARG_TYPE_STRING },
    { 0, 0, 0, 0, 0, 0 }
  };

static grub_err_t
set_matches (char **varnames, char *str, grub_size_t nmatches,
	     regmatch_t *matches)
{
  int i;
  char ch;
  char *p;
  char *q;
  grub_err_t err;
  unsigned long j;

  auto void setvar (char *v, regmatch_t *m);
  void setvar (char *v, regmatch_t *m)
  {
    ch = str[m->rm_eo];
    str[m->rm_eo] = '\0';
    err = grub_env_set (v, str + m->rm_so);
    str[m->rm_eo] = ch;
  }

  for (i = 0; varnames && varnames[i]; i++)
    {
      if (! (p = grub_strchr (varnames[i], ':')))
	{
	  /* varname w/o index defaults to 1 */
	  if (nmatches < 2 || matches[1].rm_so == -1)
	    grub_env_unset (varnames[i]);
	  else
	    setvar (varnames[i], &matches[1]);
	}
      else
	{
	  j = grub_strtoul (varnames[i], &q, 10);
	  if (q != p)
	    return grub_error (GRUB_ERR_BAD_ARGUMENT,
			       "invalid variable name format %s", varnames[i]);

	  if (nmatches <= j || matches[j].rm_so == -1)
	    grub_env_unset (p + 1);
	  else
	    setvar (p + 1, &matches[j]);
	}

      if (err != GRUB_ERR_NONE)
	return err;
    }
  return GRUB_ERR_NONE;
}

static grub_err_t
grub_cmd_regexp (grub_extcmd_context_t ctxt, int argc, char **args)
{
  regex_t regex;
  int ret;
  grub_size_t s;
  char *comperr;
  grub_err_t err;
  regmatch_t *matches = 0;

  if (argc != 2)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "2 arguments expected");

  ret = regcomp (&regex, args[0], REG_EXTENDED);
  if (ret)
    goto fail;

  matches = grub_zalloc (sizeof (*matches) * (regex.re_nsub + 1));
  if (! matches)
    goto fail;

  ret = regexec (&regex, args[1], regex.re_nsub + 1, matches, 0);
  if (!ret)
    {
      err = set_matches (ctxt->state[0].args, args[1],
			 regex.re_nsub + 1, matches);
      regfree (&regex);
      grub_free (matches);
      return err;
    }

 fail:
  grub_free (matches);
  s = regerror (ret, &regex, 0, 0);
  comperr = grub_malloc (s);
  if (!comperr)
    {
      regfree (&regex);
      return grub_errno;
    }
  regerror (ret, &regex, comperr, s);
  err = grub_error (GRUB_ERR_TEST_FAILURE, "%s", comperr);
  regfree (&regex);
  grub_free (comperr);
  return err;
}

static grub_extcmd_t cmd;

GRUB_MOD_INIT(regexp)
{
  cmd = grub_register_extcmd ("regexp", grub_cmd_regexp, 0, N_("REGEXP STRING"),
			      N_("Test if REGEXP matches STRING."), options);

  /* Setup GRUB script wildcard translator.  */
  grub_wildcard_translator = &grub_filename_translator;
}

GRUB_MOD_FINI(regexp)
{
  grub_unregister_extcmd (cmd);
  grub_wildcard_translator = 0;
}
