/* m4memory.c   (c)Copyright Sequiter Software Inc., 1990-1994.  All rights reserved. */

#ifdef S4OS2
#ifdef __DLL__
#define  INCL_DOSMEMMGR
#endif
#endif
#include "d4all.h"

#ifdef S4VB_DOS
#include "malloc.h"
#endif

#ifndef S4UNIX
#ifdef __TURBOC__
#pragma hdrstop
#endif
#endif

#ifdef S4OS2
#ifdef __DLL__
#include <bsememf.h>
#endif
#endif

#ifdef S4MEM_PRINT
int v4print = 0 ; /* if v4print == 0 then stdout, else stdprn */
#endif

#define mem4num_types 10

typedef struct
{
  LINK4  link ;
  MEM4  types[mem4num_types] ;
} MEMORY4GROUP ;

static LIST4  avail = { 0, 0, 0 } ;   /* A list of available MEM4 entries */
static LIST4  used = { 0, 0, 0 } ;   /* A list of used MEM4 entries */
static LIST4  groups = { 0, 0, 0 } ; /* A list of Allocated MEM4 groups */

#ifdef S4OS2
#ifdef S4OS2SEM
int mem4start( CODE4 *c4 )
{
  APIRET rc ;

#ifdef S4DEBUG
  if ( c4 == 0 )
    return e4( c4, e4info, "OS/2 Semaphore Failure" ) ;
#endif

  rc = DosRequestMutexSem( c4->hmtx_mem, -1 ) ;
  if ( rc != 0 )
    return e4( c4, e4info, "OS/2 Semaphore Failure" ) ;
  return 0 ;
}

void mem4stop( CODE4 *c4 )
{
#ifdef S4DEBUG
  if ( c4 == 0 )
  {
    e4( c4, e4info, "OS/2 Semaphore Failure" ) ;
    return ;
  }
#endif

  DosReleaseMutexSem( c4->hmtx_mem ) ;
}
#endif
#endif

#ifdef S4DEBUG

static char **mem4test_pointers ;
static int    mem4num_pointer = -1 ;
static int    mem4num_used = 0 ;

#ifdef S4UNIX
#define mem4extra_chars 12
#else
#define mem4extra_chars 10
#endif
#define mem4extra_tot   (mem4extra_chars*2 + sizeof(unsigned))
#define mem4check_char  0x55

/* Returns the pointer to be returned; is passed the pointer allocated by malloc ... */
static char *mem4fix_pointer( char *start_ptr, unsigned large_len )
{
  char *return_ptr ;
  unsigned pos ;

  memset( start_ptr, mem4check_char, mem4extra_chars ) ;
  return_ptr = start_ptr + mem4extra_chars ;

  memcpy( return_ptr, (void *)&large_len, sizeof(large_len) ) ;
  pos = large_len - mem4extra_chars ;
  memset( start_ptr+ pos, mem4check_char, mem4extra_chars ) ;

  return return_ptr + sizeof(unsigned) ;
}

/* Returns the pointer allocated by malloc; */
/* passed by pointer returned by 'mem4fix_pointer' */
static char *mem4check_pointer( char *return_ptr, int clear )
{
  unsigned *large_len_ptr ;
  char *malloc_ptr, *test_ptr ;
  int i, j ;

  large_len_ptr = (unsigned *)(return_ptr - sizeof(unsigned)) ;
  malloc_ptr = return_ptr - sizeof(unsigned) - mem4extra_chars ;

  for ( j = 0; j < 2; j++ )
  {
    if (j == 0)
      test_ptr = malloc_ptr ;
    else
      test_ptr = malloc_ptr + *large_len_ptr - mem4extra_chars ;

    for ( i = 0 ; i < mem4extra_chars ; i++ )
      if ( test_ptr[i] != mem4check_char )
        e4severe( e4result, E4_RESULT_CMP ) ;
  }
  if ( clear == 1 ) /* null the memory to potentially detect re-use, including clearing check chars */
    memset( malloc_ptr, 0, *large_len_ptr ) ;
  return malloc_ptr ;
}

static void mem4push_pointer( char *ptr )
{
#ifdef S4WINDOWS
  HANDLE  handle, *h_ptr, *old_h_ptr ;
  h_ptr = (HANDLE *)0 ;
#endif

  if ( mem4num_pointer < 0 )
  {
#ifdef S4WINDOWS
#ifdef __DLL__
    handle = GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, (DWORD) sizeof(char *) * 100 + sizeof(HANDLE) ) ;
#else
    handle = GlobalAlloc( GMEM_MOVEABLE | GMEM_ZEROINIT, (DWORD) sizeof(char *) * 100 + sizeof(HANDLE) ) ;
#endif

    if ( handle == (HANDLE) 0 )
      e4severe( e4memory, E4_MEMORY_YPU ) ;

    h_ptr = (HANDLE *)GlobalLock( handle ) ;
    *h_ptr++ = handle ;
    mem4test_pointers = (char **)h_ptr ;
#else
    mem4test_pointers = (char **)malloc( sizeof(char *) * 100 ) ;
#endif
    mem4num_pointer = 100 ;
  }
  if ( mem4num_pointer == mem4num_used )
  {
    mem4num_pointer += 100 ;
    if ( mem4num_pointer > 10000 )
      e4severe( e4result, E4_MEMORY_YPU ) ;

#ifdef S4WINDOWS
    old_h_ptr = (HANDLE *)(mem4test_pointers) ;
    old_h_ptr-- ;  /* get the actual handle */

#ifdef __DLL__
    handle = GlobalReAlloc( *old_h_ptr, (DWORD)sizeof(char *) * mem4num_pointer + sizeof( HANDLE ), GMEM_MOVEABLE ) ;
#else
    handle = GlobalReAlloc( *old_h_ptr, (DWORD)sizeof(char *) * mem4num_pointer + sizeof( HANDLE ), GMEM_MOVEABLE ) ;
#endif

    if ( handle == (HANDLE) 0 )
      e4severe( e4memory, E4_MEMORY_YPU ) ;

    h_ptr = (HANDLE *)GlobalLock( handle ) ;
    *h_ptr++ = handle ;
    mem4test_pointers = (char **)h_ptr ;
#else
    mem4test_pointers = (char **)realloc( (void *)mem4test_pointers, sizeof(char *)*mem4num_pointer ) ;
#endif
  }

  if ( mem4test_pointers == 0 )
    e4severe( e4memory, E4_MEMORY_YPU ) ;

  mem4test_pointers[mem4num_used++] = ptr ;
}

static void mem4pop_pointer( char *ptr )
{
  int i ;

  for ( i = mem4num_used - 1 ; i >= 0 ; i-- )
    if ( mem4test_pointers[i] == ptr )
    {
      /* This 'memmove' may create compile warning */
      memmove( mem4test_pointers+i, mem4test_pointers+i+1, (size_t) (sizeof(char *) * (mem4num_used-i-1))) ;
      mem4num_used-- ;
      return ;
    }
  e4severe( e4result, E4_MEMORY_YPO ) ;
}

void S4FUNCTION mem4check_memory()
{
  int i ;

  for ( i = 0; i < mem4num_used; i++ )
    mem4check_pointer( mem4test_pointers[i], 0 ) ;
}

int S4FUNCTION mem4free_check( int max_left )
{
#ifdef S4MEM_PRINT
  int i ;
  if ( v4print )
    for ( i = 0; i < mem4num_used; i++ )
      fprintf( stdprn, "\r\nmem4free_check: %p", mem4test_pointers[i] ) ;
  else
    for ( i = 0; i < mem4num_used; i++ )
      printf( "\r\nmem4free_check: %p", mem4test_pointers[i] ) ;
#endif

  if ( mem4num_used > max_left )
    e4severe( e4result, E4_RESULT_FRE ) ;

  return ( mem4num_used ) ;
}
#endif


void *S4FUNCTION mem4alloc( MEM4 *memory_type )
{
#ifdef S4DEBUG
  if ( memory_type == 0 )
    e4severe( e4parm, E4_MEM4ALLOC ) ;
#endif

  return mem4alloc2( memory_type, 0 ) ;
}

Y4CHUNK *S4FUNCTION mem4alloc_chunk( MEM4 *type_ptr )
{
  Y4CHUNK *chunk_ptr ;
  int  n_allocate, i ;
  char *ptr ;

  n_allocate = type_ptr->unit_expand ;
  if ( l4last( &type_ptr->chunks ) == 0 )
    n_allocate = type_ptr->unit_start ;

  chunk_ptr = (Y4CHUNK *)u4alloc_free( type_ptr->code_base, sizeof( LINK4 ) + (long)n_allocate*type_ptr->unit_size ) ;
  if ( chunk_ptr == 0 )
    return 0 ;
  ptr = (char *)&chunk_ptr->data ;
  for ( i = 0 ; i < n_allocate ; i++ )
    l4add( &type_ptr->pieces, (LINK4 *)( ptr + i * type_ptr->unit_size ) ) ;

  return  chunk_ptr ;
}

static void *mem4alloc_low( MEM4 *memory_type )
{
  LINK4 *next_piece ;
  Y4CHUNK *new_chunk ;
#ifdef S4DEBUG
  char *ptr ;
#endif
#ifdef S4OS2SEM
#ifdef S4OS2
#ifdef __DLL__
  ULONG flags ;
#endif
#endif
#endif

  if ( memory_type == 0 )
    return 0 ;
  next_piece = (LINK4 *)l4pop( &memory_type->pieces ) ;

  if ( next_piece != 0 )
  {
#ifdef S4OS2
#ifdef S4OS2SEM
#ifdef __DLL__
    /* get access to the memory */
    flags = PAG_WRITE | PAG_READ  ;
    if ( DosGetSharedMem( next_piece, flags ) != 0 )
      return 0 ;
#endif
#endif
#endif
#ifdef S4DEBUG
    memory_type->n_used++ ;
    ptr = mem4fix_pointer( (char *)next_piece, memory_type->unit_size ) ;

#ifdef S4MEM_PRINT
    if ( v4print )
      fprintf( stdprn, "\r\n  Y4ALLOC:  %p", ptr );
    else
      printf( "\r\n  Y4ALLOC:  %p", ptr);
#endif

    mem4push_pointer( ptr ) ;
    return (void *)ptr ;
#else
    return next_piece ;
#endif
  }

  if ( (new_chunk = mem4alloc_chunk( memory_type )) == 0 )
    return 0 ;
  l4add( &memory_type->chunks, &new_chunk->link ) ;

  memory_type->n_used++ ;
#ifdef S4DEBUG
  ptr = mem4fix_pointer( (char *)l4pop(&memory_type->pieces),
                        memory_type->unit_size ) ;
#ifdef S4MEM_PRINT
  if ( v4print )
    fprintf(stdprn, "\r\n  Y4ALLOC:  %p", ptr);
  else
    printf( "\r\n  Y4ALLOC:  %p", ptr);
#endif

  mem4push_pointer( ptr ) ;
  return (void *)ptr ;
#else
  return l4pop( &memory_type->pieces ) ;
#endif
}

void *S4FUNCTION mem4alloc2( MEM4 *memory_type, CODE4 *c4 )
{
  void *ptr ;

  if ( c4 )
    if ( c4->error_code < 0 )
      return 0 ;

#ifdef S4OS2SEM
#ifdef S4OS2
  if ( mem4start( memory_type->code_base ) != 0 )
    return 0 ;
#endif
#endif

  ptr = mem4alloc_low( memory_type ) ;

#ifdef S4OS2SEM
#ifdef S4OS2
  mem4stop( memory_type->code_base ) ;
#endif
#endif

  if ( ptr == 0 )
  {
    if ( c4 )
      e4set( c4, e4memory ) ;
    return 0 ;
  }
#ifdef S4DEBUG
  memset( ptr, 0, memory_type->unit_size - mem4extra_tot ) ;
#else
  memset( ptr, 0, memory_type->unit_size ) ;
#endif
  return ptr ;
}

MEM4 *S4FUNCTION mem4create( CODE4 *c4, int start, unsigned unit_size, int expand, int is_temp )
{
  MEM4 *on_type ;
#ifdef S4OS2SEM
#ifdef S4OS2
#ifdef __DLL__
  ULONG flags ;
#endif
#endif
#endif

#ifdef S4DEBUG
  if ( start < 0 || expand < 0 )
    e4severe( e4parm, E4_MEM4CREATE ) ;
  unit_size += 2 * mem4extra_chars + sizeof( unsigned ) ;
#ifdef S4OS2
  if ( c4 == 0 )
    e4severe( e4info, "OS/2 mem4create() requires valid CodeBase pointer" ) ;
#endif
#endif

  if ( c4 )
    if ( c4->error_code < 0 )
      return 0 ;

#ifdef S4OS2SEM
#ifdef S4OS2
  if ( mem4start( c4 ) != 0 )
    return 0 ;
#endif
#endif

  if ( !is_temp )
    for( on_type = 0 ; ; )
    {
      on_type = (MEM4 *)l4next( &used, on_type ) ;
      if ( on_type == 0 )
        break ;

#ifdef S4OS2
#ifdef S4OS2SEM
#ifdef __DLL__
      /* get access to the memory */
      flags = PAG_WRITE | PAG_READ  ;
      if ( DosGetSharedMem( on_type, flags ) != 0 )
        return 0 ;
#endif
#endif
#endif

      if ( on_type->unit_size == unit_size && on_type->n_repeat > 0 )
      {
        /* Match */
        if ( start > on_type->unit_start )
          on_type->unit_start = start ;
        if ( expand > on_type->unit_expand)
          on_type->unit_expand = expand ;
        on_type->n_repeat++ ;
#ifdef S4OS2
#ifdef S4OS2SEM
        mem4stop( c4 ) ;
#endif
#endif
        return on_type ;
      }
    }

  /* Allocate memory for another MEM4 */

  on_type = (MEM4 *)l4last( &avail ) ;
  if ( on_type == 0 )
  {
    MEMORY4GROUP *group ;
    int i ;

    group = (MEMORY4GROUP *)u4alloc_free( c4, sizeof( MEMORY4GROUP ) ) ;
    if ( group == 0 )
    {
      if ( c4 )
        e4set( c4, e4memory ) ;
#ifdef S4OS2SEM
#ifdef S4OS2
      mem4stop( c4 ) ;
#endif
#endif
      return 0 ;
    }

    for ( i = 0 ; i < mem4num_types ; i++ )
      l4add( &avail, group->types + i ) ;
    on_type = (MEM4 *)l4last( &avail ) ;
    l4add( &groups, group ) ;
  }

  l4remove( &avail, on_type ) ;
  memset( (void *)on_type, 0, sizeof( MEM4 ) ) ;
  l4add( &used, on_type ) ;

#ifdef S4OS2SEM
#ifdef S4OS2
  mem4stop( c4 ) ;
#endif
#endif

  on_type->unit_start = start ;
  on_type->unit_size = unit_size ;
  on_type->unit_expand= expand ;
  on_type->n_repeat = 1 ;
  on_type->n_used = 0 ;
  if ( is_temp )
    on_type->n_repeat = -1 ;

#ifdef S4MEM_PRINT
  if ( v4print )
    fprintf( stdprn, "\r\n    MEM4: %p", on_type ) ;
  else
    printf( "\r\n    MEM4: %p", on_type ) ;
#endif

  on_type->code_base = c4 ;
  return on_type ;
}

void *S4FUNCTION mem4create_alloc( CODE4 *c4, MEM4 **type_ptr_ptr, int start, unsigned unit_size, int expand, int is_temp)
{
  if ( *type_ptr_ptr == 0 )
  {
    *type_ptr_ptr = mem4create( c4, start, unit_size, expand, is_temp ) ;
    if ( *type_ptr_ptr == 0 )
      return 0 ;
  }

  return mem4alloc2( *type_ptr_ptr, c4 ) ;
}

void S4FUNCTION mem4free( MEM4 *memory_type, void *free_ptr )
{
  if ( memory_type == 0 || free_ptr == 0 )
    return ;

  memory_type->n_used-- ;
#ifdef S4DEBUG
  if ( memory_type->n_used < 0 )
    e4severe( e4result, E4_MEM4FREE ) ;

#ifdef S4MEM_PRINT
  if ( v4print )
    fprintf(stdprn, "\r\n  Y4FREE:  %p", free_ptr ) ;
  else
    printf( "\r\n  Y4FREE:  %p", free_ptr ) ;
#endif

  mem4pop_pointer( (char *)free_ptr ) ;
  l4add( &memory_type->pieces, (LINK4 *)mem4check_pointer( (char *)free_ptr, 0 ) ) ;
  /*    memset( (void *)&free_ptr, 0, sizeof(free_ptr) ) ; */
#else
  l4add( &memory_type->pieces, (LINK4 *)free_ptr ) ;
#endif
}

void S4FUNCTION mem4release( MEM4 *memory_type )
{
  void *ptr ;

  if ( memory_type == 0 )
    return ;

  memory_type->n_repeat-- ;
  if ( memory_type->n_repeat <= 0 )
  {
    for(;;)
    {
      ptr = l4pop( &memory_type->chunks) ;
      if ( ptr == 0 )
        break ;
      u4free( ptr ) ;
    }

    l4remove( &used, memory_type ) ;
    l4add( &avail, memory_type ) ;

#ifdef S4MEM_PRINT
    if ( v4print )
      fprintf( stdprn, "\r\n    Y4RELEASE: %p", memory_type ) ;
    else
      printf( "\r\n    Y4RELEASE: %p", memory_type ) ;
#endif
  }
}

#ifdef S4OLD_CODE
MEM4 *S4FUNCTION mem4type( int start, unsigned unit_size, int expand, int is_temp)
{
  return mem4create( 0, start, unit_size, expand, is_temp ) ;
}
#endif

#ifdef S4MAX
long  mem4max_memory = 0x4000 ;
long  mem4allocated = 0L ;
#ifndef S4DEBUG
S4DEBUG should be set with S4MAX (force compile error.)
#endif
#endif

  void *S4FUNCTION u4alloc( long n )
{
  size_t s ;
  char *ptr ;
#ifdef S4OS2SEM
#ifdef __DLL__
#ifdef S4OS2
  ULONG    flags;
  APIRET   rc;
#endif
#endif
#endif

#ifdef S4DEBUG
  if ( n == 0L )
    e4severe( e4parm, E4_PARM_ZER ) ;
  n += mem4extra_chars*2 + sizeof(unsigned) ;
#endif

#ifdef S4MAX
  /* Assumes 'mem4max_memory' is less than the actual maximum */
  if ( mem4allocated + n > mem4max_memory )
    return 0 ;
  mem4allocated += n ;
#endif

  s = (size_t) n ;
  if ( n > (long) s )
    return 0 ;

#ifdef S4WINDOWS
{
  HANDLE  handle, *h_ptr ;
  h_ptr = (HANDLE *)0 ;

#ifdef __DLL__
  handle = GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, (DWORD) s+ sizeof(HANDLE) ) ;
#else
  handle = GlobalAlloc( GMEM_MOVEABLE | GMEM_ZEROINIT, (DWORD) s+ sizeof(HANDLE) ) ;
#endif

  if ( handle == (HANDLE) 0 )
    return 0 ;

  h_ptr = (HANDLE *)GlobalLock( handle ) ;
  *h_ptr++ = handle ;
  ptr = (char *)h_ptr ;
}
#else
#ifdef __DLL__
#ifdef S4OS2SEM
#ifndef S4OS2
error invalid switch configuration
#endif
flags = PAG_WRITE | PAG_READ | OBJ_GETTABLE ;
rc = DosAllocSharedMem( (void *)&ptr, 0, s, flags ) ;

if (rc != 0)
  return 0 ;
  flags = PAG_WRITE | PAG_READ ;
  rc = DosGetSharedMem( ptr, flags ) ;
  if ( rc != 0 )
  return 0 ;
#else
  ptr = (char *)malloc( s ) ;

  if ( ptr == 0 )
  return 0 ;

#ifndef S4PASCAL_WIN
#ifndef S4DEBUG
  memset( ptr, 0, s ) ;
#endif
#endif
#endif
#else
  ptr = (char *)malloc( s ) ;

  if ( ptr == 0 )
  return 0 ;

  /* Borland malloc of 64K (or close to) will */
#ifdef __TURBOC__    /* result in corruption of segment memory due */
  if ( (ptr+s-1 <= ptr) && (s > 1 ))    /* to wrap-around problems  */
{
  free( ptr ) ;
  return 0 ;
}
#endif

#ifndef S4PASCAL_WIN
#ifndef S4DEBUG
memset( ptr, 0, s ) ;
#endif
#endif
#endif
#endif

#ifdef S4DEBUG
ptr = mem4fix_pointer( ptr, s ) ;
mem4push_pointer( ptr ) ;
memset( ptr, 0, s-mem4extra_chars*2 - sizeof(unsigned) ) ;
#endif

#ifdef S4MEM_PRINT
if ( v4print )
  fprintf( stdprn, "\r\nU4ALLOC:  %p   # bytes alloc: %ld", ptr, n );
else
printf("\r\nU4ALLOC:  %p   # bytes alloc: %ld", ptr, n);
#endif

return (void *)ptr ;
}

void *S4FUNCTION u4alloc_er( CODE4 S4PTR *c4, long n )
{
  void *ptr = u4alloc_free( c4, n ) ;
  if ( ptr == 0 && c4 )
    e4( c4, e4memory, 0 ) ;

  return ptr ;
}

void S4FUNCTION u4free( void *ptr )
{
#ifdef S4WINDOWS
  HANDLE  hand ;
#endif

#ifdef S4MAX
  unsigned *amount ;
#endif

  if ( ptr == 0 )
    return ;

#ifdef S4MAX
  amount = (unsigned *)ptr ;
  mem4allocated -= amount[-1] ;
#endif

#ifdef S4WINDOWS
#ifdef S4DEBUG
  mem4pop_pointer( (char *)ptr ) ;
  hand = ((HANDLE *)mem4check_pointer( (char *)ptr, 1 ))[-1] ;
#else
  hand = ((HANDLE *)ptr)[-1] ;
#endif

  GlobalUnlock( hand ) ;
  hand = GlobalFree( hand ) ;

  if ( hand != (HANDLE) 0 )
    e4severe( e4memory, E4_MEMORY_ERR ) ;
#else
#ifdef S4DEBUG
  mem4pop_pointer( (char *)ptr ) ;

#ifdef S4MEM_PRINT
  if ( v4print )
    fprintf( stdprn, "\r\nU4FREE:  %p", ptr ) ;
  else
    printf( "\r\nU4FREE:  %p", ptr );
#endif

  free(mem4check_pointer( (char *)ptr, 1 ) ) ;
#else
#ifdef S4MEM_PRINT
  if ( v4print )
    fprintf( stdprn, "\r\nU4FREE:  %p", ptr ) ;
  else
    printf( "\r\nU4FREE:  %p", ptr );
#endif

  free( ptr ) ;
#endif
#endif
}

void S4FUNCTION mem4reset()
{
  MEM4 *on_type ;
  LINK4 *on_chunk, *on_group ;
#ifdef S4WINDOWS
  HANDLE  hand ;
#endif
#ifdef S4LOCK_CHECK
  e4severe( e4result, E4_RESULT_S4L ) ;
#endif

  for( on_type = 0 ;; )
  {
    on_type = (MEM4 *)l4next(&used,on_type) ;
    if ( on_type == 0 )
      break ;
    do
    {
      on_chunk = (LINK4 *)l4pop( &on_type->chunks) ;
      u4free( on_chunk ) ;  /* free of 0 still succeeds */
    } while ( on_chunk ) ;
  }

  for( ;; )
  {
    on_group = (LINK4 *)l4pop( &groups ) ;
    if ( on_group == 0 )
      break ;
    u4free( on_group ) ;
  }

#ifdef S4DEBUG
  if ( mem4num_pointer > 0 )
  {
#ifdef S4WINDOWS
    hand = ((HANDLE *)mem4test_pointers)[-1] ;

    GlobalUnlock( hand ) ;
    hand = GlobalFree( hand ) ;

    if ( hand != (HANDLE)0 )
      e4severe( e4memory, E4_MEMORY_ERR ) ;
#else
#ifdef S4MEM_PRINT
    if ( v4print )
      fprintf( stdprn, "\r\nMEM4RESET:  %p", mem4test_pointers ) ;
    else
      printf( "\r\nMEM4RESET:  %p", mem4test_pointers );
#endif

    free( (void *)mem4test_pointers ) ;
#endif

    mem4test_pointers = 0 ;
    mem4num_pointer = -1 ;
    mem4num_used = 0 ;
  }
#endif

  mem4init() ;
}

void S4FUNCTION mem4init()
{
  memset( (void *)&avail, 0, sizeof( avail ) ) ;
  memset( (void *)&used, 0, sizeof( used ) ) ;
  memset( (void *)&groups, 0, sizeof( groups ) ) ;
}
