/* Interface name and statistics get function using proc file system
 * Copyright (C) 1999 Kunihiro Ishiguro
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra 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.
 *
 * GNU Zebra 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 GNU Zebra; see the file COPYING.  If not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.  
 */

#include <zebra.h>

#include "if.h"
#include "prefix.h"
#include "log.h"

#include "zebra/ioctl.h"
#include "zebra/connected.h"
#include "zebra/interface.h"

/* Proc filesystem one line buffer. */
#define PROCBUFSIZ                  1024

/* Path to device proc file system. */
#ifndef _PATH_PROC_NET_DEV
#define _PATH_PROC_NET_DEV        "/proc/net/dev"
#endif /* _PATH_PROC_NET_DEV */

/* Return statistics data pointer. */
static char *
interface_name_cut (char *buf, char **name)
{
  char *stat;

  /* Skip white space.  Line will include header spaces. */
  while (*buf == ' ')
    buf++;
  *name = buf;

  /* Cut interface name. */
  stat = strrchr (buf, ':');
  *stat++ = '\0';

  return stat;
}

/* Fetch each statistics field. */
static int
ifstat_dev_fields (int version, char *buf, struct interface *ifp)
{
  switch (version) 
    {
    case 3:
      sscanf(buf,
	     "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
	     &ifp->stats.rx_bytes,
	     &ifp->stats.rx_packets,
	     &ifp->stats.rx_errors,
	     &ifp->stats.rx_dropped,
	     &ifp->stats.rx_fifo_errors,
	     &ifp->stats.rx_frame_errors,
	     &ifp->stats.rx_compressed,
	     &ifp->stats.rx_multicast,

	     &ifp->stats.tx_bytes,
	     &ifp->stats.tx_packets,
	     &ifp->stats.tx_errors,
	     &ifp->stats.tx_dropped,
	     &ifp->stats.tx_fifo_errors,
	     &ifp->stats.collisions,
	     &ifp->stats.tx_carrier_errors,
	     &ifp->stats.tx_compressed);
      break;
    case 2:
      sscanf(buf, "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
	     &ifp->stats.rx_bytes,
	     &ifp->stats.rx_packets,
	     &ifp->stats.rx_errors,
	     &ifp->stats.rx_dropped,
	     &ifp->stats.rx_fifo_errors,
	     &ifp->stats.rx_frame_errors,

	     &ifp->stats.tx_bytes,
	     &ifp->stats.tx_packets,
	     &ifp->stats.tx_errors,
	     &ifp->stats.tx_dropped,
	     &ifp->stats.tx_fifo_errors,
	     &ifp->stats.collisions,
	     &ifp->stats.tx_carrier_errors);
      ifp->stats.rx_multicast = 0;
      break;
    case 1:
      sscanf(buf, "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
	     &ifp->stats.rx_packets,
	     &ifp->stats.rx_errors,
	     &ifp->stats.rx_dropped,
	     &ifp->stats.rx_fifo_errors,
	     &ifp->stats.rx_frame_errors,

	     &ifp->stats.tx_packets,
	     &ifp->stats.tx_errors,
	     &ifp->stats.tx_dropped,
	     &ifp->stats.tx_fifo_errors,
	     &ifp->stats.collisions,
	     &ifp->stats.tx_carrier_errors);
      ifp->stats.rx_bytes = 0;
      ifp->stats.tx_bytes = 0;
      ifp->stats.rx_multicast = 0;
      break;
    }
  return 0;
}

/* Update interface's statistics. */
void
ifstat_update_proc (void)
{
  FILE *fp;
  char buf[PROCBUFSIZ];
  int version;
  struct interface *ifp;
  char *stat;
  char *name;

  /* Open /proc/net/dev. */
  fp = fopen (_PATH_PROC_NET_DEV, "r");
  if (fp == NULL)
    {
      zlog_warn ("Can't open proc file %s: %s",
		 _PATH_PROC_NET_DEV, safe_strerror (errno));
      return;
    }

  /* Drop header lines. */
  fgets (buf, PROCBUFSIZ, fp);
  fgets (buf, PROCBUFSIZ, fp);

  /* To detect proc format veresion, parse second line. */
  if (strstr (buf, "compressed"))
    version = 3;
  else if (strstr (buf, "bytes"))
    version = 2;
  else
    version = 1;

  /* Update each interface's statistics. */
  while (fgets (buf, PROCBUFSIZ, fp) != NULL)
    {
      stat = interface_name_cut (buf, &name);
      ifp = if_get_by_name (name);
      ifstat_dev_fields (version, stat, ifp);
    }
  fclose(fp);
  return;
}

/* Interface structure allocation by proc filesystem. */
int
interface_list_proc ()
{
  FILE *fp;
  char buf[PROCBUFSIZ];
  struct interface *ifp;
  char *name;

  /* Open /proc/net/dev. */
  fp = fopen (_PATH_PROC_NET_DEV, "r");
  if (fp == NULL)
    {
      zlog_warn ("Can't open proc file %s: %s",
		 _PATH_PROC_NET_DEV, safe_strerror (errno));
      return -1;
    }

  /* Drop header lines. */
  fgets (buf, PROCBUFSIZ, fp);
  fgets (buf, PROCBUFSIZ, fp);

  /* Only allocate interface structure.  Other jobs will be done in
     if_ioctl.c. */
  while (fgets (buf, PROCBUFSIZ, fp) != NULL)
    {
      interface_name_cut (buf, &name);
      ifp = if_get_by_name (name);
      if_add_update (ifp);
    }
  fclose(fp);
  return 0;
}

#if defined(HAVE_IPV6) && defined(HAVE_PROC_NET_IF_INET6)

#ifndef _PATH_PROC_NET_IF_INET6
#define _PATH_PROC_NET_IF_INET6          "/proc/net/if_inet6"
#endif /* _PATH_PROC_NET_IF_INET6 */

int
ifaddr_proc_ipv6 ()
{
  FILE *fp;
  char buf[PROCBUFSIZ];
  int n;
  char addr[33];
  char ifname[21];
  int ifindex, plen, scope, status;
  struct interface *ifp;
  struct prefix_ipv6 p;

  /* Open proc file system. */
  fp = fopen (_PATH_PROC_NET_IF_INET6, "r");
  if (fp == NULL)
    {
      zlog_warn ("Can't open proc file %s: %s",
		 _PATH_PROC_NET_IF_INET6, safe_strerror (errno));
      return -1;
    }
  
  /* Get interface's IPv6 address. */
  while (fgets (buf, PROCBUFSIZ, fp) != NULL)
    {
      n = sscanf (buf, "%32s %02x %02x %02x %02x %20s", 
		  addr, &ifindex, &plen, &scope, &status, ifname);
      if (n != 6)
	continue;

      ifp = if_get_by_name (ifname);

      /* Fetch interface's IPv6 address. */
      str2in6_addr (addr, &p.prefix);
      p.prefixlen = plen;

      connected_add_ipv6 (ifp, 0, &p.prefix, p.prefixlen, NULL, ifname);
    }
  fclose (fp);
  return 0;
}
#endif /* HAVE_IPV6 && HAVE_PROC_NET_IF_INET6 */
