/*
** Splint - annotation-assisted static program checker
** Copyright (C) 1994-2003 University of Virginia,
**         Massachusetts Institute of Technology
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 2 of the License, or (at your
** option) any later version.
** 
** This program is distributed in the hope that it will be useful, but
** WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
** 
** The GNU General Public License is available from http://www.gnu.org/ or
** the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
** MA 02111-1307, USA.
**
** For information on splint: info@splint.org
** To report a bug: splint-bug@splint.org
** For more information: http://www.splint.org
*/
/*
** fileTable.c
**
** replaces filenamemap.c
** based (loosely) on typeTable.c
**
** entries in the fileTable are:
**
**        name - name of the file
**        type - kind of file (a temp file to be deleted?)
**        link - derived from this file
**
*/

# include "splintMacros.nf"
# include "basic.h"
# include "osd.h"

/*@access fileId*/

static void 
fileTable_addOpen (fileTable p_ft, /*@observer@*/ FILE *p_f, /*@only@*/ cstring p_fname) 
  /*@modifies p_ft@*/ ;

static bool fileTable_inRange (fileTable ft, fileId fid) /*@*/ 
{
  return (fileTable_isDefined (ft) && (fid >= 0) && (fid < ft->nentries));
}

static fileId fileTable_internAddEntry (fileTable p_ft, /*@only@*/ ftentry p_e) 
   /*@modifies p_ft@*/ ;
static /*@only@*/ cstring makeTempName (cstring p_pre, cstring p_suf);

# ifdef DEADCODE
static /*@only@*/ cstring
fileType_unparse (fileType ft)
{
  switch (ft)
    {
    case FILE_NORMAL:     return cstring_makeLiteral ("normal");
    case FILE_HEADER:     return cstring_makeLiteral ("header");
    case FILE_XH:         return cstring_makeLiteral ("xh");
    case FILE_METASTATE:  return cstring_makeLiteral ("metastate");
    case FILE_RC:         return cstring_makeLiteral ("rc");
    case FILE_LCL:        return cstring_makeLiteral ("lcl");
    case FILE_LCD:        return cstring_makeLiteral ("lcd");
    case FILE_IMPORT:     return cstring_makeLiteral ("import");
    case FILE_PP:         return cstring_makeLiteral ("pp");

    case FILE_LSLTEMP:    return cstring_makeLiteral ("lsltemp");
    case FILE_MACROS:     return cstring_makeLiteral ("macros");
    case FILE_NODELETE:   return cstring_makeLiteral ("nodelete");
    }

  BADEXIT;
}
# endif /* DEADCODE */

static int
fileTable_getIndex (fileTable ft, cstring s)
{
  int res;
  cstring abspath;
  if (ft == NULL) return NOT_FOUND;
  abspath = osd_absolutePath (s);
  
  if (context_getFlag (FLG_CASEINSENSITIVEFILENAMES))
    {
      abspath = cstring_downcase (abspath);
    }

  DPRINTF (("Absolute path: %s: %s", s, abspath));
  res = cstringTable_lookup (ft->htable, abspath);
  cstring_free (abspath);
  return res;
}

# ifdef DEADCODE
static cstring ftentry_unparse (fileTable ft, ftentry fte)
{
  if (fileId_isValid (fte->fder))
    {
      llassert (fileTable_isDefined (ft));

      return message ("%s %q %d (%s)", 
		      fte->fname, 
		      fileType_unparse (fte->ftype),
		      fte->fder,
		      ft->elements[fte->fder]->fname);
    }
  else
    {
      return message ("%s %q", fte->fname,
		      fileType_unparse (fte->ftype));
    }
}

/*@only@*/ cstring
fileTable_unparse (fileTable ft)
{
  cstring s = cstring_undefined;
  int i;

  if (fileTable_isUndefined (ft))
    {
      return (cstring_makeLiteral ("<fileTable undefined>"));
    }

  for (i = 0; i < ft->nentries; i++)
    {
      s = message ("%s\n[%d] %q", s, i, ftentry_unparse (ft, ft->elements[i]));
    }

  return s;
}
# endif /* DEADCODE */

void fileTable_printTemps (fileTable ft)
{
  if (fileTable_isDefined (ft))
    {
      int i;

      for (i = 0; i < ft->nentries; i++)
	{
	  if (ft->elements[i]->ftemp)
	    {
	      if (fileId_isValid (ft->elements[i]->fder))
		{
		  fprintf (stderr, "  %s:1\n\t%s:1\n", 
			   cstring_toCharsSafe (ft->elements[ft->elements[i]->fder]->fname),
			   cstring_toCharsSafe (ft->elements[i]->fname));
		}
	      else
		{
		  fprintf (stderr, "[no file]\n\t%s:1\n",
			   cstring_toCharsSafe (ft->elements[i]->fname));
		}
	    }
	}
    }
}

/*
** loads in fileTable from fileTable_dump
*/

static /*@notnull@*/ ftentry
ftentry_create (/*@keep@*/ cstring tn, bool temp, fileType typ, fileId der)
{
  ftentry t = (ftentry) dmalloc (sizeof (*t));
  
  if (cstring_isUndefined (tn))
    {
      llbug (cstring_makeLiteral ("Undefined filename!"));
    }
  
  t->fname = tn;
  t->ftemp = temp;
  t->ftype = typ;
  t->fder  = der;

  /* Don't set these until needed. */
  t->basename = cstring_undefined;
  t->fsystem = FALSE;
  t->fspecial = FALSE;

  return t;
}

static void
ftentry_free (/*@only@*/ ftentry t)
{
  cstring_free (t->fname);
  cstring_free (t->basename);
  sfree (t);
}

/*@only@*/ /*@notnull@*/ fileTable
fileTable_create (void)
{
  fileTable ft = (fileTable) dmalloc (sizeof (*ft));
  
  ft->nentries = 0;
  ft->nspace = FTBASESIZE;
  ft->elements = (ftentry *) dmalloc (FTBASESIZE * sizeof (*ft->elements));
  ft->htable = cstringTable_create (FTHASHSIZE);

  ft->nopen = 0;
  ft->nopenspace = FTBASESIZE;
  ft->openelements = (foentry *) dmalloc (FTBASESIZE * sizeof (*ft->openelements));

  return (ft);
}

/*@-bounds@*/
static void
fileTable_grow (fileTable ft)
{
  int i;
  ftentry *newent;

  llassert (fileTable_isDefined (ft));

  ft->nspace = FTBASESIZE;

  newent = (ftentry *) dmalloc ((ft->nentries + ft->nspace) * sizeof (*newent));

  for (i = 0; i < ft->nentries; i++)
    {
      newent[i] = ft->elements[i];
    }

  sfree (ft->elements);
  ft->elements = newent;
}
/*@=bounds@*/
static void
fileTable_growOpen (fileTable ft)
{
  int i;
  foentry *newent;

  llassert (fileTable_isDefined (ft));

  ft->nopenspace = FTBASESIZE;

  newent = (foentry *) dmalloc ((ft->nopen + ft->nopenspace) * sizeof (*newent));
  
  for (i = 0; i < ft->nopen; i++)
    {
      newent[i] = ft->openelements[i];
    }

  sfree (ft->openelements);
  ft->openelements = newent;
}

static fileId
fileTable_internAddEntry (fileTable ft, /*@only@*/ ftentry e)
{
  cstring sd;
  llassert (fileTable_isDefined (ft));

  if (ft->nspace <= 0)
    fileTable_grow (ft);

  ft->nspace--;

  DPRINTF (("Adding: %s", e->fname));

  if (context_getFlag (FLG_CASEINSENSITIVEFILENAMES))
    {
      sd = cstring_downcase (e->fname);
    }
  else
    {
      sd = cstring_copy (e->fname);
    }
  /* evans 2002-07-12:
     Before, there was no cstring_copy above, and e->fname was free'd in the if branch.
     Splint should have caught this, and produced a warning for this assignment.
     Why not?
  */
  cstringTable_insert (ft->htable, sd, ft->nentries); 

  ft->elements[ft->nentries] = e;
  ft->nentries++;
  return (ft->nentries - 1);
}

void fileTable_noDelete (fileTable ft, cstring name)
{
  fileId fid = fileTable_lookup (ft, name);

  if (fileId_isValid (fid)) 
    {
      llassert (fileTable_isDefined (ft));
      ft->elements[fid]->ftype = FILE_NODELETE;
    }
  else
    {
      DPRINTF (("Invalid no delete: %s", name));
    }
}

static fileId
fileTable_addFilePrim (fileTable ft, /*@temp@*/ cstring name, 
		       bool temp, fileType typ, fileId der)
   /*@modifies ft@*/
{
  ftentry e;
  cstring absname = osd_absolutePath (name);
  int tindex = fileTable_getIndex (ft, absname);
  
  llassert (ft != fileTable_undefined);

  if (tindex != NOT_FOUND)
    {
      llcontbug (message ("%s: duplicate entry: %q", __func__, absname));
      return tindex;
    }

  e = ftentry_create (absname, temp, typ, der);
  llassert (cstring_isUndefined (e->basename));
  if (der == fileId_invalid)
    {
      e->basename = fileLib_removePathFree (fileLib_removeAnyExtension (absname));
      e->fsystem = !temp &&
        (typ == FILE_NORMAL || typ == FILE_HEADER) &&
        (context_isSystemDir (absname)
          /*
          ** evans 2002-03-15: change suggested by Jim Zelenka
          **                   support relative paths for system directories
          */
         || context_isSystemDir (name));
      e->fspecial = fileLib_isSpecialFile (absname);

      if (e->fspecial)
        {
          cstring srcname = cstring_concatFree1 (fileLib_removeAnyExtension (absname), 
                                                 C_EXTENSION);
          fileId fid = fileTable_lookup (ft, srcname);
          cstring_free (srcname);

          if (fileId_isValid (fid))
            {
              fileId derid = ft->elements[fid]->fder;

              ft->elements[fid]->fspecial = TRUE;

              if (fileId_isValid (derid))
                {
                  ft->elements[derid]->fspecial = TRUE;
                }
            }
        }
    }
  else
    {
      ftentry de = ft->elements[der];

      e->basename = cstring_copy (de->basename);
      e->fsystem = de->fsystem;
      e->fspecial = de->fspecial;
    }

  return (fileTable_internAddEntry (ft, e));
}

fileId
fileTable_addFile (fileTable ft, fileType typ, cstring name)
{
  switch (typ)
    {
      case FILE_RC:
      case FILE_PP:
      case FILE_NORMAL:
      case FILE_HEADER:
      case FILE_METASTATE:
      case FILE_XH:
      case FILE_LCL:
      case FILE_IMPORT:
      case FILE_LCD:
        return fileTable_addFilePrim (ft, name, FALSE, typ, fileId_invalid);

      default:
        BADBRANCH;
        return fileId_invalid;
    }
}

fileId
fileTable_addTempFile (fileTable ft, fileType ftype, fileId fid)
{
  fileId res;
  cstring newname, pre, suf;

  llassert (fileTable_isDefined (ft));

  switch (ftype) {
    case FILE_MACROS:
      llassert (fid == fileId_invalid);
      pre = cstring_makeLiteralTemp ("lmx");
      suf = cstring_makeLiteralTemp (".llm");
      break;

    case FILE_LSLTEMP:
      llassert (fid == fileId_invalid);
      pre = cstring_makeLiteralTemp ("ls");
      suf = cstring_makeLiteralTemp (".lsl");
      break;

    case FILE_NORMAL:
    case FILE_HEADER:
    case FILE_XH:
      /* temporary files for C preprocessing */
      llassert (fileId_isValid (fid));

      pre = cstring_makeLiteralTemp ("cl");
      suf = C_EXTENSION;

      if (fileId_isValid (ft->elements[fid]->fder))
      {
        fid = ft->elements[fid]->fder;
      }
      break;

    default:
      BADBRANCH;
      return fileId_invalid;
  }

  newname = makeTempName (pre, suf);
  res = fileTable_addFilePrim (ft, newname, TRUE, ftype, fid);
  DPRINTF (("Added file: %s", fileTable_getName (ft, res)));
  cstring_free (newname);
  return res;
}

void
fileTable_addStreamFile (fileTable ft, FILE *fstream, cstring name)
{
  fileTable_addOpen (ft, fstream, cstring_copy (name));
}

bool
fileTable_isHeader (fileTable ft, fileId fid)
{
  if (fileId_isInvalid (fid))
    {
      return FALSE;
    }

  llassert (fileTable_isDefined (ft) && fileTable_inRange (ft, fid));
  return (ft->elements[fid]->ftype == FILE_HEADER);
}

bool
fileTable_isSystemFile (fileTable ft, fileId fid)
{
  if (fileId_isInvalid (fid))
    {
      return FALSE;
    }

  llassert (fileTable_isDefined (ft) && fileTable_inRange (ft, fid));
  return (ft->elements[fid]->fsystem);
}

bool
fileTable_isXHFile (fileTable ft, fileId fid)
{
  if (fileId_isInvalid (fid))
    {
      return FALSE;
    }

  if (!(fileTable_isDefined (ft) && fileTable_inRange (ft, fid)))
    {
      llcontbug (message ("Bad file table or id: %s %d", bool_unparse (fileTable_isDefined (ft)), fid));
      return FALSE;
    }
  else
    {
      return (ft->elements[fid]->ftype == FILE_XH);
    }
}

bool
fileTable_isSpecialFile (fileTable ft, fileId fid)
{
  if (fileId_isInvalid (fid))
    {
      return FALSE;
    }
  
  llassert (fileTable_isDefined (ft) && fileTable_inRange (ft, fid));
  return (ft->elements[fid]->fspecial);
}

bool
fileTable_exists (fileTable ft, cstring s)
{
  int tindex = fileTable_getIndex (ft, s);

  if (tindex == NOT_FOUND)
    {
      DPRINTF (("Not found: %s", s));
      return FALSE;
    }
  else
    {
      return TRUE;
    }
}

fileId
fileTable_lookup (fileTable ft, cstring s)
{
  int tindex = fileTable_getIndex (ft, s);

  if (tindex == NOT_FOUND)
    {
      return fileId_invalid;
    }
  else
    {
      return tindex;
    }
}

fileId
fileTable_lookupBase (fileTable ft, cstring base)
{
  int tindex;

  if (context_getFlag (FLG_CASEINSENSITIVEFILENAMES))
    {
      cstring dbase = cstring_downcase (base);
      tindex = fileTable_getIndex (ft, dbase);
      cstring_free (dbase);
    }
  else
    {
      tindex = fileTable_getIndex (ft, base);
    }

  if (tindex == NOT_FOUND)
    {
      return fileId_invalid;
    }
  else
    {
      fileId der;

      llassert (fileTable_isDefined (ft));

      der = ft->elements[tindex]->fder;
      
      if (!fileId_isValid (der))
	{
	  der = tindex;
	}

      return der; 
    }
}

cstring
fileTable_getName (fileTable ft, fileId fid)
{
  if (!fileId_isValid (fid))
    {
      llcontbug 
	(message ("%s: called with invalid type id: %d", __func__, fid));
      return cstring_makeLiteralTemp ("<invalid>");
    }

  llassert (fileTable_isDefined (ft));
  return (ft->elements[fid]->fname);
}

cstring
fileTable_getRootName (fileTable ft, fileId fid)
{
  fileId fder;

  if (!fileId_isValid (fid))
    {
      llcontbug (message ("%s: called with invalid id: %d", __func__, fid));
      return cstring_makeLiteralTemp ("<invalid>");
    }

  if (!fileTable_isDefined (ft))
    {
      return cstring_makeLiteralTemp ("<no file table>");
    }

  fder = ft->elements[fid]->fder;

  if (fileId_isValid (fder))
    {
      return (ft->elements[fder]->fname);
    }
  else
    {
      return (ft->elements[fid]->fname);
    }
}

cstring
fileTable_getNameBase (fileTable ft, fileId fid)
{
  if (!fileId_isValid (fid))
    {
      llcontbug (message ("%s: called with invalid id: %d", __func__, fid));
      return cstring_makeLiteralTemp ("<invalid>");
    }
  
  if (!fileTable_isDefined (ft))
    {
      return cstring_makeLiteralTemp ("<no file table>");
    }
  
  return (ft->elements[fid]->basename);
}

bool
fileTable_sameBase (fileTable ft, fileId f1, fileId f2)
{
  fileId fd1, fd2;

  if (!fileId_isValid (f1))
    {
      return FALSE;
    }

  if (!fileId_isValid (f2))
    {
      return FALSE;
    }

  llassert (fileTable_isDefined (ft));

  if (f1 == f2) 
    {
      return TRUE;
    }

  fd1 = ft->elements[f1]->fder;

  if (!fileId_isValid (fd1))
    {
      fd1 = f1;
    }

  fd2 = ft->elements[f2]->fder;


  if (!fileId_isValid (fd2))
    {
      fd2 = f2;
    }

  return (fd1 == fd2);
}

void
fileTable_cleanup (fileTable ft)
{
  int i;
  bool msg;
  int skip;
  
  llassert (fileTable_isDefined (ft));

  msg = ((ft->nentries > 40) && context_getFlag (FLG_SHOWSCAN));
  skip = ft->nentries / 10;

  if (msg)
    {
      (void) fflush (g_warningstream);
      displayScanOpen (cstring_makeLiteral ("cleaning"));
    }

  for (i = 0; i < ft->nentries; i++)
    {
      ftentry fe = ft->elements[i];

      if (fe->ftemp)
	{
	  /* let's be real careful now, hon! */
	  
          switch (fe->ftype) {
            case FILE_NODELETE:
              break; /* nothing to do */

            case FILE_LSLTEMP:
              break; /* already removed */

            case FILE_MACROS:
              goto delete;

            default:
              /*
              ** Make sure it is really a derived file
              */
              if (fileId_isValid (fe->fder))
                {
                delete:
                  /* this should use close (fd) also... */
                  (void) osd_unlink (fe->fname);
                }
              else
                {
                  llbug (message ("Temporary file is not derivative: %s "
                        "(not deleted)", fe->fname));
                }
          }
	}
      else
	{
	  ;
	}

      if (msg && ((i % skip) == 0))
	{
	  displayScanContinue (cstring_makeLiteral (i == 0 ? " " : "."));
	}
    }

  if (msg)
    {
      displayScanClose ();
    }
}

void
fileTable_free (/*@only@*/ fileTable f)
{
  int i = 0;
  
  if (f == (fileTable)NULL) 
    {
      return;
    }

  while ( i < f->nentries ) 
    {
      ftentry_free (f->elements[i]);
      i++;
    }
  
  cstringTable_free (f->htable);
  sfree (f->elements);
  sfree (f->openelements); /*!! why didn't splint report this? */
  sfree (f);
}

/*
** unique temp filename are constructed from <dir><pre><pid><msg>.<suf>
** requires: <dir> must end in '/'
*/

static void nextMsg (char *msg)
{
  /*@+charint@*/
  if (msg[0] < 'Z') 
    {
      msg[0]++; 
    }
  else 
    {
      msg[0] = 'A';
      if (msg[1] < 'Z')
	{ 
	  msg[1]++; 
	}
      else
	{
	  msg[1] = 'A';
	  if (msg[2] < 'Z') 
	    {
	      msg[2]++;
	    }
	  else
	    {
	      msg[2] = 'A';
	      if (msg[3] < 'Z') 
		{
		  msg[3]++; 
		}
	      else
		{
		  llassertprint (FALSE, ("nextMsg: out of unique names!!!"));
		}
	    }
	}
    }
  /*@-charint@*/
}

static /*@only@*/ cstring makeTempName (cstring pre, cstring suf)
{
  static /*@owned@*/ char *msg = NULL; 
  static /*@only@*/ cstring pidname = cstring_undefined;
  static cstring dir = cstring_undefined;
  cstring smsg;

  llassert (cstring_length (pre) <= 3);

  /*
  ** We limit the temp name to 8 characters:
  **   pre: 3 or less
  **   msg: 3
  **   pid: 2  (% 100)
  */

  if (msg == NULL)
    {
      msg = mstring_copy ("AAA"); /* there are 26^3 temp names */
    }

  if (cstring_isUndefined (pidname)) 
    {
      pidname = message ("%d", osd_getPid () % 100);
    }

  if (cstring_isUndefined (dir))
    {
      dir = context_tmpdir ();
    }
  
  DPRINTF (("Dir: %s / %s / %s / %s / %s", dir, pre, pidname, msg, suf));

  smsg = message ("%s%s%s%s%s", dir, pre, pidname, cstring_fromChars (msg), suf);
  nextMsg (msg);

  DPRINTF (("Trying: %s", smsg));

  while (osd_fileExists (smsg))
    {
      cstring_free (smsg);
      smsg = message ("%s%s%s%s%s", dir, pre, pidname, cstring_fromChars (msg), suf);
      nextMsg (msg);
    }

  return smsg;
}

static foentry
foentry_create (/*@exposed@*/ FILE *f, /*@only@*/ cstring fname)
{
  foentry t = (foentry) dmalloc (sizeof (*t));
  t->f = f;
  t->fname = fname;
  return t;
}

static void 
foentry_free (/*@only@*/ foentry foe)
{
  cstring_free (foe->fname);
  sfree (foe);
}

static void 
fileTable_addOpen (fileTable ft, /*@observer@*/ FILE *f, /*@only@*/ cstring fname)
{
  llassert (fileTable_isDefined (ft));

  if (ft->nopenspace <= 0) 
    {
      fileTable_growOpen (ft);
    }

  ft->nopenspace--;
  ft->openelements[ft->nopen] = foentry_create (f, fname);
  ft->nopen++;
}

FILE *fileTable_createTempFile (fileTable ft, cstring fname, bool read)
{
  FILE *res = osd_createTmpFile(fname, read);

  if (res != NULL) 
    {
      fileTable_addOpen (ft, res, cstring_copy (fname));
      DPRINTF (("Opening file: %s / %p", fname, res));
    }
  else
    {
      DPRINTF (("Error opening: %s", fname));
    }

  return res;
}

FILE *fileTable_openReadFile (fileTable ft, cstring fname)
{
  FILE *res = fopen (cstring_toCharsSafe (fname), "r");

  if (res != NULL) 
    {
      fileTable_addOpen (ft, res, cstring_copy (fname));
      DPRINTF (("Opening read file: %s / %p", fname, res));
    }
  else
    {
      DPRINTF (("Cannot open read file: %s", fname));
    }

  return res;
}

/*
** Allows overwriting
*/

FILE *fileTable_openWriteFile (fileTable ft, cstring fname)
{
  FILE *res = fopen (cstring_toCharsSafe (fname), "w");

  if (res != NULL) {
    fileTable_addOpen (ft, res, cstring_copy (fname));
    DPRINTF (("Opening file: %s / %p", fname, res));
  }

  return res;
}

FILE *fileTable_openWriteUpdateFile (fileTable ft, cstring fname)
{
  FILE *res = fopen (cstring_toCharsSafe (fname), "w+");

  if (res != NULL) {
    fileTable_addOpen (ft, res, cstring_copy (fname));
    DPRINTF (("Opening file: %s / %p", fname, res));
  }

  return res;
}

bool fileTable_closeFile (fileTable ft, FILE *f)
{
  bool foundit = FALSE;
  int i = 0;

  llassert (fileTable_isDefined (ft));

  DPRINTF (("Closing file: %p", f));

  for (i = 0; i < ft->nopen; i++) 
    {
      if (ft->openelements[i]->f == f)
	{
	  DPRINTF (("Closing file: %p = %s", f, ft->openelements[i]->fname));
	  
	  if (i == ft->nopen - 1)
	    {
	      foentry_free (ft->openelements[i]);
	      ft->openelements[i] = NULL;
	    }
	  else
	    {
	      foentry_free (ft->openelements[i]);
	      ft->openelements[i] = ft->openelements[ft->nopen - 1];
	      ft->openelements[ft->nopen - 1] = NULL;
	    }

	  ft->nopen--;
	  ft->nopenspace++;
	  foundit = TRUE;
	  break;
	}
    }
  
  llassert (foundit);
  return (fclose (f) == 0);
}

void fileTable_closeAll (fileTable ft)
{
  int i = 0;

  llassert (fileTable_isDefined (ft));

  for (i = 0; i < ft->nopen; i++) 
    {
      /* 
	 lldiagmsg (message ("Unclosed file at exit: %s", ft->openelements[i]->fname)); 
      */
      
      if (ft->openelements[i]->f != NULL)
	{
	  (void) fclose (ft->openelements[i]->f); /* No check - cleaning up after errors */
	}

      ft->openelements[i]->f = NULL;
      foentry_free (ft->openelements[i]);
      ft->openelements[i] = NULL;
    }
  
  ft->nopenspace += ft->nopen;
  ft->nopen = 0;
}

