#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>

/* -------------------------------------------------------------------------
   fusage - 
          - William Favorite, wfavorite@tablespace.net
	  - Released under GPL.

   Version History:
    0.2.0 -        - Original creation.
    0.3.0 - 6/1/4  - Additional command line parameters / features
    0.4.0 - 6/1/4  - cmdline mode now displays the entirety of /proc/X/cmdline
    0.5.0 - 9/28/4 - Included header info with the -h switch
    0.6.0 - 1/19/6 - Minor cleanup prior to posting on web site.
*/

/* -------------------------------------------------------------------------
   Set some defines
*/
/* Version string - self explanatory */
#define VERSION_STRING "0.6.0"

/* What type of summary information to display */
#define SUMMARY_NONE   0
#define SUMMARY_NORMAL 1
#define SUMMARY_ONLY   2

/* Should we truncate process names in normal (! -c) output. */
#define TRUNCATE_NAMES

/* Should we show the "sum" total of files. This will conflict with the number
   of kernel files - as threads have the same file-handles open(?). Turn this
   off to limit confusing elements of the display. */
#define SUM_TOTAL

/* 1 is on, 0 is off. Whatever this is, -h will toggle the value. */
#define DEFAULT_HEADER 1

/* -------------------------------------------------------------------------
   Globals
*/
/* This is used by PrintCmdLine(). I declared it as a global so that it does
   not need to be instantiated each time we call the function. */
char file[1024];


/* -------------------------------------------------------------------------
   Prototypes, etc...
*/
/* Struct - proc fd count */
struct procfdc
{
  unsigned long pid;
  char name[256];
  int fdcnt;
  struct procfdc *next;
};

/* -------------------------------------------------------------------------
   Checks to see if the string is comprised entirely of numbers.
*/
int isanumber(const char *cp)
{
  int i = 0;

  while(cp[i] != 0)
  {
    if((cp[i] < '0') || (cp[i] > '9'))
      return(0);
    i++;
  }
  return(1);
}

/* -------------------------------------------------------------------------
   Print the data from /proc/sys/fs/file-nr
*/
void PrintSysFileStat(void);

/* -------------------------------------------------------------------------
   Print the command line from /proc/<pid>/cmdline
*/
void PrintCmdLine(unsigned long pid);

/* ------------------------------------------------------------------------- */
int main(int argc, char *argv[])
{
  /**** Declare Variables ****/ 
  DIR *procd;              /* Directory handle                               */
  DIR *fdd;
  struct dirent *proce;    /* Return structure from readdir()                */
  struct dirent *fde;
  char statfname[1024];    /* String to hold filename of /proc/nnn/stat      */
  char fddirname[1024];
  FILE *statf;             /* File (stat) handle                             */
  char statbuf[1024];      /* Holder for stat contents                       */
  int i, j;                /* Counter for stepping through stat buffer       */
  int cnt;
  int got;
  unsigned long tmppid;
  struct procfdc *basepde;
  struct procfdc *thispde, *lastpde, *sortpde;
  char tmpstr[256];
  unsigned long totalcnt;
  int linesout;           /* How many lines to output                        */
  int showheader;         /* Show the header or not */
  int showall;            /* Boolean - sets linesout to ALL when true        */
  int cmdline;            /* 0 = show command, 1 = show entire command line. */
  int summary;            /* What type of summary info to display. See the
			     SUMMARY_xxxxxx defines.                         */

  /**** Set Variable Defaults ****/ 
  summary = SUMMARY_NORMAL;
  showheader = DEFAULT_HEADER;
  basepde = NULL;
  totalcnt = 0;
  linesout = 10;
  cmdline = 0;
  showall = 1;            /* The default line "weight" is 1. When we print a
			     line, we subtract this weight from the lines
			     we have to print. If we want to print them all
			     (fusage -a) then we will set this to 0.         */

  /**** Process Command Line Options ****/
  if(argc > 1)  
  {
    i = 1;
    while(i < argc)
    {
      if(isanumber(argv[i]))
      {
	linesout = atoi(argv[i]);
	if(linesout < 0)
	  linesout *= -1;
      }
      else
      {
	if(0 == strcmp(argv[i], "-v"))
	{
	  printf("Version %s\n", VERSION_STRING);
	  return(0);
	}

	if(0 == strcmp(argv[i], "-a"))
	{
	  showall = 0;
	}

	if(0 == strcmp(argv[i], "-c"))
	{
	  cmdline = 1;
	}

	if(0 == strcmp(argv[i], "-d"))
	{
	  if(showheader)
	    showheader = 0;
	  else
	    showheader = 1;
	}

	if(0 == strcmp(argv[i], "-s"))
	{
	  summary = SUMMARY_ONLY;
	}

	if(0 == strcmp(argv[i], "-S"))
	{
	  summary = SUMMARY_NONE;
	}

	if((0 == strcmp(argv[i], "-h")) |
	   (0 == strcmp(argv[i], "-H")) |
	   (0 == strcmp(argv[i], "--help")))
	{
	  printf("fusage - An open file handle counter\n");
	  printf("         Version %s\n", VERSION_STRING);
	  printf("         William Favorite <wfavorite@tablespace.net>\n");
	  printf("\n");
	  printf(" options:\n");
	  printf("  -v   Show version and exit.\n");
	  printf("  -h   Show help and exit.\n");
	  printf("  -c   Show entire command line on output.\n");
	  printf("  -s   Only show summary info.\n");
	  printf("  -S   Do not show summary info.\n");
	  if(DEFAULT_HEADER == 1)
	    printf("  -d   Disable column descriptors.\n");
	  else
	    printf("  -d   Show column descriptors.\n");
	  printf("  X   (Where X is a positive integer) Show X lines of output. Default: 10\n");
	  printf("  -a   Show all output, not just X lines.\n");


	  return(0);

	}
      } /* if(isanumber()) else */
      i++;
    } /* while(i < argc) */
  }

  /**** Start processing /proc info ****/
  if(NULL == (procd = opendir("/proc")))
  {
    fprintf(stderr, "ERROR: Unable to open /proc.\n");
    return(1);
  }

  while(NULL != (proce = readdir(procd)))
  {    
    if(isanumber(proce->d_name))
    {
      got = 0;

      sprintf(statfname, "/proc/%s/stat", proce->d_name);
      sprintf(fddirname, "/proc/%s/fd", proce->d_name);

      tmppid = 0;
      tmppid = atol(proce->d_name);

      if(tmppid > 0)
	got++;

      /*** Read the binary - process name ***/
      if(NULL != (statf = fopen(statfname, "r")))
      {
	if(NULL != (fgets(statbuf, 1024, statf)))
	{
	  i = 0;
	  j = 0;
	  /* Step through to the beginning of the binary name */
	  while(statbuf[i++] != '(');
	
	  /* Print out all the characters in the binary name */
	  while(statbuf[i] != ')')
	    tmpstr[j++] = statbuf[i++];
	  
	  tmpstr[j++] = 0;
	}

	if(j > 1)
	  got++;

	fclose(statf);
      }

      /*** Count the number of open files ***/
      if(NULL != (fdd = opendir(fddirname)))
      {
	cnt = 0;
	while(NULL != (fde = readdir(fdd)))
	{
	  cnt++;
	  totalcnt++;
	}

	got++;

	closedir(fdd);
      }

      if(got == 3)
      {
	thispde = (struct procfdc *)malloc(sizeof(struct procfdc));
	
	thispde->pid = tmppid;
	strncpy(thispde->name, tmpstr, 256);
	thispde->fdcnt = cnt;
	thispde->next = NULL;

	
	if(basepde == NULL)
	{
	  /* ONLY */
	  basepde = thispde;
	}
	else
	{
	  if(thispde->fdcnt >= basepde->fdcnt)
	  {
	    /* FIRST */
	    thispde->next = basepde;
	    basepde = thispde;

	  }
	  else
	  {
	    sortpde = basepde;
	    lastpde = NULL;

	    while(thispde->fdcnt < sortpde->fdcnt)
	    {
	      if(sortpde->next == NULL)
		break;

	      lastpde = sortpde;
	      sortpde = sortpde->next;
	    }

	    if(lastpde == NULL)
	      basepde = thispde;
	    else
	      lastpde->next = thispde;
	      
	    thispde->next = sortpde;
	    //sortpde->next = thispde;
	  }
	}
	//thispde->next = basepde;
	//basepde = thispde;
      }

    } /* if(isanumber()) */
  } /* while(readdir()) */
  closedir(procd);

  /**** Display Information ****/
  if(summary != SUMMARY_ONLY)
  {
    if((basepde) && (showheader))
    {
      printf("PID      Process Name      File Count\n");
    }

    thispde = basepde;
    cnt = 0;
    while((thispde != NULL)&(cnt < linesout))
    {
#ifdef TRUNCATE_NAMES
      thispde->name[13] = 0;
#endif
      printf("%-8lu %-17s %-4d", thispde->pid, thispde->name, thispde->fdcnt);
      if(cmdline)
	PrintCmdLine(thispde->pid);
      printf("\n");

      thispde = thispde->next;
      cnt += showall;
    }
  }

  if(summary)
  {
#ifdef SUM_TOTAL
    printf("Total files open: %lu\n", totalcnt);
#endif
    PrintSysFileStat();
  }

  return(0);
}

/* ======================================================================== */
void PrintSysFileStat(void)
{
  FILE *filenr;
  
  unsigned long sysopenf = 0;
  unsigned long sysmaxf = 0;
  unsigned long sysallocf = 0;
  unsigned long sysfreef = 0;

  if(NULL == (filenr = fopen("/proc/sys/fs/file-nr", "r")))
    return;

  if(3 == fscanf(filenr, "%lu %lu %lu", &sysallocf, &sysfreef, &sysmaxf))
  {
    fclose(filenr);

    sysopenf = sysallocf - sysfreef;
    
    printf("Kernel Open: %-8lu Max: %-8lu Alloc: %-8lu Free: %-8lu\n",
	   sysopenf,
	   sysmaxf,
	   sysallocf,
	   sysfreef);
  }
}

/* ======================================================================== */
void PrintCmdLine(unsigned long pid)
{
  FILE *f;
  int i = 0;

  sprintf(file, "/proc/%lu/cmdline", pid);

  if(NULL == (f = fopen(file, "r")))
    return;

#ifdef UNDEFINED
  /* This tends to runcate output. */
  if(fgets(file, 1024, f))
    printf(" %s", file);
#else
  /* This will not truncate output in the shell */
  while(EOF != (i = fgetc(f)))
    putchar(i);
#endif

  fclose(f);
}