/***************************************************************************
*       NAME:  PROFILE1.C $Revision: 1.6 $
**      COPYRIGHT:
**      "Copyright (c) 1994,1995 by e-Tek Labs"
**
**       "This software is furnished under a license and may be used,
**       copied, or disclosed only in accordance with the terms of such
**       license and with the inclusion of the above copyright notice.
**       This software or any other copies thereof may not be provided or
**       otherwise made available to any other person. No title to and
**       ownership of the software is hereby transfered."
****************************************************************************
* $Log: profile1.c $
* Revision 1.6  1995/08/22 17:16:15  mleibow
* Revision 1.5  1995/05/25 18:17:15  mleibow
* Revision 1.4  1995/05/25 16:27:59  mleibow
* Revision 1.3  1995/05/25 15:15:31  mleibow
* Added far ptrs for list handling.
* Added support for stdio or local file functions.
* Added support for malloc/free or local memory functions.
* Revision 1.2  1995/05/15 22:49:41  sdsmith
* Removed standard library dependencies
* Revision 1.1  1995/02/23 11:07:53  unknown
* Initial revision
***************************************************************************/
/***************************************************************************
OVERVIEW:
profile1.c - profile utilities

This file contains utility routines for use by wrappers or applications.
The utilities are for writing data to a .ini file in the standard
Microsoft Windows format:

[section1]
entry1=value1
entry2=value2

[section2]
.
.
.

These routines make free use of the standard C runtime library.  For that
reason they should probably be avoided when writing drivers for DOS.

If this routine is being compiled specifically for Microsoft Windows,
most of the code is preprocessed out since the actual Microsoft 
routines will be used.
#include "config.h"
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#if defined(_MSC_VER)
#include <malloc.h>
#define farmalloc(s) (void far *)_halloc(s,1)
#define farfree _hfree
#elif defined(__BORLANDC__)
#include <alloc.h>
#elif defined(__WATCOMC__) && defined(__FLAT__)
#include <malloc.h>
#define farmalloc malloc
#define farfree free
#endif
#ifdef _WINDOWS
#include <windows.h>
#endif

#include "iw.h"

#ifndef _WINDOWS
#include "iwllist.h"
#include "profile.h"
#include "os.h"
#include "config.h"


#define MEM_SIZE  (48U << 10)
#define EMPTY_STATUS 0
#define STAT_USED 1

#ifndef USE_MALLOC
#define MEM_HEADER_SIZE sizeof(struct mem_block)

struct mem_block {
	unsigned short size;
	unsigned short status;
	struct mem_block *prev_block;
	struct mem_block *next_block;
};
#endif

static char init = 0;
static char lFilename[FILENAME_MAX];
static char output_line[256];
extern void skip_whitespace(char RFAR *cp, char RFAR *RFAR*pcp);
unsigned short mem_seg;
static char RFAR *memory = 0;

#ifdef USE_MALLOC
#if _MSC_VER == 600
#	define pmalloc(x) halloc(x, 1)
#elif _MSC_VER > 600 
#	define pmalloc(x) _halloc(x, 1)
#elif defined(__BORLANDC__)
#	define pmalloc(x) farmalloc(x)
#else
#	define pmalloc(x) malloc(x)
#endif
#if _MSC_VER == 600
#	define pfree hfree
#elif _MSC_VER > 600
#	define pfree _hfree
#elif defined(__BORLANDC__)
#	define pfree farfree
#else
#	define pfree free
#endif
#else
static void RFAR *pmalloc(unsigned short x)
{
	int i;
	struct mem_block RFAR *m, RFAR *p, RFAR *n, RFAR *t;

	/* add room for header */
	x += sizeof(struct mem_block);
	m = (struct mem_block RFAR *)memory;
	while (FP_OFF(m)) {
	    if (!(m->status & STAT_USED) && m->size >= x) {
		/* allocate block */
		m->status |= STAT_USED;
		if (m->size - x <= 64) {
		    return((char RFAR *)MK_FP(FP_SEG(m),FP_OFF(m)+MEM_HEADER_SIZE));
		} else {
		    n = (struct mem_block RFAR *)((char RFAR *)m + x);
		    n->next_block = m->next_block;
		    m->next_block = (struct mem_block *)FP_OFF(n);
		    n->size = (m->size - x);
		    m->size = x;
		    n->prev_block = (struct mem_block *)FP_OFF(m);
		    n->status = EMPTY_STATUS;
		    if (n->next_block) {
			t = MK_FP(FP_SEG(memory), FP_OFF(n->next_block));
			t->prev_block = (struct mem_block *)FP_OFF(n);
		    }
		    return((char RFAR *)MK_FP(FP_SEG(m),FP_OFF(m)+MEM_HEADER_SIZE));
		}
	    } else {
		m = MK_FP(FP_SEG(memory), FP_OFF(m->next_block));
	    }
	}
	return(0);
}

void pfree(void RFAR *x)
{
	int i;
	struct mem_block RFAR *m, RFAR *p, RFAR *n, RFAR *t;

	/* move address back by size of header */
	m = (struct mem_block RFAR *)MK_FP(FP_SEG(memory), FP_OFF(x) - MEM_HEADER_SIZE);
	if (m->prev_block != 0) {
	    p = MK_FP(FP_SEG(memory), FP_OFF(m->prev_block));
	    if (!(p->status & STAT_USED)) {
		/* previous block is free - concatenate them */
		p->size += m->size;
		p->next_block = m->next_block;
		if (p->next_block != 0) {
		    t = MK_FP(FP_SEG(memory), FP_OFF(p->next_block));
		    t->prev_block = (struct mem_block *)FP_OFF(p);
		}
		m = p;
	    }
	}
	if (m->next_block != 0) {
	    n = MK_FP(FP_SEG(memory), FP_OFF(m->next_block));
	    if (!(n->status & STAT_USED)) {
		/* next block is free - concatenate them */
		m->size += n->size;
		m->next_block = n->next_block;
		/* now adjust the next->next block's prev pointer */
		if (m->next_block != 0) {
		    t = MK_FP(FP_SEG(memory), FP_OFF(m->next_block));
		    t->prev_block = (struct mem_block *)FP_OFF(m);
		}
	    }
	}
	m->status &= ~STAT_USED;
}
#endif

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

FUNCTION DEFINITION:
unload_profile - remove a cached profile from memory

DESCRIPTION:
unload_profile pfrees all memory used by the cached profile specified
by Filename.  If Filename is NULL, all profiles are removed.

RETURNS: void
*/
static void unload_profile(
  char RFAR *Filename) /* file name of profile to remove from memory */
{
  profile_header RFAR  *profile, RFAR *next_profile;
  profile_section RFAR *section, RFAR *next_section;
  profile_entry RFAR *entry, RFAR *next_entry;

  profile = (profile_header RFAR  *)profiles.head;
  if (Filename) {
    while (profile) {
      if (iwu_stricmp(profile->filename, Filename) == 0) break;
      profile = (profile_header RFAR  *)profile->node.next;
    }
  }

  while (profile) {
    next_profile = (profile_header RFAR  *)profile->node.next;
    section = (profile_section RFAR *)profile->sections.head;
    while (section) {
      next_section = (profile_section RFAR *)section->node.next;
      if (section->valid) {
	entry = (profile_entry RFAR *)section->entries.head;
	while (entry) {
	  next_entry = (profile_entry RFAR *)entry->node.next;
	  if (entry->valid) {
	    pfree(entry->value);
	  }
	  pfree(entry->name);
	  iwl_DeleteNode((struct iwl_list RFAR *)&section->entries, (struct iwl_node RFAR *)entry);
	  pfree(entry);
	  entry = next_entry;
	}
      }
      pfree(section->name);
      iwl_DeleteNode(&profile->sections, (struct iwl_node RFAR *)section);
      pfree(section);
      section = next_section;
    }
    pfree(profile->filename);
    iwl_DeleteNode(&profiles, (struct iwl_node RFAR *)profile);
    pfree(profile);
    profile = next_profile;
    if (Filename) break;
  }
}

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

FUNCTION DEFINITION:
load_profile - cache a profile in memory

DESCRIPTION:
load_profile build a structure in memory with the data in a .ini file
specified by Filename.  The .ini file is expected to be in the Microsoft
Windows .ini file format.  

RETURNS: int - 0 if successful; nonzero otherwise
*/
static int load_profile(
  char RFAR *Filename) /* file name of profile to cache */
{
#ifdef USE_STDIO
  FILE *fp;
#else
  int fp;
#endif
  unsigned int i,j;
  int output_lineok;
  int error = 0;
  profile_header RFAR  *new_profile;
  profile_section RFAR *new_section=0;
  profile_entry RFAR   *new_entry;
  char RFAR *val, RFAR *ol;

#ifdef USE_STDIO
  _fstrncpy(lFilename, Filename, FILENAME_MAX);
  lFilename[FILENAME_MAX - 1] = '\0';
  fp = fopen((const char *)lFilename,(const char *)"r");
#else
  fp = os_file_open(Filename);
  if (fp < 0) fp = 0;
#endif
  if (fp) {
#ifdef USE_STDIO
    fseek(fp, 0, SEEK_SET);
#else
    os_file_seek(fp, 0, DOS_SEEK_START);
#endif
    new_profile = (profile_header RFAR  *)pmalloc(sizeof(profile_header));
    if (new_profile) {
      new_profile->filename = NULL;
      iwl_InitList(&new_profile->sections);
      iwl_AddNode(&profiles, (struct iwl_node RFAR *)new_profile, 0, IWL_PREPEND);
      new_profile->filename = (char RFAR *)pmalloc(iwu_strlen(Filename) + 1);
      if (new_profile->filename) {
	iwu_strcpy(new_profile->filename, Filename);
	while (!error) {
#ifdef USE_STDIO
	  if (fgets((char *)output_line, 255, fp)) {
#else
	  if (os_file_gets(fp, (char RFAR *)output_line, 255)) {
#endif
	    skip_whitespace(output_line, &ol);
	    if (!new_section || *ol == '[') {
	      new_section = (profile_section RFAR *)pmalloc(sizeof(profile_section));
	      if (new_section) {
		new_section->valid = ol[0] == '[';
		if (new_section->valid) {
		  val = &ol[1];
		  for (i=0;i<iwu_strlen(val);i++)
		    if (val[i] == ']') val[i]='\0';
		} else {
		  val = &ol[0];
		  for (i=0;i<iwu_strlen(val);i++)
		    if (val[i] == '\n') val[i]='\0';
		}
		new_section->name = (char RFAR *)pmalloc(iwu_strlen(val) + 1);
		if (new_section->name) {
		  iwu_strcpy(new_section->name, val);
		  iwl_InitList(&new_section->entries);
		  iwl_AddNode(&new_profile->sections, (struct iwl_node RFAR *)new_section, 0, IWL_APPEND);
		}
		else {
		  error = IW_NO_MEMORY;
		}
	      }
	      else {
		error = IW_NO_MEMORY;
	      }
	    } else {
	      if (new_section) {
		output_lineok = 0;
		new_entry = (profile_entry RFAR *)pmalloc(sizeof(profile_entry));
		if (new_entry) {
		  new_entry->name = NULL;
		  new_entry->value = NULL;
		  if (*ol != ';') {
		    for (i=0;i<iwu_strlen(ol);i++) {
		      if (ol[i] == '=') {
			output_lineok = 1;
			ol[i] = 0;
			val = &(ol[i+1]);
			for (j=0;j<iwu_strlen(val);j++)
			  if (val[j] == '\n') {
			    val[j] = 0;
			    break;
			  }
			break;
		      }
		    }
		  }
		  new_entry->name = (char RFAR *)pmalloc(iwu_strlen(ol) + 1);
		  if (new_entry->name) {
		    for (i=0;i<iwu_strlen(ol);i++)
		      if (ol[i] == '\n') ol[i] = '\0';
		    iwu_strcpy(new_entry->name, ol);
		    if (output_lineok) {
		      new_entry->valid = 1;
		      new_entry->value = (char RFAR *)pmalloc(iwu_strlen(val) + 1);
		      if (new_entry->value) {
			iwu_strcpy(new_entry->value, val);
		      }
		      else error = IW_NO_MEMORY;
		    } else {
		      new_entry->valid = 0;
		    }
		    iwl_AddNode(&new_section->entries, (struct iwl_node RFAR *)new_entry, 0, IWL_APPEND);
		  }
		  else error = IW_NO_MEMORY;
		}
		else error = IW_NO_MEMORY;
	      }
	      else error = INVALID_PROFILE;
	    }
	  }
	  else error = END_OF_FILE;
	}
      }
      else error = IW_NO_MEMORY;
    }
    else error = IW_NO_MEMORY;

#ifdef USE_STDIO
    fclose(fp);
#else
    os_file_close(fp);
#endif
  }
  else error = NO_SUCH_FILE;

  if (error && error != END_OF_FILE) {
    unload_profile(Filename);
    return(error);
  }
  else return(0);
}


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

FUNCTION DEFINITION:
write_profile_to_disk - copy a cached profile to disk

DESCRIPTION:
write_profile_to_disk copies the data in the cached profile structure
specified by profile to disk.  The filename to be used is in the 
profile structure.

RETURNS: void
*/
static int write_profile_to_disk(
  profile_header RFAR  RFAR *profile) /* cached profile structure to be written */
{
#ifdef USE_STDIO
  FILE *fd;
#else
  int fd;
#endif
  profile_section RFAR *stmp;
  profile_entry RFAR *etmp;
  int last_valid=0;

#ifdef USE_STDIO
  _fstrncpy(lFilename, profile->filename, FILENAME_MAX);
  lFilename[FILENAME_MAX - 1] = '\0';
  fd = fopen((const char *)lFilename, (const char *)"w");
#else
  fd = os_file_create(profile->filename);
#endif
  if (fd) {
    stmp = (profile_section RFAR *)profile->sections.head;
    while (stmp) {
      if (last_valid != 0) {
#ifdef USE_STDIO
	  if (fprintf(fd,"\n") == EOF) return(1);
#else
	  iwu_strcpy(output_line, "\r\n");
	  if (os_file_write(fd, output_line, iwu_strlen(output_line)) == 0) return(1);
#endif
      }
      last_valid = stmp->valid;
      if (last_valid) {
#ifdef USE_STDIO
	  if (fprintf(fd,"[%s]\n",stmp->name) == EOF) return(2);
#else
	  iwu_strcpy(output_line, "[");
	  iwu_strcat(output_line, stmp->name);
	  iwu_strcat(output_line, "]\r\n");
	  if (os_file_write(fd, output_line, iwu_strlen(output_line)) == 0) return(2);
#endif
      } else {
#ifdef USE_STDIO
	  if (fprintf(fd,"%s\n",stmp->name) == EOF) return(2);
#else
	  iwu_strcpy(output_line, stmp->name);
	  iwu_strcat(output_line, "\r\n");
	  if (os_file_write(fd, output_line, iwu_strlen(output_line)) == 0) return(2);
#endif
      }
      etmp = (profile_entry RFAR *)stmp->entries.head;
      while (etmp) {
	last_valid = etmp->valid;
	if (last_valid) {
#ifdef USE_STDIO
	    if (fprintf(fd,"%s=%s\n",etmp->name, etmp->value) == EOF) return(2);
#else
	    iwu_strcpy(output_line, etmp->name);
	    iwu_strcat(output_line, "=");
	    iwu_strcat(output_line, etmp->value);
	    iwu_strcat(output_line, "\r\n");
	    if (os_file_write(fd, output_line, iwu_strlen(output_line)) == 0) return(2);
#endif
	} else {
#ifdef USE_STDIO
	    if (fprintf(fd,"%s\n",etmp->name) == EOF) return(2);
#else
	    iwu_strcpy(output_line, etmp->name);
	    iwu_strcat(output_line, "\r\n");
	    if (os_file_write(fd, output_line, iwu_strlen(output_line)) == 0) return(2);
#endif
	}
	etmp = (profile_entry RFAR *)etmp->node.next;
      }
      stmp = (profile_section RFAR *)stmp->node.next;
    }
#ifdef USE_STDIO
    if (fclose(fd) == EOF) return(2);
#else
    if (os_file_close(fd) != 0) return(2);
#endif
  } else {
    return(1);
  }
  return(0);
}

#ifndef USE_MALLOC
void iwu_profile_init(
    char RFAR *profile_buffer,
    unsigned short len)
{
	struct mem_block RFAR *m;
	unsigned short i;
	
	memory = profile_buffer;
	for (i = 0; i < len; i++) {
		memory[i] = 0;
	}

	//memory = MK_FP(mem_seg, sizeof(struct mem_block));
	if (FP_OFF(memory) == 0)
		memory += sizeof(struct mem_block);

	m = (struct mem_block RFAR *)memory;
	m->size = MEM_SIZE;
	m->next_block = 0;
	m->prev_block = 0;
	m->status = EMPTY_STATUS;
}
#endif

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

FUNCTION DEFINITION:
iwu_load_profile - cache the profile specified by file in memory

If you are NOT using the C compilers malloc and free then you must
call iwu_profile_init() first to set up a memory heap to be used by the
profile code.

RETURNS: void
*/
int iwu_load_profile(
  char RFAR *file) /* profile to be cached */
{
    unload_profile(file);
    return(!(load_profile(file)));
}

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

FUNCTION DEFINITION:
iwu_close_profile - remove a cached profile from memory

RETURNS: void
*/
int iwu_close_profile(
  char RFAR *file) /* profile to "un-cache" */
{
  int ret;
  profile_header RFAR  *profile=0, RFAR *ptmp;

  ptmp = (profile_header RFAR  *)profiles.head;
  while (ptmp) {
    if (iwu_stricmp(ptmp->filename, file) == 0) {
      profile = ptmp;
      ptmp = 0;
    }
    else {
      ptmp = (profile_header RFAR  *)ptmp->node.next;
    }
  }
  if (profile) {
    ret = write_profile_to_disk(profile);
    if (ret == 0) unload_profile(file);
    return(ret);
  } else {
    return(1);
  }
}
#endif /* if _WINDOWS is not defined */

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

FUNCTION DEFINITION:
iwu_write_profile_string - write string to a profile

DESCRIPTION:
iwu_write_profile_string performs the same function as the Microsoft
Windows function WritePrivateProfileString.

This routine searches the in memory list of profiles that may be cached
with a call to iwu_load_profile.  If the profile has not been chached
and it exists on disk it is cached in memory.  If the profile does
not exist on disk, a new profile structure is built in memory.

Once there is a cached profile structure, it is searched for the 
section specified by the caller.  If the section does not exist, a
new section is created.

Once there is a valid section structure corresponding to the caller's
section, the entries in the section are searched to find the entry
that matches the caller's specification.  If the entry does not
exist a new entry is created.

Once there is a valid entry, the value specified by the caller's 
buffer is copied into the entry.

The profile is then written out to disk.

If this routine is being compiled specifically for use with Microsoft
Windows, the actual Microsoft WritePrivateProfileString is called.

RETURNS: void

SEE ALSO: 
iwu_load_profile
WritePrivateProfileString in the Microsoft Windows SDK documentation

*/
int iwu_write_profile_string(
  char RFAR *in_section, /* section of profile to update */
  char RFAR *in_entry,   /* entry of profile to update */
  char RFAR *buffer,     /* new value for entry */
  char RFAR *file)       /* profile to be updated */
{
#ifdef _WINDOWS
  return(WritePrivateProfileString(in_section, in_entry, buffer, file));
#else
  profile_header RFAR  *profile, RFAR *ptmp;
  profile_section RFAR *section, RFAR *stmp;
  profile_entry RFAR *entry, RFAR *etmp, RFAR *netmp;

  /* Find the profile in the cached list.  If the profile has not been
   * cached, cache it.  If the profile does not exist on disk go on.
   */
  do {
    profile = 0;
    ptmp = (profile_header RFAR  *)profiles.head;
    while (ptmp) {
      if (iwu_stricmp(ptmp->filename, file) == 0) {
	profile = ptmp;
	ptmp = 0;
      }
      else {
	ptmp = (profile_header RFAR  *)ptmp->node.next;
      }
    }
  } while (!profile && load_profile(file) != NO_SUCH_FILE);

  if (!profile) {
    /* if we get in here we are building a new profile */
    profile = (profile_header RFAR  *)pmalloc(sizeof(profile_header));
    if (profile) {
      iwl_InitList(&profile->sections);
      profile->filename = (char RFAR *)pmalloc(iwu_strlen(file) + 1);
      if (profile->filename) {
	iwu_strcpy(profile->filename, file);
	iwl_AddNode(&profiles, (struct iwl_node RFAR *)profile, 0, IWL_PREPEND);
      }
    } else {
      return(IW_NO_MEMORY);
    }
  }

   /* At this point we should have a valid profile structure */
  if (profile) {
    /* Find the specified section in the profile.  If the section does 
     * not exist then go on.
     */
    section = 0;
    stmp = (profile_section RFAR *)profile->sections.head;
    while (stmp) {
      if (stmp->valid && iwu_stricmp(stmp->name, in_section) == 0) {
	section = stmp;
	stmp = 0;
      }
      else {
	stmp = (profile_section RFAR *)stmp->node.next;
      }
    }
    if (!section && in_entry) {
      /* If we get in here we are making a new section for the profile */
      section = (profile_section RFAR *)pmalloc(sizeof(profile_section));
      if (section) {
	section->valid = 1;
	section->name = (char RFAR *)pmalloc(iwu_strlen(in_section) + 1);
	if (section->name) {
	  iwu_strcpy(section->name, in_section);
	  iwl_InitList(&section->entries);
	  iwl_AddNode(&profile->sections, (struct iwl_node RFAR *)section, 0, IWL_APPEND);
	} else {
	  return(IW_NO_MEMORY);
	}
      } else {
	return(IW_NO_MEMORY);
      }
    }
    if (section) {
      /* Find the entry in the specified section.  If the entry does
       * not exist then go on.
       */
      if (in_entry == 0) { /* if entry is NULL remove section */
	etmp = (profile_entry RFAR *)section->entries.head;
	while (etmp) {
	    netmp = (profile_entry RFAR *)etmp->node.next;
	    pfree(etmp->name);
	    pfree(etmp->value);
	    iwl_DeleteNode(&section->entries, (struct iwl_node RFAR *)etmp);
	    pfree(etmp);
	    etmp = netmp;
	}
	pfree(section->name);
	iwl_DeleteNode(&profile->sections, (struct iwl_node RFAR *)section);
	pfree(section);
      } else {
	entry = 0;
	etmp = (profile_entry RFAR *)section->entries.head;
	while (etmp) {
	  if (etmp->valid && iwu_stricmp(etmp->name, in_entry) == 0) {
	    entry = etmp;
	    etmp = 0;
	  }
	  else {
	    etmp = (profile_entry RFAR *)etmp->node.next;
	  }
	}
	if (!entry && buffer) {
	  /* if we are in here we are making a new profile entry */
	  entry = (profile_entry RFAR *)pmalloc(sizeof(profile_entry));
	  if (entry) {
	    entry->valid = 1;
	    entry->name = (char RFAR *)pmalloc(iwu_strlen(in_entry) + 1);
	    if (entry->name) {
	      iwu_strcpy(entry->name, in_entry);
	      entry->value = 0;
	      iwl_AddNode(&section->entries, (struct iwl_node RFAR *)entry, 0, IWL_APPEND);
	    } else {
	      return(IW_NO_MEMORY);
	    }
	  } else {
	    return(IW_NO_MEMORY);
	  }
	}
	if (entry) {
	  if (buffer) {
	    /* We now have a valid entry structure in a valid section structure
	     * in a valid profile structure so..
	     */
	    if (entry->value) pfree(entry->value);
	    entry->value = (char RFAR *)pmalloc(iwu_strlen(buffer) + 1);
	    if (entry->value) {
	      /* copy the value to the profile entry and write the profile
	       * to disk.
	       */
	      iwu_strcpy(entry->value, buffer);
	    } else {
	      return(IW_NO_MEMORY);
	    }
	  } else {
	    /* remove entry from section */
	    pfree(entry->name);
	    if (entry->value) pfree(entry->value);
	    iwl_DeleteNode(&section->entries, (struct iwl_node RFAR *)entry);
	  }
	}
      }
    }
  }
  return(0);
#endif
}
