/******************************************************************************
  quotal.c -- els quotal functions
  
  Author: Mark Baranowski
  Email:  requestXXX@els-software.org (remove XXX)
  Download: http://els-software.org

  Last significant change: May 20, 2008
  
  These functions are provided "as is" in the hopes that they might serve some
  higher purpose.  If you want these functions to serve some lower purpose,
  then that too is all right.  So as to keep these hopes alive you may
  freely distribute these functions so long as this header remains intact.
  You may also freely distribute modified versions of these functions so long
  as you indicate that such versions are modified and so long as you
  provide access to the unmodified original copy.
  
  Note: The most recent version of these functions may be obtained from
        http://els-software.org

  ****************************************************************************/


#include "sysdefs.h"

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>	/* sys/stat.h needed by els.h */
#include <pwd.h>
#include <grp.h>
#include <string.h>	/* string.h needed by memset */

#include "defs.h"
#include "auxil.h"
#include "els.h"
#include "elsMisc.h"
#include "sysdep.h"
#include "quotal.h"


typedef struct Quotal_Item
{
  Ulong nfiles;
  Ulong nblocks;
} Quotal_Item;

typedef struct Quotal_List
{
  Ulong id;
  Quotal_Item item;
  struct Quotal_List *next;
} Quotal_List;

typedef struct Quotal_Hash
{
  Ulong id;
  struct Quotal_List *ptr;
} Quotal_Hash;


#define NFS2_ID_LIMIT  0x10000
Local Quotal_Item *grandQuotal_uid = NULL;
Local Quotal_Item *grandQuotal_gid = NULL;
Local Quotal_Item *subQuotal_uid = NULL;
Local Quotal_Item *subQuotal_gid = NULL;
Local char *UNASSIGNED = "UNASSIGNED";

/* BIG_UID_HASH_SIZE must be a power of 2: */
#define BIG_UID_HASH_SIZE  0x8000
/* BIG_GID_HASH_SIZE must be a power of 2: */
#define BIG_GID_HASH_SIZE  0x4000
Local Quotal_Hash *bigUidHashTable = NULL;
Local Quotal_Hash *bigGidHashTable = NULL;
Local Quotal_List *bigUidList = NULL;
Local Quotal_List *bigGidList = NULL;


Local Quotal_List *searchIdList(char type, Ulong id, Ulong id_hash,
				Quotal_Hash *id_hash_table,
				Quotal_List **id_list_ptr)
{
  if ((Debug&16) && id_hash_table[id_hash].id != 0)
    printf("Hash %cid ?: %7ld == %7ld\n",
	   type, id_hash_table[id_hash].id, id);

  if (id_hash_table[id_hash].id == id)
  {
    /* Hash-hit -- Return hashed element: */
    if (Debug&16) printf("Hashed %cid: %7ld\n", type, id);
    return(id_hash_table[id_hash].ptr);
  }
  else if (*id_list_ptr == NULL)
  {
    /* Start of new-list -- Create and return first element: */
    Quotal_List *ptr;
    if (Debug&16) printf("START %cid:  %7ld\n", type, id);
    *id_list_ptr = memAllocZero(sizeof(Quotal_List));
    ptr = *id_list_ptr;
    ptr->id = id;
    ptr->next = NULL;
    return(ptr);
  }
  else
  {
    /* Search list for element and create new element if necessary: */
    Quotal_List *ptr = *id_list_ptr;
    Quotal_List *prevptr = NULL;
    Quotal_List *newptr;
    while (ptr != NULL && ptr->id < id)
    {
      prevptr = ptr;
      ptr = ptr->next;
    }
    if (ptr != NULL && ptr->id == id)
    {
      /* Found existing element: */
      if (Debug&16) printf("Found %cid:  %7ld\n", type, id);
      id_hash_table[id_hash].id = id;
      id_hash_table[id_hash].ptr = ptr;
      return(ptr);
    }
    newptr = memAllocZero(sizeof(Quotal_List));
    newptr->id = id;
    if (prevptr == NULL)
    {
      /* Prepend new element to front of list: */
      if (Debug&16) printf("Prepend %cid:%7ld\n", type, id);
      newptr->next = ptr;
      *id_list_ptr = newptr;
    }
    else if (ptr == NULL)
    {
      /* Append new element to end of list: */
      if (Debug&16) printf("Append %cid: %7ld\n", type, id);
      prevptr->next = newptr;
      newptr->next = NULL;
    }
    else
    {
      /* Insert new element inside list: */
      if (Debug&16) printf("Insert %cid: %7ld\n", type, id);
      prevptr->next = newptr;
      newptr->next = ptr;
    }
    id_hash_table[id_hash].id = id;
    id_hash_table[id_hash].ptr = newptr;
    return(newptr);
  }
}


Local Quotal_List *searchUidList(Ulong uid)
{
  Ulong uid_hash = uid & (BIG_UID_HASH_SIZE - 1);
  if (bigUidHashTable == NULL)
  {
    bigUidHashTable = memAllocZero(sizeof(Quotal_Hash) * BIG_UID_HASH_SIZE);
    bigUidHashTable[0].id = 1;  /* Element zero must be non-zero */
  }
  return(searchIdList('U', uid, uid_hash, bigUidHashTable, &bigUidList));
}


Local Quotal_List *searchGidList(Ulong gid)
{
  Ulong gid_hash = gid & (BIG_GID_HASH_SIZE - 1);
  if (bigGidHashTable == NULL)
  {
    bigGidHashTable = memAllocZero(sizeof(Quotal_Hash) * BIG_GID_HASH_SIZE);
    bigGidHashTable[0].id = 1;  /* Element zero must be non-zero */
  }
  return(searchIdList('G', gid, gid_hash, bigGidHashTable, &bigGidList));
}


void Quotal(Dir_Item *file)
{
  Ulong nblocks;
  Ulong uid = uid2Ulong(file->info.st_uid);
  Ulong gid = gid2Ulong(file->info.st_gid);

  if (grandQuotal_uid == NULL)
  {
    int mem_size = sizeof(Quotal_Item) * NFS2_ID_LIMIT;
    grandQuotal_uid = memAllocZero(mem_size);
    grandQuotal_gid = memAllocZero(mem_size);
    subQuotal_uid = memAllocZero(mem_size);
    subQuotal_gid = memAllocZero(mem_size);
  }

  nblocks = getStatBlocks(&file->info);

  if (uid < NFS2_ID_LIMIT)
  {
    grandQuotal_uid[uid].nfiles++;
    grandQuotal_uid[uid].nblocks += nblocks;
    subQuotal_uid[uid].nfiles++;
    subQuotal_uid[uid].nblocks += nblocks;
  }
  else
  {
    Quotal_List *bigUid = searchUidList(uid);
    bigUid->item.nfiles++;
    bigUid->item.nblocks += nblocks;
    /* FIXME: subQuotals for bigUids not necessary at this time */
  }

  if (gid < NFS2_ID_LIMIT)
  {
    grandQuotal_gid[gid].nfiles++;
    grandQuotal_gid[gid].nblocks += nblocks;
    subQuotal_gid[gid].nfiles++;
    subQuotal_gid[gid].nblocks += nblocks;
  }
  else
  {
    Quotal_List *bigGid = searchGidList(gid);
    bigGid->item.nfiles++;
    bigGid->item.nblocks += nblocks;
    /* FIXME: subQuotals for bigGids not necessary at this time */
  }

  return;
}


char *quotalUid(Ulong i)
{
  static char Uid[MAX_USER_GROUP_NAME + 16 + 2];
  char num[16];
  char *uid = uid2name(i);
  if (uid == NULL) uid = UNASSIGNED;
  sprintf(num, "/%lu", i);
  sprintf(Uid, "%10s%-6s", uid, num);
  return(Uid);
}


char *quotalGid(Ulong i)
{
  static char Gid[MAX_USER_GROUP_NAME + 16 + 2];
  char num[16];
  char *gid = gid2name(i);
  if (gid == NULL) gid = UNASSIGNED;
  sprintf(num, "/%lu", i);
  sprintf(Gid, "%10s%-6s", gid, num);
  return(Gid);
}


void grandQuotal_print(void)
{
  int i;
  Quotal_List *ptr;
  Ulong grandQuotal_nbytes = 0;
  Ulong grandQuotal_nfiles = 0;
  char *format;

  /* NB: grandQuotal_uid might be NULL if no files were listed: */
  if (grandQuotal_uid == NULL) return;

  printf("\n");
  /*      xID: 1234567890/1234567 1234567890 1234567890 */
  printf("      User Name/Number      KBytes      Files\n"); 
  format = "%3s: %-18s %10lu %10lu\n";

  for (i = 0; i < NFS2_ID_LIMIT; i++)
  {
    Quotal_Item *q = &grandQuotal_uid[i];
    if (q->nfiles > 0)
    {
      Ulong n = q->nblocks;
      n = n / 2;	/* Convert quotals to KBytes */
      printf(format, "UID", quotalUid(i), n, q->nfiles);
      grandQuotal_nbytes += n;
      grandQuotal_nfiles += q->nfiles;
    }
  }
  for (ptr = bigUidList; ptr != NULL; ptr = ptr->next)
  {
    Quotal_Item *q = &ptr->item;
    Ulong n = q->nblocks;
    n = n / 2;	/* Convert quotals to KBytes */
    printf(format, "UID", quotalUid(ptr->id), n, q->nfiles);
    grandQuotal_nbytes += n;
    grandQuotal_nfiles += q->nfiles;
  }
  
  printf("\n");
  /*      xID: 1234567890/1234567 1234567890 1234567890 */
  printf("     Group Name/Number      KBytes      Files\n"); 

  for (i = 0; i < NFS2_ID_LIMIT; i++)
  {
    Quotal_Item *q = &grandQuotal_gid[i];
    if (q->nfiles > 0)
    {
      Ulong n = q->nblocks;
      n = n / 2;	/* Convert quotals to KBytes */
      printf(format, "GID", quotalGid(i), n, q->nfiles);
    }
  }
  for (ptr = bigGidList; ptr != NULL; ptr = ptr->next)
  {
    Quotal_Item *q = &ptr->item;
    Ulong n = q->nblocks;
    n = n / 2;	/* Convert quotals to KBytes */
    printf(format, "GID", quotalGid(ptr->id), n, q->nfiles);
  }

  /*      xID: 1234567890/1234567 1234567890 1234567890 */
  printf("                        ---------- ----------\n");
  printf("TOTAL:                  %10lu %10lu\n",
	 grandQuotal_nbytes, grandQuotal_nfiles);
  printf("\n");

  return;
}


/* FIXME: Are subQuotals ever going to be of any use? */
void subQuotal_print(void)
{
#ifdef SUBQUOTAL
  int i;
  int mem_size = sizeof(Quotal_Item) * NFS2_ID_LIMIT;

  if (subQuotal_uid != NULL)
  {
    printf("UID:\n"); 
    for (i = 0; i < NFS2_ID_LIMIT; i++)
    {
      Quotal_Item *q = &subQuotal_uid[i];
      if (q->nfiles > 0)
      {
	Ulong n = q->nblocks;
	n = n / 2;	/* Convert quotals to KBytes */
	printf("%s: %10lu %10lu\n", quotalUid(i), n, q->nfiles);
      }
    }
    memset(subQuotal_uid, 0, mem_size);
  }
  /* FIXME: subQuotals for bigUids not implemented */
  
  if (subQuotal_gid != NULL)
  {
    printf("GID:\n");
    for (i = 0; i < NFS2_ID_LIMIT; i++)
    {
      Quotal_Item *q = &subQuotal_gid[i];
      if (q->nfiles > 0)
      {
	Ulong n = q->nblocks;
	n = n / 2;	/* Convert quotals to KBytes */
	printf("%s: %10lu %10lu\n", quotalGid(i), n, q->nfiles);
      }
    }
    memset(subQuotal_gid, 0, mem_size);
  }
  /* FIXME: subQuotals for bigGids not implemented */

#endif
  return;
}


