/*
 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
 * 
 * The contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License. Please obtain
 * a copy of the License at http://www.apple.com/publicsource and read it before
 * using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 * specific language governing rights and limitations under the License.
 */


// file: .../c++-lib/src/tcl-if.C
//
// $Header: /cvs/Darwin/src/live/Security/SecuritySNACCRuntime/c++-lib/c++/tcl-if.cpp,v 1.1.1.1 2001/05/18 23:14:06 mb Exp $
// $Log: tcl-if.cpp,v $
// Revision 1.1.1.1  2001/05/18 23:14:06  mb
// Move from private repository to open source repository
//
// Revision 1.2  2001/05/05 00:59:17  rmurphy
// Adding darwin license headers
//
// Revision 1.1  2000/06/15 18:44:58  dmitch
// These snacc-generated source files are now checked in to allow cross-platform build.
//
// Revision 1.2  2000/06/08 20:05:37  dmitch
// Mods for X port. These files are actually machine generated and probably don't need to be in CVS....
//
// Revision 1.1.1.1  2000/03/09 01:00:06  rmurphy
// Base Fortissimo Tree
//
// Revision 1.2  1999/02/26 00:23:41  mb
// Fixed for Mac OS 8
//
// Revision 1.1  1999/02/25 05:21:57  mb
// Added snacc c++ library
//
// Revision 1.6  1997/02/28 13:39:47  wan
// Modifications collected for new version 1.3: Bug fixes, tk4.2.
//
// Revision 1.5  1997/01/01 23:24:35  rj
// `typename' appears to be a reserved word in gcc 2.7, so prefix it with `_'
//
// Revision 1.4  1995/09/07  18:57:13  rj
// duplicate code merged into a new function SnaccTcl::gettypedesc().
//
// Revision 1.3  1995/08/17  15:09:09  rj
// snacced.[hC] renamed to tcl-if.[hC].
// class SnaccEd renamed to SnaccTcl.
// set Tcl's errorCode variable.
//
// Revision 1.2  1995/07/27  09:53:38  rj
// comment leader fixed
//
// Revision 1.1  1995/07/27  09:52:22  rj
// new file: tcl interface used by snacced.

#if !defined(macintosh) && !defined(__APPLE__)
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <strstream.h>
#include <fstream.h>
#include <string.h>

#include "asn-incl.h"

#if TCL

#ifdef _AIX32
extern "C" int strncasecmp (const char* s1, const char* s2, size_t number);
extern "C" int strcasecmp (const char* s1, const char* s2);
#endif

#include "tcl-if.h"
#include "init.h"

//\[banner "utility functions"]-----------------------------------------------------------------------------------------------------
static bool strniabbr (const char *pattern, const char *test, size_t min)
{
  register      len;

  if (strlen (pattern)<min)
    fprintf (stderr, "strniabbr(): strlen (pattern) < min\n");
  if ((len = strlen (test))<min)
    return false;
  return !strncasecmp (pattern, test, len);
}

//\[banner "ctor & dtor"]-----------------------------------------------------------------------------------------------------------
ASN1File::ASN1File (const AsnTypeDesc *typedesc)
{
  type = typedesc;
  pdu = type->create();
  fn = NULL;
  fd = -1;
  filesize = 0;
}

ASN1File::ASN1File (const AsnTypeDesc *typedesc, const char *_fn, int _fd)
{
  type = typedesc;
  pdu = type->create();

  int fnlen = strlen (_fn) + 1;
  fn = new char [fnlen];
  memcpy (fn, _fn, fnlen);

  fd = _fd;
}

ASN1File::~ASN1File()
{
  delete pdu;
  delete fn;
  if (fd >= 0)
    close (fd);
}

bool ASN1File::bad()
{
  return fd < 0;
}

int ASN1File::finfo (Tcl_Interp *interp)
{
  Tcl_AppendElement (interp, fn ? fn : "");
  char *acc = "bad";
  if (!bad())
  {
    int flags;
    if ((flags = fcntl (fd, F_GETFL)) != -1)
      switch (flags & O_ACCMODE)
      {
	case O_RDONLY:
	  acc = "ro";
	  break;
	case O_WRONLY:
	  acc = "wo";
	  break;
	case O_RDWR:
	  acc = "rw";
	  break;
      }
  }
  Tcl_AppendElement (interp, acc);

  return TCL_OK;
}

int ASN1File::read (Tcl_Interp *interp, const char *rfn)
{
  int rfd;
  TmpFD tmpfd;

  delete pdu;
  pdu = type->create();

  if (rfn)
  {
    if ((rfd = open (rfn, O_RDONLY)) < 0)
    {
      Tcl_AppendResult (interp, "can't open \"", rfn, "\": ", Tcl_PosixError (interp), NULL);
      return TCL_ERROR;
    }
    tmpfd = rfd;
  }
  else if (fd < 0)
  {
    Tcl_AppendResult (interp, "can't read, file is not open", NULL);
    Tcl_SetErrorCode (interp, "SNACC", "MUSTOPEN", NULL);
    return TCL_ERROR;
  }
  else
  {
    rfn = fn;
    lseek (rfd = fd, 0l, SEEK_SET);
  }

  struct stat statbuf;
  if (fstat (rfd, &statbuf))
  {
    Tcl_AppendResult (interp, "can't fstat \"", rfn, "\": ", Tcl_PosixError (interp), NULL);
    return TCL_ERROR;
  }

  filesize = statbuf.st_size;

  char* buf = new char[filesize];
  if (::read (rfd, buf, filesize) != filesize)
  {
    Tcl_AppendResult (interp, "can't read \"", rfn, "\": ", Tcl_PosixError (interp), NULL);
    delete buf;
    return TCL_ERROR;
  }

  AsnBuf inputBuf;
  inputBuf.InstallData (buf, filesize);

  size_t decodedLen = 0;
  jmp_buf env;
  int eval;
  if (eval = setjmp (env))
  {
    char eno[80];
    sprintf (eno, "%d", eval);
    Tcl_AppendResult (interp, "can't decode (error ", eno, ")", NULL);
    Tcl_SetErrorCode (interp, "SNACC", "DECODE", eno, NULL);
    delete buf;
    return TCL_ERROR;
  }
  pdu->BDec (inputBuf, decodedLen, env);
  if (inputBuf.ReadError())
  {
    Tcl_AppendResult (interp, "can't decode, out of data", NULL);
    Tcl_SetErrorCode (interp, "SNACC", "DECODE", "EOBUF", NULL);
    delete buf;
    return TCL_ERROR;
  }

#if DEBUG
cout << "DECODED:" << endl << *pdu << endl;
#endif

  if (decodedLen != filesize)
    sprintf (interp->result, "decoded %d of %d bytes", decodedLen, filesize);

  delete buf;
  return TCL_OK;
}

int ASN1File::write (Tcl_Interp *interp, const char *wfn)
{
  int wfd;
  TmpFD tmpfd;

  if (wfn)
  {
    if ((wfd = open (wfn, O_CREAT|O_TRUNC|O_WRONLY, 0666)) < 0)
    {
      Tcl_AppendResult (interp, "can't open \"", wfn, "\": ", Tcl_PosixError (interp), NULL);
      return TCL_ERROR;
    }
    tmpfd = wfd;
  }
  else if (fd < 0)
  {
    Tcl_AppendResult (interp, "can't write, file is not open", NULL);
    Tcl_SetErrorCode (interp, "SNACC", "MUSTOPEN", NULL);
    return TCL_ERROR;
  }
  else
  {
    wfn = fn;
    int flags;
    if ((flags = fcntl (fd, F_GETFL)) == -1)
    {
      Tcl_AppendResult (interp, "can't fcntl \"", wfn, "\": ", Tcl_PosixError (interp), NULL);
      return TCL_ERROR;
    }
    else
    {
      if ((flags & O_ACCMODE) == O_RDONLY)
      {
	Tcl_AppendResult (interp, "can't write, file is read only", NULL);
	Tcl_SetErrorCode (interp, "SNACC", "WRITE", "RDONLY", NULL);
	return TCL_ERROR;
      }
    }
    lseek (wfd = fd, 0l, SEEK_SET);
  }

  size_t size = filesize ? filesize : 10240;
  char *buf;
  AsnBuf outputBuf;
  size_t encodedLen;
  for (;;)
  {
    size <<= 1;
    buf = new char[size];
    outputBuf.Init (buf, size);
    outputBuf.ResetInWriteRvsMode();
    encodedLen = pdu->BEnc (outputBuf);
    if (!outputBuf.WriteError())
      break;
    delete buf;
  }

  outputBuf.ResetInReadMode();
  size_t hunklen = 8192;
  char* hunk = new char[hunklen];
  for (size_t written=0; written<encodedLen; written+=hunklen)
  {
    if (encodedLen-written < hunklen)
      hunklen = encodedLen - written;
    outputBuf.CopyOut (hunk, hunklen);
    if (::write (wfd, hunk, hunklen) != hunklen)
    {
      Tcl_AppendResult (interp, "write error on \"", wfn, "\": ", Tcl_PosixError (interp), NULL);
      delete hunk; // may affect errno
      delete buf; // may affect errno
      return TCL_ERROR;
    }
  }

  delete hunk;
  delete buf;

  filesize = encodedLen;
  if (!wfn)
    ftruncate (wfd, filesize);

  return TCL_OK;
}

//\[banner "import & export"]-------------------------------------------------------------------------------------------------------
int import (Tcl_Interp *interp, int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc import filename\"");
    return TCL_ERROR;
  }

  const char *fn = argv[1];
  int fd;
  if ((fd = open (fn, O_RDONLY)) < 0)
  {
    Tcl_AppendResult (interp, "can't open \"", fn, "\": ", Tcl_PosixError (interp), NULL);
    return TCL_ERROR;
  }
  TmpFD tmpfd (fd);

  struct stat statbuf;
  if (fstat (fd, &statbuf))
  {
    Tcl_AppendResult (interp, "can't fstat \"", fn, "\"'s fd: ", Tcl_PosixError (interp), NULL);
    return TCL_ERROR;
  }

  off_t filesize = statbuf.st_size;

  char* ibuf = new char[filesize];
  if (::read (fd, ibuf, filesize) != filesize)
  {
    Tcl_AppendResult (interp, "read error on \"", fn, "\": ", Tcl_PosixError (interp), NULL);
    delete ibuf;
    return TCL_ERROR;
  }

  int result = debinify (interp, ibuf, filesize);
  delete ibuf;
  return result;
}

int export (Tcl_Interp *interp, int argc, char **argv)
{
  if (argc != 3)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc export str filename\"");
    return TCL_ERROR;
  }

  const char *str = argv[1], *fn = argv[2];
  char* obuf = new char[strlen (str)]; // the binary buffer is as most as long as the escaped Tcl string.
  size_t olen;
  if (binify (interp, str, obuf, &olen) != TCL_OK)
  {
    delete obuf;
    return TCL_ERROR;
  }

  int fd;
  if ((fd = open (fn, O_CREAT|O_TRUNC|O_WRONLY, 0666)) < 0)
  {
    Tcl_AppendResult (interp, "can't open \"", fn, "\": ", Tcl_PosixError (interp), NULL);
    delete obuf;
    return TCL_ERROR;
  }
  TmpFD tmpfd (fd);

  if (::write (fd, obuf, olen) != olen)
  {
    Tcl_AppendResult (interp, "write error on \"", fn, "\": ", Tcl_PosixError (interp), NULL);
    delete obuf;
    return TCL_ERROR;
  }

  delete obuf;
  return TCL_OK;
}

//\[banner "ctor & dtor"]-----------------------------------------------------------------------------------------------------------
SnaccTcl::SnaccTcl (Tcl_Interp *i)
{
  interp = i;

  Tcl_InitHashTable (&modules, TCL_STRING_KEYS);
  Tcl_InitHashTable (&types, TCL_STRING_KEYS);

  const AsnModuleDesc **moddesc;
  for (moddesc=asnModuleDescs; *moddesc; moddesc++)
  {
    int created;
    Tcl_HashEntry *entry = Tcl_CreateHashEntry (&modules, (char*)(*moddesc)->name, &created);
    assert (created);
    Tcl_SetHashValue (entry, *moddesc);

    const AsnTypeDesc **typedesc;
    for (typedesc=(*moddesc)->types; *typedesc; typedesc++)
    {
      char buf[1024];
      sprintf (buf, "%s %s", (*moddesc)->name, (*typedesc)->name);
      char *_typename = strdup (buf);
      int created;
      Tcl_HashEntry *entry = Tcl_CreateHashEntry (&types, _typename, &created);
      if (!created)
      {
	cerr << "fatal error: duplicate type " << _typename << endl;
	exit (1);
      }
      Tcl_SetHashValue (entry, *typedesc);
    }
  }

  Tcl_InitHashTable (&files, TCL_STRING_KEYS);
}

SnaccTcl::~SnaccTcl()
{
  Tcl_DeleteHashTable (&files);
}

//\[banner "utility functions"]-----------------------------------------------------------------------------------------------------
const AsnTypeDesc *SnaccTcl::gettypedesc (const char *cmdname, const char *_typename)
{
  Tcl_HashEntry *typedescentry;
  if (typedescentry = Tcl_FindHashEntry (&types, (char*)_typename))
    return (const AsnTypeDesc *)Tcl_GetHashValue (typedescentry);
  else
  {
    Tcl_SetErrorCode (interp, "SNACC", "ILLTYPE", NULL);
    Tcl_AppendResult (interp, "snacc ", cmdname, ": no type \"", _typename, "\"", NULL);
    return NULL;
  }
}

//\[banner "data manipulation functions"]-------------------------------------------------------------------------------------------
Tcl_HashEntry *SnaccTcl::create()
{
  static unsigned int id;
  int created;
  Tcl_HashEntry *entry;
  do
  {
    sprintf (interp->result, "file%u", id++);
    entry = Tcl_CreateHashEntry (&files, interp->result, &created);
  }
  while (!created);
  return entry;
}

int SnaccTcl::create (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc create {module type}\"");
    return TCL_ERROR;
  }

  const char	*_typename = argv[1];
  const AsnTypeDesc *typedesc;
  if (!(typedesc = gettypedesc ("type", _typename)))
    return TCL_ERROR;

  Tcl_HashEntry *entry = create();
  ASN1File *file = new ASN1File (typedesc);
  Tcl_SetHashValue (entry, file);

  return TCL_OK;
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
// snacc open {module type} filename ?flags? ?permissions?

int SnaccTcl::openfile (int argc, char **argv)
{
  if (argc < 3 || argc > 5)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc open {module type} filename ?flags? ?permissions?\"");
    return TCL_ERROR;
  }

  const char	*_typename = argv[1];
  const char	*filename = argv[2];
  bool		rw_spec = false;
  int		oflags = 0, omode = 0666, fd = -1;

  switch (argc)
  {
    case 5:
      if (Tcl_GetInt (interp, argv[4], &omode))
	return TCL_ERROR;
      // \(da fall thru
    case 4:
      {
	Args flags;
	if (Tcl_SplitList (interp, argv[3], &flags.c, &flags.v) != TCL_OK)
	  return TCL_ERROR;

	for (int i=0; i<flags.c; i++)
	{
	  if (strniabbr ("truncate", flags.v[i], 1))
	    oflags |= O_TRUNC;
	  else if (strniabbr ("create", flags.v[i], 1))
	    oflags |= O_CREAT;
	  else if (!strcasecmp ("ro", flags.v[i]))
	  {
	    oflags |= O_RDONLY;
	    rw_spec = true;
	  }
	  else if (!strcasecmp ("rw", flags.v[i]))
	  {
	    oflags |= O_RDWR;
	    rw_spec = true;
	  }
	  else
	  {
	    Tcl_AppendResult (interp, "snacc open: illegal argument \"", flags.v[i], "\" in flags", NULL);
	    return TCL_ERROR;
	  }
	}
      }
      break;
  }

  const AsnTypeDesc *typedesc;
  if (!(typedesc = gettypedesc ("open", _typename)))
    return TCL_ERROR;

  if (rw_spec)
    fd = open (filename, oflags, omode);
  else
    if ((fd = open (filename, oflags | O_RDWR, omode)) < 0)
      fd = open (filename, oflags | O_RDONLY, omode);

  if (fd < 0)
  {
    Tcl_AppendResult (interp, "can't open \"", filename, "\": ", Tcl_PosixError (interp), NULL);
    return TCL_ERROR;
  }

  ASN1File *file = new ASN1File (typedesc, filename, fd);
  if (file->bad())
  {
    delete file;
    Tcl_AppendResult (interp, "internal error on \"", filename, "\": bad status", NULL);
    Tcl_SetErrorCode (interp, "SNACC", "OPEN", "BAD", NULL);
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = create();
  Tcl_SetHashValue (entry, file);

  return file->read (interp);
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::finfo (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc finfo file\"");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, argv[1]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "no file named \"", argv[1], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  return file->finfo (interp);
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
// snacc read file ?{module type} filename?

int SnaccTcl::read (int argc, char **argv)
{
  const char	*_typename, *filename;

  switch (argc)
  {
    case 2: // reread from old fd
      _typename = filename = NULL;
      break;
    case 4:
      _typename = argv[2];
      filename = argv[3];
      break;
    default:
      strcpy (interp->result, "wrong # args: should be \"snacc read file ?{module type} filename?\"");
      return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, argv[1]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "no file named \"", argv[1], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  if (_typename)
  {
    const AsnTypeDesc *typedesc;
    if (!(typedesc = gettypedesc ("read", _typename)))
      return TCL_ERROR;

    delete file;
    file = new ASN1File (typedesc);
    Tcl_SetHashValue (entry, file);
  }

  return file->read (interp, filename);
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::write (int argc, char **argv)
{
  if (argc < 2 || argc > 3)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc write file ?filename?\"");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, argv[1]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "no file named \"", argv[1], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  return file->write (interp, argv[2]);
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::closefile (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc close file\"");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, argv[1]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "no file named \"", argv[1], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);
  delete file;

  Tcl_DeleteHashEntry (entry);

  return TCL_OK;
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::modulesinfo (int argc, char **argv)
{
  if (argc != 1)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc modules\"");
    return TCL_ERROR;
  }

  Tcl_HashEntry *moduleentry;
  Tcl_HashSearch hi;
  for (moduleentry=Tcl_FirstHashEntry (&modules, &hi); moduleentry; moduleentry=Tcl_NextHashEntry (&hi))
    Tcl_AppendElement (interp, Tcl_GetHashKey (&modules, moduleentry));

  return TCL_OK;
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::typesinfo (int argc, char **argv)
{
  switch (argc)
  {
    case 1:
      Tcl_HashEntry *typeentry;
      Tcl_HashSearch hi;
      for (typeentry=Tcl_FirstHashEntry (&types, &hi); typeentry; typeentry=Tcl_NextHashEntry (&hi))
	Tcl_AppendElement (interp, Tcl_GetHashKey (&types, typeentry));
      return TCL_OK;
    case 2:
      Tcl_HashEntry *moduleentry;
      if (moduleentry = Tcl_FindHashEntry (&modules, argv[1]))
      {
	const AsnModuleDesc *moddesc = (const AsnModuleDesc *)Tcl_GetHashValue (moduleentry);
	const AsnTypeDesc **typedesc;
	for (typedesc=moddesc->types; *typedesc; typedesc++)
	  Tcl_AppendElement (interp, (char*)(*typedesc)->name);
	return TCL_OK;
      }
      else
      {
	Tcl_AppendResult (interp, "snacc types: no module \"", argv[1], "\"", NULL);
	return TCL_ERROR;
      }
    default:
      strcpy (interp->result, "wrong # args: should be \"snacc types ?module?\"");
      return TCL_ERROR;
  }
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::typeinfo (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc type {module type}\"");
    return TCL_ERROR;
  }

  const char	*_typename = argv[1];
  const AsnTypeDesc *typedesc;
  if (!(typedesc = gettypedesc ("type", _typename)))
    return TCL_ERROR;

  Tcl_DString desc;
  Tcl_DStringInit (&desc);
  int rc = typedesc->TclGetDesc (&desc);
  Tcl_DStringResult (interp, &desc);
  return rc;
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::info (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc info path\"");
    return TCL_ERROR;
  }

  Args path;
  if (Tcl_SplitList (interp, argv[1], &path.c, &path.v) != TCL_OK)
    return TCL_ERROR;

  if (path.c < 1)
  {
    strcpy (interp->result, "snacc info: wrong # args in path");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, path.v[0]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "snacc info: no file named \"", path.v[0], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  AsnType *var = (AsnType *)*file;
  for (int i=1; i<path.c; i++)
    if (!(var = var->_getref (path.v[i])))
    {
      Tcl_AppendResult (interp, "snacc info: illegal component \"", path.v[i], "\" in path", NULL);
      return TCL_ERROR;
    }

  Tcl_DString desc;
  Tcl_DStringInit (&desc);
  int rc;
  if ((rc = var->_getdesc()->AsnTypeDesc::TclGetDesc (&desc)) == TCL_OK)
    rc = var->TclGetDesc (&desc);
  Tcl_DStringResult (interp, &desc);
  return rc;
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::getval (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc get path\"");
    return TCL_ERROR;
  }

  Args path;
  if (Tcl_SplitList (interp, argv[1], &path.c, &path.v) != TCL_OK)
    return TCL_ERROR;

  if (path.c < 1)
  {
    strcpy (interp->result, "snacc get: wrong # args in path");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, path.v[0]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "snacc get: no file named \"", path.v[0], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  AsnType *var = (AsnType *)*file;
  for (int i=1; i<path.c; i++)
    if (!(var = var->_getref (path.v[i])))
    {
      Tcl_AppendResult (interp, "snacc get: illegal component \"", path.v[i], "\" in path", NULL);
      return TCL_ERROR;
    }

  return var->TclGetVal (interp);
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::test (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc get path\"");
    return TCL_ERROR;
  }

  Args path;
  if (Tcl_SplitList (interp, argv[1], &path.c, &path.v) != TCL_OK)
    return TCL_ERROR;

  if (path.c < 1)
  {
    strcpy (interp->result, "snacc get: wrong # args in path");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, path.v[0]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "snacc get: no file named \"", path.v[0], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  AsnType *var = (AsnType *)*file;
  for (int i=1; i<path.c; i++)
    if (!(var = var->_getref (path.v[i])))
    {
      Tcl_AppendResult (interp, "snacc test: illegal component \"", path.v[i], "\" in path", NULL);
      return TCL_ERROR;
    }

cout << *var;
  strstream s;
  s << *var;
  s.put ('\0');
  cout << strlen(s.str()) << endl;
  cout << s.str() << endl;

  return TCL_OK;
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::setval (int argc, char **argv)
{
  if (argc != 3)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc set path value\"");
    return TCL_ERROR;
  }

  Args path;
  if (Tcl_SplitList (interp, argv[1], &path.c, &path.v) != TCL_OK)
    return TCL_ERROR;

  if (path.c < 1)
  {
    strcpy (interp->result, "snacc set: wrong # args in path");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, path.v[0]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "snacc set: no file named \"", path.v[0], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  AsnType *var = (AsnType *)*file;
  for (int i=1; i<path.c; i++)
    if (!(var = var->_getref (path.v[i], true)))
    {
      Tcl_AppendResult (interp, "snacc set: illegal component \"", path.v[i], "\" in path", NULL);
      return TCL_ERROR;
    }

  return var->TclSetVal (interp, argv[2]);
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int SnaccTcl::unsetval (int argc, char **argv)
{
  if (argc != 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc unset path\"");
    return TCL_ERROR;
  }

  Args path;
  if (Tcl_SplitList (interp, argv[1], &path.c, &path.v) != TCL_OK)
    return TCL_ERROR;

  if (path.c == 1)
  {
    strcpy (interp->result, "snacc unset: sorry, but you are not allowed to unset the file itself");
    return TCL_ERROR;
  }
  else if (path.c < 1)
  {
    strcpy (interp->result, "snacc unset: wrong # args in path");
    return TCL_ERROR;
  }

  Tcl_HashEntry *entry = Tcl_FindHashEntry (&files, path.v[0]);
  if (!entry)
  {
    Tcl_AppendResult (interp, "snacc unset: no file named \"", path.v[0], "\"", NULL);
    return TCL_ERROR;
  }

  ASN1File *file = (ASN1File *)Tcl_GetHashValue (entry);

  AsnType *var = (AsnType *)*file;
  for (int i=1; i<path.c-1; i++)
  {
    if (!(var = var->_getref (path.v[i])))
    {
      Tcl_AppendResult (interp, "snacc unset: illegal component \"", path.v[i], "\" in path", NULL);
      return TCL_ERROR;
    }
  }

  return var->TclUnsetVal (interp, path.v[path.c-1]);
}

//\[sep]----------------------------------------------------------------------------------------------------------------------------
int Snacc_Cmd (ClientData cd, Tcl_Interp *interp, int argc, char **argv)
{
  SnaccTcl *ed = (SnaccTcl *)cd;

#ifdef DEBUG
  ed->ckip (interp);
#endif

  if (argc < 2)
  {
    strcpy (interp->result, "wrong # args: should be \"snacc option arg ?arg ...?\"");
    return TCL_ERROR;
  }
  --argc;
  argv++;

  switch (**argv)
  {
    case 'c':
      if (!strcmp (*argv, "close"))
	return ed->closefile (argc, argv);
      else if (!strcmp (*argv, "create"))
	return ed->create (argc, argv);
      break;
    case 'e':
      if (!strcmp (*argv, "export"))
	return export (interp, argc, argv);
      break;
    case 'f':
      if (!strcmp (*argv, "finfo"))
	return ed->finfo (argc, argv);
      break;
    case 'g':
      if (!strcmp (*argv, "get"))
	return ed->getval (argc, argv);
      break;
    case 'i':
      if (!strcmp (*argv, "import"))
	return import (interp, argc, argv);
      else if (!strcmp (*argv, "info"))
	return ed->info (argc, argv);
      break;
    case 'm':
      if (!strcmp (*argv, "modules"))
	return ed->modulesinfo (argc, argv);
      break;
    case 'o':
      if (!strcmp (*argv, "open"))
	return ed->openfile (argc, argv);
      break;
    case 'r':
      if (!strcmp (*argv, "read"))
	return ed->read (argc, argv);
      break;
    case 's':
      if (!strcmp (*argv, "set"))
	return ed->setval (argc, argv);
      break;
    case 't':
      if (!strcmp (*argv, "test"))
	return ed->test (argc, argv);
      else if (!strcmp (*argv, "type"))
	return ed->typeinfo (argc, argv);
      else if (!strcmp (*argv, "types"))
	return ed->typesinfo (argc, argv);
      break;
    case 'u':
      if (!strcmp (*argv, "unset"))
	return ed->unsetval (argc, argv);
      break;
    case 'w':
      if (!strcmp (*argv, "write"))
	return ed->write (argc, argv);
      break;
  }
  sprintf (interp->result, "bad command option %s: should be close, create, export, finfo, get, import, info, modules, open, read, set, type, types, unset or write", *argv);

  return TCL_ERROR;
}

//\[banner "check for proper initialization & finalization"]------------------------------------------------------------------------

struct check
{
  int	i, j;

	check (int);

  bool	bad();
};

static int cki;

check::check (int v)
{
  i = v;
  j = ~i;
}

#define CK	42

bool check::bad()
{
  return i != CK || j != ~CK;
}

check	check (CK);

//\[banner "initialization & finalization"]-----------------------------------------------------------------------------------------
void Snacc_Exit (ClientData data)
{
  delete (SnaccTcl *)data;
}

// prohibit function name mangling to enable tkAppInit.c:Tcl_AppInit() to call this function:
extern "C" int Snacc_Init (Tcl_Interp *interp)
{
  if (check.bad())
  {
    static const char emsg[] = "linkage error, constructors of static variables didn't get called!\n";
    write (2, emsg, sizeof emsg);
    exit (1);
  }

  SnaccTcl *data = new SnaccTcl (interp);
  Tcl_CreateCommand (interp, "snacc", Snacc_Cmd, (ClientData)data, Snacc_Exit);
  return TCL_OK;
}

#endif // TCL
