/***************************************************************************
                                  proc.cpp
                             -------------------
    begin                : Mon Aug 9 1999
    copyright            : (C) 1999 by Markus Gustavsson
                           (C) 2001 - 2003 by Roland Riegel
    email                : mighty@fragzone.se
                           feedback@roland-riegel.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 of the License, or     *
 *   (at your option) any later version.                                   * 
 *                                                                         *
 ***************************************************************************/


#include "proc.h"

#include <config.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <unistd.h>

#ifdef HAVE_LINUX
	#include <sys/time.h>
	#include <string>
	using std::string;
#endif

#ifdef HAVE_BSD
	#include <sys/param.h>
	#include <sys/sysctl.h>
	#include <net/if_dl.h>
	#include <net/route.h>
	#include <string>
	using std::string;
#endif

#ifdef HAVE_SOLARIS
	#include <kstat.h>
	#include <sys/sockio.h>
#endif

#ifdef HAVE_HPUX
	#include <fcntl.h>
	#include <sys/stropts.h>
	#include <sys/dlpi.h>
	#include <sys/dlpi_ext.h>
	#include <sys/mib.h>
#endif

Proc::Proc()
{
	m_total[0] = m_total[1] = 0;
	m_time_last_read = m_elapsed_time = 0;
}

Proc::~Proc()
{
}

void Proc::setProcDev(const char *new_procdev)
{
	unsigned long dummy;
	strcpy( m_dev, new_procdev );
	readLoad( dummy, dummy );
}

const char* Proc::procDev()
{
	return m_dev;
}

bool Proc::procDevExists()
{
	return m_dev_exists;
}

const char* Proc::ip()
{
	struct sockaddr_in* sin;
	struct ifreq ifr;
	int sk;
	
	m_ip[0] = 0;
	
	if( m_dev[0] == 0 ) return m_ip;
	
	/* create a temporary socket: ioctl needs one */
	if( ( sk = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) return m_ip;
	
	/* copy the device name into the ifreq structure */
	strncpy( ifr.ifr_name, m_dev, IFNAMSIZ - 1 );
	ifr.ifr_name[ IFNAMSIZ - 1 ] = 0;
	
	/* make the request */
	if( ! ioctl( sk, SIOCGIFADDR, &ifr ) )
	{
		sin = (struct sockaddr_in *) ( &ifr.ifr_addr );
		
		/* only use the IP number if the address family is really IPv4 */
		if( sin->sin_family == AF_INET )
		{
			char* str_ip = inet_ntoa( sin->sin_addr );
			sprintf( m_ip, "%s", str_ip );
		}
	}
	
	/* close the temporary socket */
	close( sk );
	
	return m_ip;
}

void Proc::readLoad( unsigned long& in, unsigned long& out )
{
	unsigned long total_new[2] = { 0, 0 };
	int curr_time = 0;
	struct timeval time;

	in = out = 0;
	
	//measure the ellapsed time since the last function call
	gettimeofday( &time, NULL );

	curr_time = time.tv_sec * 1000 + time.tv_usec / 1000;
	m_elapsed_time = abs( curr_time - m_time_last_read );

	m_time_last_read = curr_time;
	
	m_dev_exists = false;
	
	if( m_dev[0] == 0 )
		return;
	
// === Linux specific network data reading code ===
// Code taken out of knetload: Copyright by Markus Gustavsson <mighty@fragzone.se>
//                             modified by Roland Riegel <feedback@roland-riegel.de>
// ================================================
#ifdef HAVE_LINUX
	FILE *fd;
	char buf[512] = "";
	char dev[128] = "";
	char *tmp, *tmp2;

	do
	{
		if( ( fd = fopen( "/proc/net/dev", "r" ) ) == NULL )
			break;
		
		fgets( buf, 512, fd );
		fgets( buf, 512, fd );
		
		m_dev_exists = false;
		
		while( !feof( fd ) )
		{
			fgets( buf, 512, fd );
			
			memset( dev, 0, 32 );
			
			tmp = buf;
			tmp2 = dev;
			
			while( *tmp == ' ' ) tmp++;
			while( ( *tmp2++ = *tmp++ ) != ':' );
			
			*--tmp2 = '\0';
			
			if( ! strcmp( m_dev, dev ) )
			{
				sscanf( tmp, "%lu %*u %*u %*u %*u %*u %*u %*u %lu", &total_new[0], &total_new[1] );
				
				if( total_new[0] > m_total[0] )
					in = total_new[0] - m_total[0];
				m_total[0] = total_new[0];
				
				if( total_new[1] > m_total[1] )
					out = total_new[1] - m_total[1];
				m_total[1] = total_new[1];
				
				m_dev_exists = true;
				
				break;
			}
		}
	} while( 0 );

	fclose(fd);
	
#endif
// === End Linux specific network data reading code ===
	
// === Free/Net/OpenBSD specific network data reading code ===
// Code taken out of gkrellm: Copyright by Bill Wilson <bill@gkrellm.net>
//                            FreeBSD code contributed by Hajimu Umemoto <ume@mahoroba.org>
//                            NetBSD code contributed by Anthony Mallet <anthony.mallet@useless-ficus.net>
//                            Hajimu Umemoto merged Free/Net/OpenBSD code
//                            Roland Riegel fixed a memory leak <feedback@roland-riegel.de>
// ===========================================================
#ifdef HAVE_BSD
	struct if_msghdr *ifm, *nextifm;
	struct sockaddr_dl *sdl;
	char *lim, *next;
	size_t needed;
	char s[32];
	int mib[] = { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0 };
	char *buf = 0;
	
	do
	{
		if( sysctl( mib, 6, NULL, &needed, NULL, 0 ) < 0 )
			break;
		
		buf = (char *) malloc( needed );
		if( buf == NULL )
			break;
		
		if( sysctl( mib, 6, buf, &needed, NULL, 0 ) < 0 )
			break;
		
		lim = buf + needed;
		
		next = buf;
		while( next < lim )
		{
			ifm = (struct if_msghdr *) next;
			if( ifm->ifm_type != RTM_IFINFO )
				break;
			
			next += ifm->ifm_msglen;
			
			while( next < lim )
			{
				nextifm = (struct if_msghdr *) next;
				if( nextifm->ifm_type != RTM_NEWADDR )
					break;
				next += nextifm->ifm_msglen;
			}
			
			if( ifm->ifm_flags & IFF_UP )
			{
				sdl = (struct sockaddr_dl *) ( ifm + 1 );
				if( sdl->sdl_family != AF_LINK )
					continue;
				strncpy( s, sdl->sdl_data, sdl->sdl_nlen );
				s[ sdl->sdl_nlen ] = '\0';
				
				if( strcmp( m_dev, s ) == 0 )
				{
					total_new[0] = ifm->ifm_data.ifi_ibytes;
					if( total_new[0] > m_total[0] )
						in = total_new[0] - m_total[0];
					m_total[0] = total_new[0];
					
					total_new[1] = ifm->ifm_data.ifi_obytes;
					if( total_new[1] > m_total[1] )
						out = total_new[1] - m_total[1];
					m_total[1] = total_new[1];
					
					m_dev_exists = true;
					
					break;
				}
			}
		}
	} while ( 0 );
	
	free( buf );
	
#endif
// === End Free/Net/OpenBSD specific network data reading code ===
	
// === Solaris specific network data reading code ===
// Code taken out of gkrellm: Copyright by Bill Wilson <bill@gkrellm.net>
//                            Solaris code by Daisuke Yabuki <dxy@acm.org>
//                            adapted for nload by Roland Riegel <feedback@roland-riegel.de>
// ==================================================
#ifdef HAVE_SOLARIS
	kstat_ctl_t *kc;
	kstat_t *ksp;
	kstat_named_t *knp;
	
	kc = kstat_open();
	ksp = kstat_lookup( kc, NULL, -1, m_dev );
	if( ksp && kstat_read( kc, ksp, NULL ) >= 0 )
	{
		knp = (kstat_named_t *) kstat_data_lookup( ksp, "rbytes" );
		if( knp )
		{
			total_new[0] = knp->value.ui32;
			if( total_new[0] > m_total[0] )
				in = total_new[0] - m_total[0];
			m_total[0] = total_new[0];
		}

		knp = (kstat_named_t *) kstat_data_lookup( ksp, "obytes" );
		if( knp )
		{
			total_new[1] = knp->value.ui32;
			if( total_new[1] > m_total[1] )
				out = total_new[1] - m_total[1];
			m_total[1] = total_new[1];
		}

		m_dev_exists = true;
	}

	kstat_close( kc );
#endif
// === End Solaris specific network data reading code ===
	
// === HP-UX specific network data reading code ===
/**
  *
  * Copyright 1998 by Hewlett-Packard Company
  *
  * Permission to use, copy, modify, and distribute this
  * software and its documentation for any purpose and without
  * fee is hereby granted, provided that the above copyright
  * notice appear in all copies and that both that copyright
  * notice and this permission notice appear in supporting
  * documentation, and that the name of Hewlett-Packard Company not
  * be used in advertising or publicity pertaining to distribution
  * of the software without specific, written prior permission.
  * Hewlett-Packard makes no representations about the
  * suitability of this software for any purpose.  It is provided
  * "as is" without express or implied warranty.
  *
  *
  *     Modification History:
  *
*/
/*
        int_stats.c : Get network statistics from the dlpi device
        On 10.20 only get a mib_ifEntry, on 11.0 this is followed
        by an mib_Dot3StatsEntry. Accessing the latter structure
        is left as an exercise for the reader, the struct is defined
        in /usr/include/sys/mib.h along with mib_ifEntry.

        Author: Jon Dewis, SPP 3/26/98
        (With ackn to 'lanadmin' source code and streams class notes)

        24-June-1998            Add mib3 stats (AJD) when compiling
                                for 11.0 ensure HP_UX11 macro defined
                                e.g. cc -o int_stats -DHP_UX11 int_stats.c
        16-Dec-1999             Change to obviate need for lanscan to get
                                nmid - thus assumes any old nmid will do
                                and just iterates thru the 1st 100 possibilities
                                (MAX_NMID). Works on 10.20 and 11.0
        Dec 2003                
        Modified by Roshan Sequeira roshan.sequeira@hp.com, to get network 
        statistics for the nload port to HP-UX. Original code available at 
        http://h21007.www2.hp.com/dspp/tech/tech_TechDocumentDetailPage_IDX/1,1701,2599,00.html
*/

	#ifdef HAVE_HPUX
	#define AREA_SIZE 40000
	#define LONG_AREA_SIZE  (AREA_SIZE / sizeof(u_long))
	#define TRUE 1
	
	static u_long ctrl_area[LONG_AREA_SIZE];	/* for control messages */
	static u_long data_area[LONG_AREA_SIZE];	/* for data messages */
	static u_long ppa_area[LONG_AREA_SIZE]; 	/* for saving ppa area */
	static struct strbuf ctrl_buf = {AREA_SIZE, 0, (char*)ctrl_area};
	static struct strbuf data_buf = {AREA_SIZE, 0, (char*)data_area};
	
	dl_get_statistics_req_t *get_statistics_req =
	                (dl_get_statistics_req_t *) ctrl_area;
    dl_get_statistics_ack_t *get_statistics_ack =
	                (dl_get_statistics_ack_t *) ctrl_area;
	
	dl_hp_ppa_info_t ppa_info, *ppa_info_temp;
	dl_hp_ppa_req_t *ppa_req;
	dl_hp_ppa_ack_t *ppa_ack;
	dl_attach_req_t *attach_req;
	mib_ifEntry     *mib_ptr;
	
	char *ppa_no = NULL;
	unsigned int ppa = 0;
	int fd = 0, flags = 0, ppa_count = 0, count = 0, found = 0;
	
	do
	{
		//Make sure interface name begins with lan
		if ( strspn(m_dev, "lan") < 3 )
			break;
		
		//Get the PPA from the interface name
		ppa_no = (char*)malloc((strlen(m_dev)-3));
		strcpy(ppa_no, (m_dev+3));
		ppa = atoi(ppa_no);
		free(ppa_no);
		
		if ( (fd = open("/dev/dlpi", O_RDWR)) < 0) {
			perror("Open /dev/dlpi");
			break;
		}
		
		ppa_req = (dl_hp_ppa_req_t *)ctrl_area;
		ppa_ack = (dl_hp_ppa_ack_t *)ctrl_area;
		
		ppa_req->dl_primitive = DL_HP_PPA_REQ;
		
		ctrl_buf.len = sizeof(dl_hp_ppa_req_t);
		if (putmsg(fd, &ctrl_buf, 0, 0) < 0) {
			perror("putmsg DL_HP_PPA_REQ");
			break;
	    }
	    flags = 0;
	    ctrl_area[0] = 0;
		
		if (getmsg(fd, &ctrl_buf, &data_buf, &flags) < 0) {
			perror("getmsg DL_HP_PPA_REQ");
			break;
		}
		
		if (ppa_ack->dl_length == 0) {
			fprintf(stderr, "Error: No PPAs available\n");
			break;
		}
		
		//Save all the PPA information.
		memcpy((u_char *)ppa_area, (u_char *)ctrl_area+ppa_ack->dl_offset,
		       ppa_ack->dl_length);
		ppa_count = ppa_ack->dl_count;
		
		for (count = found = 0, ppa_info_temp = (dl_hp_ppa_info_t *)ppa_area;
		     count<ppa_count; count++, ppa_info_temp++) {
			if ( ppa_info_temp->dl_ppa == ppa ) {
				found = TRUE;
				break;
			}
		}
		if (!found) {
			fprintf(stderr, "Error: PPA %d not found\n", ppa);
			break;
		}
		
		attach_req = (dl_attach_req_t *)ctrl_area;
		attach_req->dl_primitive = DL_ATTACH_REQ;
		attach_req->dl_ppa = ppa;
		ctrl_buf.len = sizeof(dl_attach_req_t);
		if (putmsg(fd, &ctrl_buf, 0, 0) < 0) {
			perror("putmsg DL_ATTACH_REQ");
			break;
	    }
		
		ctrl_area[0] = 0;
		if (getmsg(fd, &ctrl_buf, &data_buf, &flags) < 0) {
			perror("getmsg DL_ATTACH_REQ");
			break;
		}
		memcpy(&ppa_info, ppa_info_temp, sizeof(ppa_info));
		
		get_statistics_req->dl_primitive = DL_GET_STATISTICS_REQ;
		ctrl_buf.len = sizeof(dl_get_statistics_req_t);
		flags = 0;
		
		if ( putmsg(fd, &ctrl_buf, NULL, 0) < 0) {
			perror("putmsg DL_GET_STATISTICS_REQ");
			break;
		}
		
		if (getmsg(fd, &ctrl_buf, NULL, &flags) < 0) {
			perror("getmsg DL_GET_STATISTICS_REQ");
			break;
		}
		if (get_statistics_ack->dl_primitive != DL_GET_STATISTICS_ACK)
			fprintf(stderr, "Error: Wrong primitive\n");
		
		mib_ptr = (mib_ifEntry *)((u_char *) ctrl_area + get_statistics_ack->dl_stat_offset);
		
		close(fd);
		
		total_new[0] = mib_ptr->ifInOctets;
		total_new[1] = mib_ptr->ifOutOctets;
		
		if( total_new[0] > m_total[0] )
			in = total_new[0] - m_total[0];
		m_total[0] = total_new[0];
		
		if( total_new[1] > m_total[1] )
			out = total_new[1] - m_total[1];
		m_total[1] = total_new[1];
		
		m_dev_exists = true;
	} while( 0 );
	
#endif
// === End HP-UX specific network data reading code ===
	
	if( ! m_dev_exists )
		m_total[0] = m_total[1] = 0;
	
	return;
}

int Proc::getElapsedTime()
{
	return m_elapsed_time;
}

unsigned long Proc::totalIn()
{
	return m_total[0];
}

unsigned long Proc::totalOut()
{
	return m_total[1];
}



syntax highlighted by Code2HTML, v. 0.9.1