/*
 * General DB routines
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * Copyright © 1993 AmiTCP/IP Group, <AmiTCP-group@hut.fi>
 *                  Helsinki University of Technology, Finland.
 *
 * Created      : Fri Jan 14 20:46:20 1994 ppessi
 * Last modified: Thu May 19 04:09:10 1994 ppessi
 */

#include "entries.h"
#include <string.h>
#include <assert.h>

static struct NetInfoPointer *FindPointer(struct List *list, struct Ent*to);
void GetByNameCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);
void GetByIDCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);
void ResetCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);
void ReadCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);
void WriteCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);
void UpdateCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);
void MembersCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim);

const static DeviceCmd_t map_cmds[NI_END] =
{
  /* Standard commands */
  UnknownCommand,		/* CMD_INVALID */
  ResetCmd,			/* CMD_RESET */
  ReadCmd,			/* CMD_READ */
  WriteCmd,			/* CMD_WRITE */
  UpdateCmd,			/* CMD_UPDATE */
  UnknownCommand,		/* CMD_CLEAR */
  UnknownCommand,		/* CMD_STOP */
  UnknownCommand,		/* CMD_START */
  UnknownCommand,		/* CMD_FLUSH */
  /* NetInfo commands */
  GetByIDCmd,			/* NI_GETBYID */
  GetByNameCmd,			/* NI_GETBYNAME */
  MembersCmd,			/* NI_MEMBERS */
};

static const init_map_func_t initMapFuncs[NETINFO_UNITS] = 
{
  InitPasswdMap,
  InitGroupMap,
};

struct NetInfoMap *InitNetInfoMap(BASE, struct MsgPort * mp, ULONG mapno)
{
  struct NetInfoMap *nim;
  assert(mapno < 2);

  if (nim = initMapFuncs[mapno](nid)) {
    nim->nim_Port = mp;
    nim->nim_Commands = map_cmds;
  
    InitSemaphore(nim->nim_ReqLock);
    InitList(nim->nim_Rx);
    InitList(nim->nim_Wx);

    InitSemaphore(nim->nim_EntLock);
    InitList(nim->nim_Ent);
  
    InitSemaphore(nim->nim_PointerLock);
    InitList(nim->nim_Pointer);
  }
  return nim;
}

void DeInitNetInfoMap(BASE, struct NetInfoMap *nim)
{
  if (Method(cleanup, nim) != NULL) 
    Method(cleanup, nim)(nid, nim);
  
  FreeVec(nim);
}

/*
 * NI_GETBYNAME, search an entry by name
 */
void GetByNameCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  struct Ent *e = InternalSetEnts(nid, nim);
  const UBYTE *name = (const UBYTE *)
    ((struct NetInfoEnt*)req->io_Data)->nie_name;
  LONG retval = NIERR_NOTFOUND;

  while (e = GetNextEnt(e)) {
    if (strcmp(e->e_name, name) == 0) {
      if (Method(copy_out, nim)(req, e)) {
	retval = 0;
      } else {
	retval = NIERR_TOOSMALL;
      }
      break;
    }
  }
  
  req->io_Error = retval;

  InternalEndEnts(nid, nim);
  TermIO(req);
}

/*
 * NI_GETBYID, Search an entry by ID
 */
void GetByIDCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  struct Ent *e = InternalSetEnts(nid, nim);
  LONG id = ((struct NetInfoEnt*)req->io_Data)->nie_id;
  BYTE retval = NIERR_NOTFOUND;

  while (e = GetNextEnt(e)) {
    if (e->e_id == id) {
      if (Method(copy_out, nim)(req, e)) {
	retval = 0;
      } else {
	retval = NIERR_TOOSMALL;
      }
      break;
    }
  }

  req->io_Error = retval;

  InternalEndEnts(nid, nim);
  TermIO(req);
}

/*
 * CMD_RESET, start reading the database in sequential order
 */
void ResetCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  struct NetInfoPointer *p = (struct NetInfoPointer *)req->io_Unit;

  p->nip_Ent = (void *)InternalSetEnts(nid, nim);
  InternalEndEnts(nid, nim);
  req->io_Error = 0;
  TermIO(req);
}

/*
 * CMD_READ, get next entry  
 */
void ReadCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  struct NetInfoPointer *p = (struct NetInfoPointer *)req->io_Unit;
  struct Ent *e;
  BYTE retval;

  InternalSetEnts(nid, nim);
  e = p->nip_Ent;
  if (p->nip_Ent && (p->nip_Ent = GetNextEnt(e))) {
    if (Method(copy_out, nim)(req, p->nip_Ent)) {
      retval = 0;
    } else {
      retval = NIERR_TOOSMALL;
    }
  } else {
    retval = NIERR_NOTFOUND;
  }
  InternalEndEnts(nid, nim);

  req->io_Error = retval;
  TermIO(req);
}

/*
 * CMD_WRITE
 */
void WriteCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  struct Ent *e, *new_e;
  const UBYTE *name = ((struct NetInfoPasswd *)req->io_Data)->pw_name;

  if (new_e = Method(copy_in, nim)(nid, req)) {
    /* Exclusive lock for writing */
    DbMapLock(nim);

    e = InternalSetEnts(nid, nim);

    while (e = GetNextEnt(e)) {
      if (strcmp(e->e_name, name) == 0) {
	/* A match was found - add new */ 
	Insert(nim->nim_Ent, new_e, e);

	/* Remove old */
	Remove((struct Node *)e);

	/* Update pointers */
	ObtainSemaphore(nim->nim_PointerLock);
	{
	  struct NetInfoPointer *nip;
	  if (nip = FindPointer(nim->nim_Pointer, e)) {
	    nip->nip_Ent = (void *)new_e;
	  }
	}
	ReleaseSemaphore(nim->nim_PointerLock);

	/* Free old */
	FreeVec(e);

	new_e = NULL;
	break;
      }
    }
    
    /*
     * A new entry?
     */
    if (new_e != NULL) {
      AddTail(nim->nim_Ent, new_e);
    }

    req->io_Error = 0;
    InternalEndEnts(nid, nim);
  
    nim->nim_Flags |= NIMF_CHANGED;
    DbMapUnlock(nim);
  } else {
    /* copy_in method will set the io_Error */
  }

  TermIO(req);
}

/*
 * CMD_UPDATE
 */
void UpdateCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  BYTE retval = 0;

  if ((nim->nim_Flags & NIMF_CHANGED) != 0) {
    UBYTE newname[128], oldname[128];
    BPTR newfile;
    DbMapLock(nim);
    if ((nim->nim_Flags & NIMF_CHANGED) == 0)
      goto exit;

    newname[sizeof(newname)-1] = '\0';
    strncpy(newname, nim->nim_Filename, sizeof(newname)-1);
    strncat(newname, ".new", sizeof(newname)-1);

    oldname[sizeof(oldname)-1] = '\0';
    strncpy(oldname, nim->nim_Filename, sizeof(oldname)-1);
    strncat(oldname, ".old", sizeof(oldname)-1);

    if (newfile = Open(newname, MODE_NEWFILE)) {
      struct Node *entry, *next;

      retval = 0;

      for (entry = nim->nim_Ent->lh_Head;
	   next = entry->ln_Succ;
	   entry = next) {
	retval = Method(print_out, nim)(nid, newfile, (struct Ent *)entry);
	if (retval) 
	  break;
      }
      Close(newfile);

      if (retval == 0) {
	/* Now we are supposed to move newfile to file */
	DeleteFile(oldname);
	Rename(nim->nim_Filename, oldname);
	if (Rename(newname, nim->nim_Filename))
	  nim->nim_Flags &= NIMF_CHANGED;
	else
	  retval = NIERR_ACCESS;
      }
    } else {
      retval = NIERR_ACCESS;
    }
  exit:
    DbMapUnlock(nim);
  }

  req->io_Error = retval;
  TermIO(req);
}

/*
 * NI_MEMBERS
 */
void MembersCmd(BASE, struct NetInfoReq *req, struct NetInfoMap *nim)
{
  if (Method(membercmd, nim) != NULL) {
    Method(membercmd, nim)(nid, req, nim);
  } else {
    req->io_Error = IOERR_NOCMD;
    TermIO(req);
  }
}

/*
 * Find a pointer to this entry
 */
static struct NetInfoPointer *FindPointer(struct List *list, struct Ent *to)
{
  struct NetInfoPointer *entry, *next;

  for (entry = (struct NetInfoPointer *)list->lh_Head;
       next = (struct NetInfoPointer *)entry->nip_Node->ln_Succ;
       entry = next) 
    if (entry->nip_Ent == to) {
      return entry;
    }

  return NULL;
}

/*
 * Search for map
 */
struct NetInfoMap *CheckUnit(BASE, struct Unit *u) 
{
  struct NetInfoPointer *nip = (struct NetInfoPointer *)u;

  if (nip != NULL)
    return nip->nip_Map;
  else
    return NULL;
}


/*
 * Parse group database if needed
 */
struct Ent *InternalSetEnts(BASE, struct NetInfoMap *nim)
{
  DbMapLockShared(nim);

  if ((nim->nim_Flags & NIMF_PARSED) == 0) {
    int was_too_long = 0;
    UBYTE *p, *line = NULL;
    BPTR fh = NULL;
    struct Ent *ent;

    /* We can not get exclusive lock if we have shared lock */
    DbMapUnlock(nim);
    /* Exclusive lock for writing */
    DbMapLock(nim);
    if ((nim->nim_Flags & NIMF_PARSED) != 0)
      goto error;

    line = AllocVec(MAXLINELENGTH + 1, MEMF_PUBLIC); 
    fh = Open((STRPTR)nim->nim_Filename, MODE_OLDFILE);

    if (!line || !fh)
      goto error;
    
    /* Free old entries */
    FreeListVec(nid, nim->nim_Ent);

    /* Invalidate pointers to old database */
    ObtainSemaphore(nim->nim_PointerLock);
    {
      struct NetInfoPointer *nip, *next;

      for (nip = (struct NetInfoPointer *)nim->nim_Pointer->lh_Head; 
	   next = (struct NetInfoPointer *)nip->nip_Node->ln_Succ; 
	   nip = next) {
	nip->nip_Ent = NULL;
      }
    }
    ReleaseSemaphore(nim->nim_PointerLock);

    line[MAXLINELENGTH] = '\0';
    
    while (p = FGets(fh, line, MAXLINELENGTH)) {
      while (*p && *p != '\n')
	p++;
      if (!*p) {
	/* Line is too long */
	was_too_long = 1;
	continue;
      }
      if (was_too_long) {
	/* Rest of a line */
	was_too_long = 0;
	continue;
      }
      if (ent = Method(parse_ent, nim)(nid, line))
	AddTail(nim->nim_Ent, (struct Node *)ent);
    }
    nim->nim_Flags |= NIMF_PARSED;

    /* Ask for notifies */ 
    if (nim->nim_Methods->notify != NULL &&
	nim->nim_Notify->nr_UserData == NULL) {
      memset(nim->nim_Notify, 0, sizeof *nim->nim_Notify);
      nim->nim_Notify->nr_Name = (STRPTR)nim->nim_Filename;
      nim->nim_Notify->nr_Flags = NRF_SEND_MESSAGE;
      nim->nim_Notify->nr_stuff.nr_Msg.nr_Port = nid->nid_NotifyPort;
      nim->nim_Notify->nr_UserData = (ULONG) nim;
      StartNotify(nim->nim_Notify);
    }

  error:
    if (line) FreeVec(line);
    if (fh) Close(fh);

    /* Downgrade to a shared lock */ 
    DbMapUnlock(nim);
    DbMapLockShared(nim);
  }

  return (struct Ent *)nim->nim_Ent;
}

void InternalEndEnts(BASE, struct NetInfoMap *nim)
{
  DbMapUnlock(nim);
}

/*
 * Return next entry
 */
struct Ent *GetNextEnt(struct Ent *e)
{
  e = (struct Ent *)e->e_node.ln_Succ;

  if (e->e_node.ln_Succ)
    return e;
  else
    return NULL;
}

/*
 * Mark as unparsed (when map has received DOS notify)
 */
void EntHandleNotify(BASE, struct NetInfoMap *nim)
{
  DbMapLockShared(nim);
  nim->nim_Flags &= ~NIMF_PARSED;
  DbMapUnlock(nim);
}

/*
 * Free all entries
 */
void EntCleanup(BASE, struct NetInfoMap *nim)
{
  DbMapLock(nim);
  FreeListVec(nid, nim->nim_Ent);
  /* End notifies */ 
  if (nim->nim_Notify->nr_UserData != NULL) {
    EndNotify(nim->nim_Notify);
    nim->nim_Notify->nr_UserData = NULL;
  }
  DbMapUnlock(nim);
}

