/*
   Copyright (C) 1996, 1997, 2000, 2001, 2006, 2007, 2017
     Free Software Foundation, Inc.

   Written by Miles Bader <miles@gnu.org>

   This file is part of the GNU Hurd.

   The GNU Hurd 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 2, or (at
   your option) any later version.

   The GNU Hurd 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 the GNU Hurd.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Fsysopts and command line option parsing */

#include <options.h>

#include <stdlib.h>
#include <argp.h>
#include <argz.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if_arp.h>
#include <error.h>

#include <lwip/netif.h>
#include <lwip/tcpip.h>

#include <lwip-hurd.h>
#include <lwip-util.h>
#include <netif/ifcommon.h>

/* Fsysopts and command line option parsing */

/* Adds an empty interface slot to H, and sets H's current interface to it, or
   returns an error. */
static error_t
parse_hook_add_interface (struct parse_hook *h)
{
  int i;

  struct parse_interface *new = realloc (h->interfaces,
					 (h->num_interfaces +
					  1) *
					 sizeof (struct parse_interface));
  if (!new)
    return ENOMEM;

  h->interfaces = new;
  h->num_interfaces++;
  h->curint = new + h->num_interfaces - 1;
  memset (&h->curint->dev_name, 0, DEV_NAME_LEN);
  h->curint->address.addr = INADDR_NONE;
  h->curint->netmask.addr = INADDR_NONE;
  h->curint->peer.addr = INADDR_NONE;
  h->curint->gateway.addr = INADDR_NONE;
  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++)
    ip6_addr_set_zero ((ip6_addr_t *) & h->curint->addr6[i]);

  return 0;
}

/* Option parser */
static error_t
parse_opt (int opt, char *arg, struct argp_state *state)
{
  error_t err = 0;
  struct parse_hook *h = state->hook;
  int i;

  /* Return _ERR from this routine */
#define RETURN(_err)                          \
  do { return _err; } while (0)

  /* Print a parsing error message and (if exiting is turned off) return the
     error code ERR.  */
#define PERR(err, fmt, args...)               \
  do { argp_error (state, fmt , ##args); RETURN (err); } while (0)

  /* Like PERR but for non-parsing errors.  */
#define FAIL(rerr, status, perr, fmt, args...)  \
  do{ argp_failure (state, status, perr, fmt , ##args); RETURN (rerr); } while(0)

  /* Parse STR and return the corresponding  internet address.  If STR is not
     a valid internet address, signal an error mentioned TYPE.  */
#undef	ADDR
#define ADDR(str, type)                         \
  ({ unsigned long addr = inet_addr (str);      \
     if (addr == INADDR_NONE) PERR (EINVAL, "Malformed %s", type);  \
     addr; })

  if (!arg && state->next < state->argc && (*state->argv[state->next] != '-'))
    {
      arg = state->argv[state->next];
      state->next++;
    }

  switch (opt)
    {
      struct parse_interface *in;
      uint8_t addr6_prefix_len;
      ip6_addr_t *address6;
      char *ptr;

    case 'i':
      /* An interface.  */
      err = 0;

      /* First see if a previously specified one is being re-specified.  */
      for (in = h->interfaces; in < h->interfaces + h->num_interfaces; in++)
	if (strcmp (in->dev_name, arg) == 0)
	  /* Re-use an old slot.  */
	  {
	    h->curint = in;
	    return 0;
	  }

      if (h->curint->dev_name[0])
	/* The current interface slot is not available.  */
	{
	  /* Add a new interface entry.  */
	  err = parse_hook_add_interface (h);
	}
      in = h->curint;

      strncpy (in->dev_name, arg, sizeof(in->dev_name)-1);
      break;

    case 'a':
      /* An address */
      if (arg)
	{
	  /* Check if it's legal */
	  h->curint->address.addr = ADDR (arg, "address");
	  if (!IN_CLASSA (ntohl (h->curint->address.addr))
	      && !IN_CLASSB (ntohl (h->curint->address.addr))
	      && !IN_CLASSC (ntohl (h->curint->address.addr)))
	    {
	      if (IN_MULTICAST (ntohl (h->curint->address.addr)))
		FAIL (EINVAL, 1, 0,
		      "%s: Cannot set interface address to multicast address",
		      arg);
	      else
		FAIL (EINVAL, 1, 0,
		      "%s: Illegal or undefined network address", arg);
	    }
	}
      else
	{
	  /* No address given, set default values */
	  h->curint->address.addr = ADDR ("0.0.0.0", "address");
	  h->curint->netmask.addr = ADDR ("255.0.0.0", "netmask");
	  h->curint->gateway.addr = INADDR_NONE;
	}
      break;

    case 'm':
      /* Netmask */
      if (arg)
	h->curint->netmask.addr = ADDR (arg, "netmask");
      else
	h->curint->netmask.addr = INADDR_NONE;
      break;

    case 'p':
      /* Peer address */
      if (arg)
	h->curint->peer.addr = ADDR (arg, "peer");
      else
	h->curint->peer.addr = INADDR_NONE;
      break;

    case 'g':
      /* Gateway for the current interface */
      if (arg)
	{
	  h->curint->gateway.addr = ADDR (arg, "gateway");
	}
      else
	h->curint->gateway.addr = INADDR_NONE;
      break;

    case '4':
      translator_bind (PORTCLASS_INET, arg);

      /* Install IPv6 port class on bootstrap port. */
      lwip_bootstrap_portclass = PORTCLASS_INET6;
      break;

    case '6':
      translator_bind (PORTCLASS_INET6, arg);
      break;

    case 'A':
      /* IPv6 address */
      if (arg)
	{
	  /* Check prefix */
	  if ((ptr = strchr (arg, '/')))
	    {
	      addr6_prefix_len = atoi (ptr + 1);
	      if (addr6_prefix_len > 128)
		FAIL (EINVAL, 1, 0, "%s: The prefix-length is invalid", arg);

	      /* Remove the prefix from the address */
	      *ptr = 0;

	      if (addr6_prefix_len != 64)
		{
		  error (0, 0,
			 "The only supported value for the prefix-length"
			 " is /64. Defaulting to %s/64.\n", arg);
		}
	    }
	  else
	    {
	      error (0, 0, "No prefix-length given, "
		     "defaulting to %s/64.\n", arg);
	    }

	  for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++)
	    {
	      address6 = (ip6_addr_t *) & h->curint->addr6[i];

	      /* Is the slot free? */
	      if (!ip6_addr_isany (address6))
		continue;

	      /* Use the slot */
	      if (ip6addr_aton (arg, address6) <= 0)
		PERR (EINVAL, "Malformed address");

	      break;
	    }
	}

      break;

    case ARGP_KEY_INIT:
      /* Initialize our parsing state.  */
      h = malloc (sizeof (struct parse_hook));
      if (!h)
	FAIL (ENOMEM, 11, ENOMEM, "option parsing");

      h->interfaces = 0;
      h->num_interfaces = 0;
      err = parse_hook_add_interface (h);
      if (err)
	FAIL (err, 12, err, "option parsing");

      state->hook = h;
      break;

    case ARGP_KEY_SUCCESS:
      /* If the interface list is not empty, a previous configuration exists */
      if (netif_list == 0)
	{
	  /* Inititalize LwIP */
	  tcpip_init (init_ifs, h);
	}
      else
	{
	  /* No need to initialize the stack again */
	  tcpip_callback (init_ifs, h);
	}
      break;

    case ARGP_KEY_ERROR:
      /* Parsing error occurred, free everything. */
      free (h->interfaces);
      free (h);
      break;

    default:
      return ARGP_ERR_UNKNOWN;
    }

  return err;
}

/* Create the output for fsysopts */
error_t
trivfs_append_args (struct trivfs_control * fsys, char **argz,
		    size_t * argz_len)
{
  error_t err = 0;
  struct netif *netif;
  int i;
  uint32_t addr, netmask, gateway;
  uint32_t addr6[LWIP_IPV6_NUM_ADDRESSES][4];
  uint8_t addr6_prefix_len[LWIP_IPV6_NUM_ADDRESSES];

#define ADD_OPT(fmt, args...)           \
  do { char buf[100];                   \
       if (! err) {                     \
         snprintf (buf, sizeof buf, fmt , ##args);      \
         err = argz_add (argz, argz_len, buf); } } while (0)
#define ADD_ADDR_OPT(name, addr)        \
  do { struct in_addr i;                \
       i.s_addr = (addr);               \
       ADD_OPT ("--%s=%s", name, inet_ntoa (i)); } while (0)

  NETIF_FOREACH(netif)
    {
      /* Skip the loopback interface */
      if (netif_get_state (netif)->type == ARPHRD_LOOPBACK)
	{
	  continue;
	}

      inquire_device (netif, &addr, &netmask, 0, 0, &gateway,
		      (uint32_t *) addr6, addr6_prefix_len);

      ADD_OPT ("--interface=%s", netif_get_state (netif)->devname);
      if (addr != INADDR_NONE)
	ADD_ADDR_OPT ("address", addr);
      if (netmask != INADDR_NONE)
	ADD_ADDR_OPT ("netmask", netmask);
      if (gateway != INADDR_NONE)
	ADD_ADDR_OPT ("gateway", gateway);
      for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++)
	if (!ip6_addr_isany (((ip6_addr_t *) & addr6[i])))
	  ADD_OPT ("--address6=%s/%d",
		   ip6addr_ntoa (((ip6_addr_t *) & addr6[i])),
		   addr6_prefix_len[i]);
    }

#undef ADD_ADDR_OPT

#undef ADD_OPT
  return err;
}

struct argp lwip_argp = { options, parse_opt, 0, doc };

struct argp *trivfs_runtime_argp = &lwip_argp;
