/* linux.c -- Linux specific code for ifconfig
  Copyright (C) 2001-2023 Free Software Foundation, Inc.

  This file is part of GNU Inetutils.

  GNU Inetutils 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.

  GNU Inetutils 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 this program.  If not, see `http://www.gnu.org/licenses/'. */

/* Written by Marcus Brinkmann.  */

#include <config.h>

#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <xalloc.h>

#include <unistd.h>

#include <string.h>

#if STDC_HEADERS
# include <stdlib.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
#endif

#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#ifdef HAVE_NETINET_ETHER_H
# include <netinet/ether.h>
#endif

#include <read-file.h>
#include <attribute.h>

#include "../ifconfig.h"


/* ARPHRD stuff.  */

static void
print_hwaddr_ether (format_data_t form MAYBE_UNUSED, unsigned char *data)
{
  *column += printf ("%02X:%02X:%02X:%02X:%02X:%02X",
		     data[0], data[1], data[2], data[3], data[4], data[5]);
  had_output = 1;
}

static void
print_hwaddr_arcnet (format_data_t form MAYBE_UNUSED, unsigned char *data)
{
  *column += printf ("%02X", data[0]);
  had_output = 1;
}

static void
print_hwaddr_dlci (format_data_t form MAYBE_UNUSED, unsigned char *data)
{
  *column += printf ("%i", *((short *) data));
  had_output = 1;
}

static void
print_hwaddr_ax25 (format_data_t form, unsigned char *data)
{
  int i = 0;
  while (i < 6 && (data[i] >> 1) != ' ')
    {
      put_char (form, (data[i] >> 1));
      i++;
    }
  i = (data[6] & 0x1E) >> 1;
  if (i)
    {
      *column += printf ("-%i", i);
      had_output = 1;
    }
#undef mangle
}

static void
print_hwaddr_irda (format_data_t form MAYBE_UNUSED, unsigned char *data)
{
  *column += printf ("%02X:%02X:%02X:%02X",
		     data[3], data[2], data[1], data[0]);
  had_output = 1;
}

static void
print_hwaddr_rose (format_data_t form MAYBE_UNUSED, unsigned char *data)
{
  *column += printf ("%02X%02X%02X%02X%02X",
		     data[0], data[1], data[2], data[3], data[4]);
  had_output = 1;
}

struct arphrd_symbol
{
  const char *name;
  const char *title;
  int value;
  void (*print_hwaddr) (format_data_t form, unsigned char *data);
} arphrd_symbols[] = {
  /* ARP protocol HARDWARE identifiers. */
#ifdef ARPHRD_NETROM		/* From KA9Q: NET/ROM pseudo.  */
  {"NETROM", "AMPR NET/ROM", ARPHRD_NETROM, print_hwaddr_ax25},
#endif
#ifdef ARPHRD_ETHER		/* Ethernet 10/100Mbps.  */
  {"ETHER", "Ethernet", ARPHRD_ETHER, print_hwaddr_ether},
#endif
#ifdef ARPHRD_EETHER		/* Experimental Ethernet.  */
  {"EETHER", "Experimental Etherner", ARPHRD_EETHER, NULL},
#endif
#ifdef ARPHRD_AX25		/* AX.25 Level 2.  */
  {"AX25", "AMPR AX.25", ARPHRD_AX25, print_hwaddr_ax25},
#endif
#ifdef ARPHRD_PRONET		/* PROnet token ring.  */
  {"PRONET", "PROnet token ring", ARPHRD_PRONET, NULL},
#endif
#ifdef ARPHRD_CHAOS		/* Chaosnet.  */
  {"CHAOS", "Chaosnet", ARPHRD_CHAOS, NULL},
#endif
#ifdef ARPHRD_IEEE802		/* IEEE 802.2 Ethernet/TR/TB.  */
  {"IEEE802", "16/4 Mbps Token Ring", ARPHRD_IEEE802, print_hwaddr_ether},
#endif
#ifdef ARPHRD_ARCNET		/* ARCnet.  */
  {"ARCNET", "ARCnet", ARPHRD_ARCNET, print_hwaddr_arcnet},
#endif
#ifdef ARPHRD_APPLETLK		/* APPLEtalk.  */
  {"APPLETLK", "Appletalk", ARPHRD_APPLETLK, NULL},
#endif
#ifdef ARPHRD_DLCI		/* Frame Relay DLCI.  */
  {"DLCI", "Frame Relay DLCI", ARPHRD_DLCI, print_hwaddr_dlci},
#endif
#ifdef ARPHRD_ATM		/* ATM.  */
  {"ATM", "ATM", ARPHRD_ATM, NULL},
#endif
#ifdef ARPHRD_METRICOM		/* Metricom STRIP (new IANA id).  */
  {"METRICOM", "Metricom STRIP", ARPHRD_METRICOM, NULL},
#endif
  /* Dummy types for non ARP hardware.  */
#ifdef ARPHRD_SLIP
  {"SLIP", "Serial Line IP", ARPHRD_SLIP, NULL},
#endif
#ifdef ARPHRD_CSLIP
  {"CSLIP", "VJ Serial Line IP", ARPHRD_CSLIP, NULL},
#endif
#ifdef ARPHRD_SLIP6
  {"SLIP6", "6-bit Serial Line IP", ARPHRD_SLIP6, NULL},
#endif
#ifdef ARPHRD_CSLIP6
  {"CSLIP6", "VJ 6-bit Serial Line IP", ARPHRD_CSLIP6, NULL},
#endif
#ifdef ARPHRD_RSRVD		/* Notional KISS type.  */
  {"SLIP", "Notional KISS type", ARPHRD_SLIP, NULL},
#endif
#ifdef ARPHRD_ADAPT
  {"ADAPT", "Adaptive Serial Line IP", ARPHRD_ADAPT, NULL},
#endif
#ifdef ARPHRD_ROSE
  {"ROSE", "AMPR ROSE", ARPHRD_ROSE, print_hwaddr_rose},
#endif
#ifdef ARPHRD_X25		/* CCITT X.25.  */
  {"X25", "CCITT X.25", ARPHRD_X25, NULL},
#endif
#ifdef ARPHRD_HWX25		/* Boards with X.25 in firmware.  */
  {"HWX25", "CCITT X.25 in firmware", ARPHRD_HWX25, NULL},
#endif
#ifdef ARPHRD_PPP
  {"PPP", "Point-to-Point Protocol", ARPHRD_PPP, NULL},
#endif
#ifdef ARPHRD_HDLC		/* (Cisco) HDLC.  */
  {"HDLC", "(Cisco)-HDLC", ARPHRD_HDLC, NULL},
#endif
#ifdef ARPHRD_LAPB		/* LAPB.  */
  {"LAPB", "LAPB", ARPHRD_LAPB, NULL},
#endif
#ifdef ARPHRD_DDCMP		/* Digital's DDCMP.  */
  {"DDCMP", "DDCMP", ARPHRD_DDCMP, NULL},
#endif
#ifdef ARPHRD_TUNNEL		/* IPIP tunnel.  */
  {"TUNNEL", "IPIP Tunnel", ARPHRD_TUNNEL, NULL},
#endif
#ifdef ARPHRD_TUNNEL6		/* IPIP6 tunnel.  */
  {"TUNNEL", "IPIP6 Tunnel", ARPHRD_TUNNEL6, NULL},
#endif
#ifdef ARPHRD_FRAD		/* Frame Relay Access Device.  */
  {"FRAD", "Frame Relay Access Device", ARPHRD_FRAD, NULL},
#endif
#ifdef ARPHRD_SKIP		/* SKIP vif.  */
  {"SKIP", "SKIP vif", ARPHRD_SKIP, NULL},
#endif
#ifdef ARPHRD_LOOPBACK		/* Loopback device.  */
  {"LOOPBACK", "Local Loopback", ARPHRD_LOOPBACK, NULL},
#endif
#ifdef ARPHRD_LOCALTALK		/* Localtalk device.  */
  {"LOCALTALK", "Localtalk", ARPHRD_LOCALTALK, NULL},
#endif
#ifdef ARPHRD_FDDI		/* Fiber Distributed Data Interface. */
  {"FDDI", "Fiber Distributed Data Interface", ARPHRD_FDDI, NULL},
#endif
#ifdef ARPHRD_BIF		/* AP1000 BIF.  */
  {"BIF", "AP1000 BIF", ARPHRD_BIF, NULL},
#endif
#ifdef ARPHRD_SIT		/* sit0 device - IPv6-in-IPv4.  */
  {"SIT", "IPv6-in-IPv4", ARPHRD_SIT, NULL},
#endif
#ifdef ARPHRD_IPDDP		/* IP-in-DDP tunnel.  */
  {"IPDDP", "IP-in-DDP", ARPHRD_IPDDP, NULL},
#endif
#ifdef ARPHRD_IPGRE		/* GRE over IP.  */
  {"IPGRE", "GRE over IP", ARPHRD_IPGRE, NULL},
#endif
#ifdef ARPHRD_PIMREG		/* PIMSM register interface.  */
  {"PIMREG", "PIMSM register", ARPHRD_PIMREG, NULL},
#endif
#ifdef ARPHRD_HIPPI		/* High Performance Parallel I'face. */
  {"HIPPI", "HIPPI", ARPHRD_HIPPI, print_hwaddr_ether},
#endif
#ifdef ARPHRD_ASH		/* (Nexus Electronics) Ash.  */
  {"ASH", "Ash", ARPHRD_ASH, NULL},
#endif
#ifdef ARPHRD_ECONET		/* Acorn Econet.  */
  {"ECONET", "Econet", ARPHRD_ECONET, NULL},
#endif
#ifdef ARPHRD_IRDA		/* Linux-IrDA.  */
  {"IRDA", "IrLap", ARPHRD_IRDA, print_hwaddr_irda},
#endif
#ifdef ARPHRD_FCPP		/* Point to point fibrechanel.  */
  {"FCPP", "FCPP", ARPHRD_FCPP, NULL},
#endif
#ifdef ARPHRD_FCAL		/* Fibrechanel arbitrated loop.  */
  {"FCAL", "FCAL", ARPHRD_FCAL, NULL},
#endif
#ifdef ARPHRD_FCPL		/* Fibrechanel public loop.  */
  {"FCPL", "FCPL", ARPHRD_FCPL, NULL},
#endif
#ifdef ARPHRD_FCPFABRIC		/* Fibrechanel fabric.  */
  {"FCFABRIC", "FCFABRIC", ARPHRD_FCPFABRIC, NULL},
#endif
#ifdef ARPHRD_IEEE802_TR	/* Magic type ident for TR.  */
  {"IEEE802_TR", "16/4 Mbps Token Ring (New)", ARPHRD_IEEE802_TR,
   print_hwaddr_ether},
#endif
#ifdef ARPHRD_VOID
  {"VOID", "Void (nothing is known)", ARPHRD_VOID, NULL},
#endif
};

struct arphrd_symbol *
arphrd_findvalue (int value)
{
  struct arphrd_symbol *arp = arphrd_symbols;
  while (arp->name != NULL)
    {
      if (arp->value == value)
	break;
      arp++;
    }
  if (arp->name)
    return arp;
  else
    return NULL;
}


struct pnd_stats
{
  struct pnd_stats *next;
  char *name;
  unsigned long long rx_packets;	/* total packets received */
  unsigned long long tx_packets;	/* total packets transmitted */
  unsigned long long rx_bytes;	/* total bytes received */
  unsigned long long tx_bytes;	/* total bytes transmitted */
  unsigned long rx_errors;	/* packets receive errors */
  unsigned long tx_errors;	/* packet transmit errors */
  unsigned long rx_dropped;	/* input packets dropped */
  unsigned long tx_dropped;	/* transmit packets dropped */
  unsigned long rx_multicast;	/* multicast packets received */
  unsigned long rx_compressed;	/* compressed packets received */
  unsigned long tx_compressed;	/* compressed packets transmitted */
  unsigned long collisions;

  /* detailed rx_errors: */
  unsigned long rx_length_errors;
  unsigned long rx_over_errors;	/* receiver ring buffer overflow */
  unsigned long rx_crc_errors;	/* received packets with crc error */
  unsigned long rx_frame_errors;	/* received frame alignment errors */
  unsigned long rx_fifo_errors;	/* recveiver fifo overruns */
  unsigned long rx_missed_errors;	/* receiver missed packets */
  /* detailed tx_errors */
  unsigned long tx_aborted_errors;
  unsigned long tx_carrier_errors;
  unsigned long tx_fifo_errors;
  unsigned long tx_heartbeat_errors;
  unsigned long tx_window_errors;
};

struct pnd_stats *stats_head, *stats_tail;
static int pnd_stats_init = 0;

static int
procnet_parse_fields_v1 (char *buf, struct pnd_stats *stats)
{
  int n = sscanf (buf,
		  "%llu %lu %lu %lu %lu %llu %lu %lu %lu %lu %lu",
		  &stats->rx_packets,
		  &stats->rx_errors,
		  &stats->rx_dropped,
		  &stats->rx_fifo_errors,
		  &stats->rx_frame_errors,
		  &stats->tx_packets,
		  &stats->tx_errors,
		  &stats->tx_dropped,
		  &stats->tx_fifo_errors,
		  &stats->collisions,
		  &stats->tx_carrier_errors);
  stats->rx_bytes = 0;
  stats->tx_bytes = 0;
  stats->rx_multicast = 0;
  return n == 11;
}

static int
procnet_parse_fields_v2 (char *buf, struct pnd_stats *stats)
{
  int n = sscanf (buf,
		  "%llu %llu %lu %lu %lu %lu %llu %llu %lu %lu %lu %lu %lu",
		  &stats->rx_bytes,
		  &stats->rx_packets,
		  &stats->rx_errors,
		  &stats->rx_dropped,
		  &stats->rx_fifo_errors,
		  &stats->rx_frame_errors,
		  &stats->tx_bytes,
		  &stats->tx_packets,
		  &stats->tx_errors,
		  &stats->tx_dropped,
		  &stats->tx_fifo_errors,
		  &stats->collisions,
		  &stats->tx_carrier_errors);
  stats->rx_multicast = 0;
  return n == 13;
}

static int
procnet_parse_fields_v3 (char *buf, struct pnd_stats *stats)
{
  int n = sscanf (buf,
		  "%llu %llu %lu %lu %lu %lu %lu %lu %llu %llu %lu %lu %lu %lu %lu %lu",
		  &stats->rx_bytes,
		  &stats->rx_packets,
		  &stats->rx_errors,
		  &stats->rx_dropped,
		  &stats->rx_fifo_errors,
		  &stats->rx_frame_errors,
		  &stats->rx_compressed,
		  &stats->rx_multicast,
		  &stats->tx_bytes,
		  &stats->tx_packets,
		  &stats->tx_errors,
		  &stats->tx_dropped,
		  &stats->tx_fifo_errors,
		  &stats->collisions,
		  &stats->tx_carrier_errors,
		  &stats->tx_compressed);
  return n == 16;
}

static int (*pnd_parser[]) (char *, struct pnd_stats *) = {
  procnet_parse_fields_v1,
  procnet_parse_fields_v2,
  procnet_parse_fields_v3
};

static int
pnd_version (char *buf)
{
  if (strstr (buf, "compressed"))
    return 2;
  if (strstr (buf, "bytes"))
    return 1;
  return 0;
}

static void
pnd_read ()
{
  FILE *fp;
  char *buf = NULL;
  size_t bufsize = 0;
  int v;

  fp = fopen (PATH_PROCNET_DEV, "r");
  if (!fp)
    {
      error (0, errno, "cannot open %s", PATH_PROCNET_DEV);
      return;
    }

  /* Skip header lines */
  if (getline (&buf, &bufsize, fp) < 0 || getline (&buf, &bufsize, fp) < 0)
    {
      error (0, errno, "malformed %s", PATH_PROCNET_DEV);
      fclose (fp);
      free (buf);
      return;
    }

  v = pnd_version (buf);

  while (getline (&buf, &bufsize, fp) > 0)
    {
      struct pnd_stats *stats = xzalloc (sizeof (*stats));
      char *p = buf, *q;
      size_t len;

      while (*p
	     && isascii (*(unsigned char *) p)
	     && isspace (*(unsigned char *) p))
	p++;
      q = strchr (p, ':');
      if (!q)
	{
	  error (0, errno, "malformed %s", PATH_PROCNET_DEV);
	  free (stats);
	  break;
	}
      len = q - p;
      stats->name = xmalloc (len + 1);
      memcpy (stats->name, p, len);
      stats->name[len] = 0;
      if (pnd_parser[v] (q + 1, stats) == 0)
	{
	  error (0, errno, "malformed %s", PATH_PROCNET_DEV);
	  free (stats);
	  break;
	}
      stats->next = NULL;
      if (stats_tail)
	stats_tail->next = stats;
      else
	stats_head = stats;
      stats_tail = stats;
    }

  fclose (fp);
  free (buf);
}

struct pnd_stats *
pnd_stats_locate (const char *name)
{
  struct pnd_stats *stats;
  if (!pnd_stats_init)
    {
      pnd_read ();
      pnd_stats_init = 1;
    }
  for (stats = stats_head; stats; stats = stats->next)
    if (strcmp (stats->name, name) == 0)
      break;
  return stats;
}



/* Output format stuff.  */

const char *system_default_format = "net-tools";

void
system_fh_ifstat_query (format_data_t form, int argc, char *argv[])
{
  select_arg (form, argc, argv,
	      pnd_stats_locate (form->ifr->ifr_name) ? 0 : 1);
}

#define _IU_DECLARE(fld)						     \
void                                                                         \
_IU_CAT2 (system_fh_,fld) (format_data_t form, int argc, char *argv[])       \
{                                                                            \
  struct pnd_stats *stats = pnd_stats_locate (form->ifr->ifr_name);          \
  if (!stats)                                                                \
    put_string (form, "(" #fld " unknown)");                                 \
  else								             \
    put_ulong (form, argc, argv, stats->fld);                                \
}

_IU_DECLARE (rx_packets)
_IU_DECLARE (tx_packets)
_IU_DECLARE (rx_bytes)
_IU_DECLARE (tx_bytes)
_IU_DECLARE (rx_errors)
_IU_DECLARE (tx_errors)
_IU_DECLARE (rx_dropped)
_IU_DECLARE (tx_dropped)
_IU_DECLARE (rx_multicast)
_IU_DECLARE (rx_compressed)
_IU_DECLARE (tx_compressed)
_IU_DECLARE (collisions)
_IU_DECLARE (rx_length_errors)
_IU_DECLARE (rx_over_errors)
_IU_DECLARE (rx_crc_errors)
_IU_DECLARE (rx_frame_errors)
_IU_DECLARE (rx_fifo_errors)
_IU_DECLARE (rx_missed_errors)
_IU_DECLARE (tx_aborted_errors)
_IU_DECLARE (tx_carrier_errors)
_IU_DECLARE (tx_fifo_errors)
_IU_DECLARE (tx_heartbeat_errors) _IU_DECLARE (tx_window_errors)
     void
     system_fh_hwaddr_query (format_data_t form, int argc, char *argv[])
{
#ifdef SIOCGIFHWADDR
  struct arphrd_symbol *arp;

  if (ioctl (form->sfd, SIOCGIFHWADDR, form->ifr) < 0)
    select_arg (form, argc, argv, 1);

  arp = arphrd_findvalue (form->ifr->ifr_hwaddr.sa_family);
  select_arg (form, argc, argv, (arp && arp->print_hwaddr) ? 0 : 1);
#else
  select_arg (form, argc, argv, 1);
#endif
}

void
system_fh_hwaddr (format_data_t form, int argc MAYBE_UNUSED,
		  MAYBE_UNUSED char *argv[])
{
#ifdef SIOCGIFHWADDR
  if (ioctl (form->sfd, SIOCGIFHWADDR, form->ifr) < 0)
    error (EXIT_FAILURE, errno,
	   "SIOCGIFHWADDR failed for interface `%s'", form->ifr->ifr_name);
  else
    {
      struct arphrd_symbol *arp;

      arp = arphrd_findvalue (form->ifr->ifr_hwaddr.sa_family);
      if (arp && arp->print_hwaddr)
	arp->print_hwaddr (form,
			   (unsigned char *) form->ifr->ifr_hwaddr.sa_data);
      else
	put_string (form, "(hwaddr unknown)");
    }
#else
  *column += printf ("(not available)");
  had_output = 1;
#endif
}

void
system_fh_hwtype_query (format_data_t form, int argc, char *argv[])
{
#ifdef SIOCGIFHWADDR
  if (ioctl (form->sfd, SIOCGIFHWADDR, form->ifr) >= 0)
    select_arg (form, argc, argv, 0);
  else
#endif
    select_arg (form, argc, argv, 1);
}

void
system_fh_hwtype (format_data_t form, int argc MAYBE_UNUSED,
		  MAYBE_UNUSED char *argv[])
{
#ifdef SIOCGIFHWADDR
  if (ioctl (form->sfd, SIOCGIFHWADDR, form->ifr) < 0)
    error (EXIT_FAILURE, errno,
	   "SIOCGIFHWADDR failed for interface `%s'", form->ifr->ifr_name);
  else
    {
      struct arphrd_symbol *arp;

      arp = arphrd_findvalue (form->ifr->ifr_hwaddr.sa_family);
      if (arp)
	put_string (form, arp->title);
      else
	put_string (form, "(hwtype unknown)");
    }
#else
  *column += printf ("(not available)");
  had_output = 1;
#endif
}

/* Accept every value of metric as printable.  The net-tools'
 * implementation of ifconfig displays metric 0 as `1', so we
 * aim at the same thing, even though all other unices disagree.
 */
void
system_fh_metric_query (format_data_t form, int argc, char *argv[])
{
#ifdef SIOCGIFMETRIC
  if (ioctl (form->sfd, SIOCGIFMETRIC, form->ifr) >= 0)
    select_arg (form, argc, argv, 0);
  else
#endif
    select_arg (form, argc, argv, 1);
}

void
system_fh_metric (format_data_t form, int argc, char *argv[])
{
#ifdef SIOCGIFMETRIC
  if (ioctl (form->sfd, SIOCGIFMETRIC, form->ifr) < 0)
    error (EXIT_FAILURE, errno,
	   "SIOCGIFMETRIC failed for interface `%s'", form->ifr->ifr_name);
  else
    put_int (form, argc, argv,
	     form->ifr->ifr_metric ? form->ifr->ifr_metric : 1);
#else
  *column += printf ("(not available)");
  had_output = 1;
#endif
}

void
system_fh_txqlen_query (format_data_t form, int argc, char *argv[])
{
#ifdef SIOCGIFTXQLEN
  if (ioctl (form->sfd, SIOCGIFTXQLEN, form->ifr) >= 0)
    select_arg (form, argc, argv, (form->ifr->ifr_qlen >= 0) ? 0 : 1);
  else
#endif
    select_arg (form, argc, argv, 1);
}

void
system_fh_txqlen (format_data_t form, int argc, char *argv[])
{
#ifdef SIOCGIFTXQLEN
  if (ioctl (form->sfd, SIOCGIFTXQLEN, form->ifr) < 0)
    error (EXIT_FAILURE, errno,
	   "SIOCGIFTXQLEN failed for interface `%s'", form->ifr->ifr_name);
  else
    put_int (form, argc, argv, form->ifr->ifr_qlen);
#else
  *column += printf ("(not available)");
  had_output = 1;
#endif
}


/* Argument parsing stuff.  */

const char *system_help = "\
 NAME [ADDR] [broadcast BRDADDR]\
 [pointopoint|dstaddr DSTADDR] [netmask MASK]\
 [ether|hwaddr|lladdr MACADDR]\
 [metric N] [mtu N] [txqueuelen N] [up|down] [FLAGS]";

void
system_parse_opt_set_txqlen (struct ifconfig *ifp, char *arg)
{
  char *end;

  if (!ifp)
    error (EXIT_FAILURE, 0, "no interface specified for txqlen `%s'", arg);

  if (!(ifp->valid & IF_VALID_SYSTEM))
    {
      ifp->system = malloc (sizeof (struct system_ifconfig));
      if (!ifp->system)
	error (EXIT_FAILURE, errno,
	       "can't get memory for system interface configuration");
      ifp->system->valid = 0;
      ifp->valid |= IF_VALID_SYSTEM;
    }
  if (ifp->system->valid & IF_VALID_TXQLEN)
    error (EXIT_FAILURE, 0,
	   "only one txqlen allowed for interface `%s'", ifp->name);
  ifp->system->txqlen = strtol (arg, &end, 0);
  if (*arg == '\0' || *end != '\0')
    error (EXIT_FAILURE, 0,
	   "txqlen value `%s' for interface `%s' is not a number",
	   arg, ifp->name);
  ifp->system->valid |= IF_VALID_TXQLEN;
}

static struct argp_option linux_argp_options[] = {
  {"txqlen", 'T', "N", 0,
   "set transmit queue length to N", 0},
  {NULL, 0, NULL, 0, NULL, 0}
};

static error_t
linux_argp_parser (int key, char *arg, struct argp_state *state)
{
  struct ifconfig *ifp = *(struct ifconfig **) state->input;
  switch (key)
    {
    case 'T':			/* txqlen */
      system_parse_opt_set_txqlen (ifp, arg);
      break;

    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

static struct argp linux_argp =
  { linux_argp_options, linux_argp_parser, NULL, NULL, NULL, NULL, NULL };

struct argp_child system_argp_child = {
  &linux_argp,
  0,
  "Linux-specific options",
  0
};


int
system_parse_opt (struct ifconfig **ifpp, char option, char *arg)
{
  struct ifconfig *ifp = *ifpp;

  switch (option)
    {
    case 'T':			/* txqlen */
      system_parse_opt_set_txqlen (ifp, arg);
      break;

    default:
      return 0;
    }
  return 1;
}

int
system_parse_opt_rest (struct ifconfig **ifp, int argc, char *argv[])
{
  int i = 0;
  enum
  {
    EXPECT_AF,
    EXPECT_NOTHING,
    EXPECT_BROADCAST,
    EXPECT_DSTADDR,
    EXPECT_NETMASK,
    EXPECT_HWADDR,
    EXPECT_MTU,
    EXPECT_METRIC,
    EXPECT_TXQLEN,
  } expect = EXPECT_AF;
  int mask, rev;

  *ifp = parse_opt_new_ifs (argv[0]);

  while (++i < argc)
    {
      switch (expect)
	{
	case EXPECT_BROADCAST:
	  parse_opt_set_brdaddr (*ifp, argv[i]);
	  break;

	case EXPECT_DSTADDR:
	  parse_opt_set_point_to_point (*ifp, argv[i]);
	  break;

	case EXPECT_NETMASK:
	  parse_opt_set_netmask (*ifp, argv[i]);
	  break;

	case EXPECT_HWADDR:
	  parse_opt_set_hwaddr (*ifp, argv[i]);
	  break;

	case EXPECT_MTU:
	  parse_opt_set_mtu (*ifp, argv[i]);
	  break;

	case EXPECT_METRIC:
	  parse_opt_set_metric (*ifp, argv[i]);
	  break;

	case EXPECT_TXQLEN:
	  system_parse_opt_set_txqlen (*ifp, argv[i]);
	  break;

	case EXPECT_AF:
	  expect = EXPECT_NOTHING;
	  if (!strcmp (argv[i], "inet"))
	    continue;
	  else if (!strcmp (argv[i], "inet6"))
	    {
	      error (0, 0, "%s is not a supported address family", argv[i]);
	      return 0;
	    }
	  break;

	case EXPECT_NOTHING:
	  break;
	}

      if (expect != EXPECT_NOTHING)
	expect = EXPECT_NOTHING;
      else if (!strcmp (argv[i], "broadcast"))
	expect = EXPECT_BROADCAST;
      else if (!strcmp (argv[i], "dstaddr")
	       || !strcmp (argv[i], "pointopoint"))
	expect = EXPECT_DSTADDR;
      else if (!strcmp (argv[i], "netmask"))
	expect = EXPECT_NETMASK;
      else if (!strcmp (argv[i], "ether")
	       || !strcmp (argv[i], "hwaddr") || !strcmp (argv[i], "lladdr"))
	expect = EXPECT_HWADDR;
      else if (!strcmp (argv[i], "metric"))
	expect = EXPECT_METRIC;
      else if (!strcmp (argv[i], "mtu"))
	expect = EXPECT_MTU;
      else if (!strcmp (argv[i], "txqueuelen"))
	expect = EXPECT_TXQLEN;
      else if (!strcmp (argv[i], "up"))
	parse_opt_set_flag (*ifp, IFF_UP | IFF_RUNNING, 0);
      else if (!strcmp (argv[i], "down"))
	parse_opt_set_flag (*ifp, IFF_UP, 1);
      else if (((mask = if_nameztoflag (argv[i], &rev))
		& ~IU_IFF_CANTCHANGE) != 0)
	parse_opt_set_flag (*ifp, mask, rev);
      else
	parse_opt_set_address (*ifp, argv[i]);
    }

  switch (expect)
    {
    case EXPECT_BROADCAST:
      error (0, 0, "option `broadcast' requires an argument");
      break;

    case EXPECT_DSTADDR:
      error (0, 0, "option `pointopoint' (`dstaddr') requires an argument");
      break;

    case EXPECT_NETMASK:
      error (0, 0, "option `netmask' requires an argument");
      break;

    case EXPECT_HWADDR:
      error (0, 0, "option `hwaddr' requires an argument");
      break;

    case EXPECT_METRIC:
      error (0, 0, "option `metric' requires an argument");
      break;

    case EXPECT_MTU:
      error (0, 0, "option `mtu' requires an argument");
      break;

    case EXPECT_TXQLEN:
      error (0, 0, "option `txqueuelen' requires an argument");
      break;

    case EXPECT_AF:
    case EXPECT_NOTHING:
      return 1;
    }
  return 0;
}


int
system_preconfigure (int sfd MAYBE_UNUSED, struct ifreq *ifr MAYBE_UNUSED)
{
  return 0;
}

int
system_configure (int sfd, struct ifreq *ifr, struct system_ifconfig *ifs)
{
  if (ifs->valid & IF_VALID_TXQLEN)
    {
#ifndef SIOCSIFTXQLEN
      error (0, 0, "don't know how to set the txqlen on this system");
      return -1;
#else
      int err = 0;

      ifr->ifr_qlen = ifs->txqlen;
      err = ioctl (sfd, SIOCSIFTXQLEN, ifr);
      if (err < 0)
	error (0, errno, "SIOCSIFTXQLEN failed");
      if (verbose)
	printf ("Set txqlen value of `%s' to `%i'.\n",
		ifr->ifr_name, ifr->ifr_qlen);
#endif
    }
  return 0;
}

static struct if_nameindex *
linux_if_nameindex (void)
{
  char *content, *it;
  size_t length, index;
  struct if_nameindex *idx = NULL;
  int fd;

  fd = socket (AF_INET, SOCK_DGRAM, 0);
  if (fd < 0)
    return NULL;

  /* Read a public text file.  */
  content = read_file (PATH_PROCNET_DEV, 0, &length);
  if (content == NULL)
    return NULL;

  /* Count how many interfaces we have.  */
  {
    size_t n = 0;
    it = content;
    do
      {
	it = memchr (it + 1, ':', length - (it - content));
	n++;
      }
    while (it);

    idx = malloc (n * sizeof (*idx));
    if (idx == NULL)
      {
	int saved_errno = errno;
	close (fd);
	free (content);
	errno = saved_errno;
	return NULL;
      }
  }

  for (it = memchr (content, ':', length), index = 0; it;
       it = memchr (it, ':', length - (it - content)), index++)
    {
      char *start = it - 1;
      *it = '\0';

      while (*start != ' ' && *start != '\n')
	start--;

      idx[index].if_name = strdup (start + 1);
      idx[index].if_index = index + 1;

#if defined SIOCGIFINDEX
      {
	struct ifreq cur;
	strcpy (cur.ifr_name, idx[index].if_name);
	cur.ifr_index = -1;
	if (ioctl (fd, SIOCGIFINDEX, &cur) >= 0)
	  idx[index].if_index = cur.ifr_index;
      }
#endif

      if (idx[index].if_name == NULL)
	{
	  int saved_errno = errno;
	  close (fd);
	  free (content);
	  errno = saved_errno;
	  return NULL;
	}
    }

  idx[index].if_index = 0;
  idx[index].if_name = NULL;

  free (content);
  return idx;
}



/* System hooks. */

struct if_nameindex *(*system_if_nameindex) (void) = linux_if_nameindex;
