/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/* $XConsortium: Access.c /main/11 1996/11/01 10:09:29 drk $ */
/************************************<+>*************************************
 ****************************************************************************
 **
 **   File:        Access.c
 **
 **   Project:     Run Time Project File Access
 **
 **   Description: This body of code handles the access routines for the
 **                Display Area.
 **
 **
 **  (c) Copyright 1987-1994, 1996 Hewlett-Packard Company
 **  (c) Copyright 1993, 1994, 1996 International Business Machines Corp.
 **  (c) Copyright 1993, 1994, 1996 Sun Microsystems, Inc.
 **  (c) Copyright 1993, 1994, 1996 Novell, Inc.
 **  (c) Copyright 1996 Digital Equipment Corporation.	
 **  (c) Copyright 1996 FUJITSU LIMITED.	
 **  (c) Copyright 1996 Hitachi.	
 **
 **
 ****************************************************************************
 ************************************<+>*************************************/

/*
 * system includes
 */
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <X11/Xlib.h>
#include <X11/Xresource.h>

#ifdef X_NOT_STDC_ENV
extern int errno;
#endif

/*
 * Canvas Engine includes
 */
#include "CanvasP.h"
#include "CanvasSegP.h"

/*
 * private includes
 */
#include "CanvasError.h"
#include "Access.h"
#include "bufioI.h"
#include "FontAttrI.h"
#include "AccessP.h"
#include "AccessI.h"
#include "AccessSDLP.h"
#include "AccessSDLI.h"
#include "AccessCCDFI.h"
#include "FormatUtilI.h"

#include "SDLI.h"
#include "FormatSDLI.h"

#include "CCDFUtilI.h"
#include "StringFuncsI.h"

#include "Lock.h"              /* Process and App Lock macros */

#ifdef NLS16
#endif

/********    Private Defines      ********/
#define	LIST_INCREMENT	10
#define	BUFF_SIZE	256
/********    End Private Defines  ********/

/********    Private Function Declarations    ********/
static	int	  GetVolumeKeywords (
			_DtHelpVolume     vol, 
			char	***retKeywords);
static	int	  VolumeLoad (
			char        *volFile, 
			_DtHelpVolume  *retVol);
static	int	  VolumeUnload (
			_DtHelpVolume vol);
/********    End Private Function Declarations    ********/

/********    Private Macro Declarations        ********/
/********    End Private Macro Declarations    ********/

/******************************************************************************
*
* Private variables used within this file.
*
*******************************************************************************/
static _DtHelpVolume volChain = NULL;	/* Pointer to the head of the chain */
					/* of all the open volumes. */
static const char *Slash  = "/";
static const char *Period = ".";

/******************************************************************************
 *                             Private Functions
 ******************************************************************************/
/******************************************************************************
 * Function:	CheckVolList (_DtHelpVolume vol, _DtHelpVolume *ret_prev)
 *
 * Parameters:	vol		Specifies the volume to search for.
 *		ret_prev	Returns the volume whose nextVol element
 *				points to 'vol' if non NULL.
 *
 * Returns:	0 if successful, -1 if failure.
 *
 * errno Values:
 *
 * Purpose:	To check for the existence of a volume.
 *
 ******************************************************************************/
static	int
CheckVolList (
    _DtHelpVolume	 vol,
    _DtHelpVolume	*ret_prev )
{
    _DtHelpVolume	myVol;
    _DtHelpVolume	prevVol = NULL;

    _DtHelpProcessLock();
    myVol = volChain;

    while (myVol != NULL && myVol != vol)
      {
	prevVol = myVol;
	myVol = myVol->nextVol;
      }

    if (ret_prev)
	*ret_prev = prevVol;

    if (myVol != vol)
      {
	_DtHelpProcessUnlock();
	return -1;
      }

    _DtHelpProcessUnlock();
    return 0;
}

/******************************************************************************
 * Function:	int VolumeLoad (char *volFile, _DtHelpVolume *retVol);
 *
 * Parameters:	volFile		Specifies the name of the Help Volume file
 *				to load.
 *
 *		retVol		Returns the handle to the loaded volume.
 *
 * Return Value:	Returns 0 if successful,
 *			-1 if an error occurred.
 *
 * errno Values:	CEErrorMalloc
 *			CEErrorIllegalDatabaseFile
 *
 * Purpose:	This function must be called to load a Help Volume file
 *		before any of the information in the volume can be
 *		accessed. 
 *
 ******************************************************************************/
static int 
VolumeLoad (
    char        *volFile, 
    _DtHelpVolume  *retVol)
{
    /* Allocate the volume structure and initialize it. */
    *retVol = (_DtHelpVolume) malloc (sizeof (struct _DtHelpVolumeRec));
    if (*retVol)
      {
        (*retVol)->sdl_flag      = False;
        (*retVol)->volFile       = volFile;
        (*retVol)->keywords      = NULL;
        (*retVol)->keywordTopics = NULL;
        (*retVol)->openCount     = 1;
        (*retVol)->nextVol       = NULL;
        (*retVol)->vols.ccdf_vol = NULL;

	if (_DtHelpCeOpenSdlVolume ((*retVol)) == 0)
	    return 0;
	else if (_DtHelpCeOpenCcdfVolume(*retVol) == 0)
	    return 0;

	/*
	 * Set the global error
	 */
	errno = CEErrorIllegalDatabaseFile;

	/*
	 * error on loading the database.
	 */
	free ((char *) ((*retVol)->volFile));
	free ((char *) (*retVol));
	*retVol = NULL;
      }
    else
	errno = CEErrorMalloc;

    return -1;

} /* End VolumeLoad */

/*******************************************************************************
 * Function:	int VolumeUnload (_DtHelpVolume vol);
 *
 * Parameters:	vol	Specifies the loaded volume.
 *
 * Return Value:	0 if successful, -1 if a failure occurs
 *
 * errno Values:	None
 *
 * Purpose:	When the volume is no longer needed, it should be unloaded
 *		with this call.  Unloading it frees the memory (which means
 *		any handles on the volume become invalid.)
 *
 ******************************************************************************/
static int 
VolumeUnload (
     _DtHelpVolume vol)
{
    char	***topicList;
    
    if (vol != NULL)
      {

	if (vol->sdl_flag == True)
	    _DtHelpCeCloseSdlVolume((_DtHelpVolumeHdl) vol);
	else
	    _DtHelpCeCloseCcdfVolume(vol);

	if (vol->volFile != NULL)
	    free (vol->volFile);

	if (vol->keywords != NULL)
	    _DtHelpCeFreeStringArray (vol->keywords);

	if (vol->keywordTopics != NULL)
	  {
	    for (topicList = vol->keywordTopics;
					*topicList != NULL; topicList++)
		_DtHelpCeFreeStringArray (*topicList);

	    free (vol->keywordTopics);
          }

	free (vol);
      }

    return (0);
}

/*******************************************************************************
 * Function:	int RereadVolume (_DtHelpVolume vol);
 *
 * Parameters:	vol	Specifies the loaded volume.
 *
 * Return Value:	0 if successful, -1 if a failure occurs
 *
 * errno Values:	None
 *
 * Purpose:	When the volume is no longer needed, it should be unloaded
 *		with this call.  Unloading it frees the memory (which means
 *		any handles on the volume become invalid.)
 *
 ******************************************************************************/
static int 
RereadVolume (
     _DtHelpVolume vol)
{
    int            result;
    char	***topicList;
    
    if (vol->keywords != NULL)
      {
        _DtHelpCeFreeStringArray (vol->keywords);
        vol->keywords = NULL;
      }

    if (vol->keywordTopics != NULL)
      {
        for (topicList = vol->keywordTopics; *topicList != NULL; topicList++)
	    _DtHelpCeFreeStringArray (*topicList);

        free (vol->keywordTopics);
	vol->keywordTopics = NULL;
      }

    if (vol->sdl_flag == False)
        result = _DtHelpCeRereadCcdfVolume(vol);
    else
        result = _DtHelpCeRereadSdlVolume(vol);

    return (result);
}

/******************************************************************************
 * Function:	static int GetKeywordTopics (_DtHelpVolume vol, char *keyword,
 *				       char ***topics);
 *
 * Parameters:	vol		Specifies the loaded volume
 *		keyword		Specifies the keyword whose location is desired.
 *		topics		Returns a NULL-terminated string array of the
 *				list of topics which contain the keyword.
 *				This array is NOT owned by the caller and
 *				should only be read or copied.
 *
 * Return Value:	0 if successful, -1 if a failure occurs
 *
 * errno Values:	CEErrorNoKeywordList
 *					Specifies that the volume does not
 *					have a keyword list.
 *			CEErrorIllegalKeyword
 *					Specifies that 'keyword' was not
 *					found.
 *			CEErrorMalloc
 *			CEErrorIllegalDatabaseFile
 *					Specifies that the keyword file is
 *					invalid or corrupt.
 *			CEErrorMissingKeywordsRes
 *					Specifies that the keyword file does
 *					not contain the 'Keywords/keywords'
 *					resource or the resource is NULL
 *
 *
 * Purpose:	Find which topic contains a specified locationId.
 *
 ******************************************************************************/
static int 
GetKeywordTopics (
   _DtHelpVolume     vol, 
   char		  *keyword, 
   char		***retTopics)
{
    char	**keywords;
    char	**nextKey;
    int index;

    _DtHelpProcessLock();
    *retTopics = NULL;

    /* Get the list of keywords. */
    if (GetVolumeKeywords (vol, &keywords) != 0)
      {
	_DtHelpProcessUnlock();
	return -1;
      }

    if (keywords == NULL || vol->keywordTopics == NULL)
      {
	errno = CEErrorNoKeywordList;
	_DtHelpProcessUnlock();
	return -1;
      }

    /* Search the list of keywords for the current one. */
    nextKey = keywords;
    while (*nextKey != NULL && strcmp (*nextKey, keyword))
	nextKey++;

    if (*nextKey == NULL)
      {
	errno = CEErrorIllegalKeyword;
	_DtHelpProcessUnlock();
	return -1;
      }

    index = nextKey - keywords;
    *retTopics = *(vol->keywordTopics + index);

    _DtHelpProcessUnlock();
    return (0);
}

/******************************************************************************
 * Function:	static int GetVolumeKeywords(_DtHelpVolume vol,char ***keywords);
 *
 * Parameters:	vol		Specifies the volume.
 *		keywords	Returns a NULL-terminated string array
 *				containing the sorted list of keywords in the
 *				volume.  This array is NOT owned by the caller
 *				and should only be read or copied.
 *
 * Return Value:	0 if successful, -1 if a failure occurs
 *
 * errno Values:	CEErrorMalloc
 *			CEErrorIllegalDatabaseFile
 *					Specifies that the keyword file is
 *					invalid or corrupt.
 *			CEErrorMissingKeywordsRes
 *					Specifies that the keyword file does
 *					not contain the 'Keywords/keywords'
 *					resource or the resource is NULL
 *
 * Purpose:	Get the list of keywords defined in a volume.
 *
 ******************************************************************************/
static int 
GetVolumeKeywords (
    _DtHelpVolume     vol, 
    char	***retKeywords)
{
    int   result;
    _DtHelpCeLockInfo lockInfo;

    _DtHelpProcessLock();

    /* Keywords aren't loaded until they are needed, so see if they have
       been loaded yet. */
    if (vol->keywords == NULL)
      {
        /*
         * What type of volume is it?
         */
	if (_DtHelpCeLockVolume(vol, &lockInfo) != 0)
	  {
	    _DtHelpProcessUnlock();
	    return -1;
	  }

        if (vol->sdl_flag == False)
            result = _DtHelpCeGetCcdfKeywordList(vol);
	else
            result = _DtHelpCeGetSdlKeywordList(vol);

	_DtHelpCeUnlockVolume(lockInfo);

	if (result != 0)
	  {
	    _DtHelpProcessUnlock();
	    return -1;
	  }
      }

    /* All of the keyword processing is done when they are loaded. */
     *retKeywords = vol->keywords;

    if (*retKeywords == NULL)
      {
	_DtHelpProcessUnlock();
	return (-1);
      }

    _DtHelpProcessUnlock();
    return (0);
}

/*****************************************************************************
 * Function: GetTopicTitleAndAbbrev (
 *
 * Parameters:
 *
 * Memory own by caller:
 *              ret_name
 *              ret_abrrev
 *
 * Returns:     0 if successful, -2 if didn't find the id,
 *		-3 if couldn't format the topic,
 *              otherwise -1.
 *
 * Purpose:     Find the title and abbreviated title of a topic.
 *
 *****************************************************************************/
static int
GetTopicTitleAndAbbrev (
        _DtHelpVolume    volume,
        char             *id,
        char            **ret_name,
        char            **ret_abbrev )
{
    int         result = 0;
    int         offset;
    char        buffer[BUFF_SIZE];
    char       *bufPtr;
    char       *filename = NULL;
    BufFilePtr  file;
    _DtHelpCeLockInfo lockInfo;

    if (_DtHelpCeLockVolume(volume, &lockInfo) != 0)
	return -1;

    if (_DtHelpCeFindId(volume, id, lockInfo.fd, &filename, &offset) != True)
        result = -2;

    /*
     * What type of volume is it?
     */
    if (result == 0)
      {
	if (0 == _DtHelpCeGetVolumeFlag(volume))
          {
            result = _DtHelpCeFileOpenAndSeek(filename,offset,-1,&file,NULL);
            if (result == 0)
              {
		result = -1;
                if (_DtHelpCeReadBuf (file, buffer, BUFF_SIZE) != -1)
                  {
		    result = 0;
                    bufPtr = buffer;
                    if (_DtHelpCeGetCcdfTopicAbbrev (NULL, file,
                                buffer, &bufPtr, BUFF_SIZE, MB_CUR_MAX,
                                        ret_name, NULL, ret_abbrev) != 0)
		        result = -3;
                  }
                _DtHelpCeBufFileClose(file, True);
              }
          }
        else
	  {
	    _DtHelpProcessLock();
	    if (_DtHelpCeFrmtSDLTitleToAscii(volume, offset,
                                                ret_name, ret_abbrev) != 0)
	      result = -3;
	    _DtHelpProcessUnlock();
	  }
      }

    if (filename != NULL)
        free(filename);

    _DtHelpCeUnlockVolume(lockInfo);

    return result;
}

/*****************************************************************************
 * Function: static int FileOpenRtnFd (char *name, int *ret_fd)
 *
 * Parameters:	name		Specifies the file to open.
 *		ret_fd		Returns the fd of the opened file.
 *
 * Returns:	 0 if required uncompress.
 *		   file descriptor to remove the file from the system.
 *		 1 if no uncompression required.
 *		-1 if a failure occurs
 *
 * errno Values:	EINVAL		Specifies an invalid parameter was
 *					used.
 *			CEErrorFileSeek
 *					Specifies the seek offset was invalid.
 *
 * Purpose:	Find out if a file is compressed and uncompress it if it is.
 *
 *****************************************************************************/
static int
FileOpenRtnFd (
    char	*name,
    int		*ret_fd )
{
    char *inFile = NULL;
    char  tmpName[MAXPATHLEN + 1];
    int   result = 1;

    /*
     * check to see if the file exists in uncompressed form
     */
    *ret_fd = open(name, O_RDONLY);
    if (*ret_fd == -1)
      {
	/*
	 * get a temporary name
	 */
	(void) tmpnam (tmpName);

	/*
	 * malloc memory for the dot Z file name.
	 */
	inFile = (char *) malloc (strlen (name) + 3);
	if (inFile != NULL)
	  {
	    /*
	     * make the dot Z file
	     */
	    strcpy (inFile, name);
	    strcat (inFile, ".Z");

	    /*
	     * do the uncompress
	     */
	    result = _DtHelpCeUncompressFile (inFile, tmpName);
	    free (inFile);

	    if (result != 0)
	      {
		errno = ENOENT;
		return -1;
	      }

	    /*
	     * now open the uncompressed file
	     */
	    *ret_fd = open(tmpName, O_RDONLY);
	    if (*ret_fd == -1)
		result = -1;
	    else
		unlink(tmpName);
	  }
	else
	  {
	    errno = CEErrorMalloc;
	    return -1;
	  }
      }

    return result;

} /* End FileOpenRtnFd */

/******************************************************************************
 *                          Semi-Public Functions
 ******************************************************************************/
/*****************************************************************************
 * Function: char *_DtHelpCeExpandPathname (char *spec, char *filename, char *type,
 *		char *suffix, char *lang, _DtSubstitutionRec *subs, int num)
 *
 * Parameters:
 *		spec		Specifies a string with substitution
 *					characters.
 *				containing the character set if found.
 *		filename	Specifies the string to substitute for %N.
 *		type		Specifies the string to substitute for %T.
 *		suffix		Specifies the string to substitute for %S.
 *		lang		Specifies the string to substitute for %L.
 *		subs		Specifies the application own specific
 *					substitutions.
 *		num		Specifies the number of substitution pairs
 *					in 'subs'.
 *
 * Memory own by caller:
 *		returned pointer
 *
 * Returns:	The expanded filename if successful. NULL if errors.
 *
 * Purpose:	Expand a string with %<char> substitution values.
 *		Default substitutions are:
 *			%N	replaced with 'filename'.
 *			%T	replaced with 'type'
 *			%S	replaced with 'suffix'
 *			%L	replaced with 'lang'
 *
 *			%l	replaced with the language sub part of 'lang'.
 *			%t	replaced with the territory sub part of 'lang'.
 *			%c	replaced with the code set sub part of 'lang'.
 *		Other substitutions can be done via the 'subs' parameter.
 *
 *****************************************************************************/
char *
_DtHelpCeExpandPathname (
    char  *spec,
    char  *filename,
    char  *type,
    char  *suffix,
    char  *lang,
    _DtSubstitutionRec *subs,
    int    num )
{
    int    i;
    int    len = 1;
    char  *ptr;
    char  *subString;
    char  *partLang;
    char  *partTer;
    char  *partCodeSet;
    char   pathName [MAXPATHLEN + 5];
    Boolean   previousSlash = False;
#define	MY_NUM 7
    _DtSubstitutionRec mySubs [MY_NUM];

    if (spec == NULL || *spec == '\0')
      {
	errno = EINVAL;
	return NULL;
      }

    /*
     * fill in the language sub parts
     */
    if (_DtHelpCeGetLangSubParts (lang, &partLang, &partTer, &partCodeSet))
	return NULL;

    mySubs[0].match = 'N';
    mySubs[0].substitution = filename;
    mySubs[1].match = 'T';
    mySubs[1].substitution = type;
    mySubs[2].match = 'S';
    mySubs[2].substitution = suffix;
    mySubs[3].match = 'L';
    mySubs[3].substitution = lang;

    mySubs[4].match = 'l';
    mySubs[4].substitution = partLang;
    mySubs[5].match = 't';
    mySubs[5].substitution = partTer;
    mySubs[6].match = 's';
    mySubs[6].substitution = partCodeSet;

    ptr = pathName;
    while (*spec)
      {
	len = 1;
	if (MB_CUR_MAX != 1)
	    len = mblen (spec, MB_CUR_MAX);

	if (len == 1 && *spec == '/')
	  {
	    if (previousSlash)
		spec++;
	    else
	      {
		previousSlash = True;
		*ptr++ = *spec++;
	      }
	  }
	else if (len == 1 && *spec == '%')
	  {
	    spec++;
	    switch (*spec)
	      {
		case '\0':
			*ptr++ = '%';
			break;

		case '/':
			if (!previousSlash)
			  {
			     previousSlash = True;
			     *ptr++ = *spec;
			  }
			spec++;
			break;

		default:
			i = 0;
			while (i < MY_NUM && mySubs && mySubs[i].match != *spec)
			    i++;

			if (i < MY_NUM)
			  {
			    if (mySubs[i].substitution != NULL)
			      {
			        subString = mySubs[i].substitution;
				if (((int)(ptr - pathName + strlen(subString)))
						> MAXPATHLEN)
				  {
				    errno = CEErrorExceedMaxSize;
				    return NULL;
				  }
			        while (subString && *subString)
				    *ptr++ = *subString++;
			      }
			  }
			else
			  {
			    i = 0;
			    while (i < num && subs && subs[i].match != *spec)
				i++;
			    /*
			     * If the substitution character is not found
			     * include the character onto the final string.
			     */
			    if (i >= num)
			        *ptr++ = *spec;
			    else if (subs[i].substitution != NULL)
			      {
			        subString = subs[i].substitution;
				if (((int)(ptr - pathName + strlen(subString)))
						> MAXPATHLEN)
				  {
				    errno = CEErrorExceedMaxSize;
				    return NULL;
				  }
			        while (subString && *subString)
				    *ptr++ = *subString++;
			      }
			  }
			spec++;
			previousSlash = False;
			break;
	      }
	  }
	else
	  {
	    previousSlash = False;
	    do 
	      {
	        *ptr++ = *spec++;
		len--;
	      } while (len > 0);
	  }

	if (ptr - pathName > MAXPATHLEN)
	  {
	    errno = CEErrorExceedMaxSize;
	    return NULL;
	  }
      }

    if (partLang)
	free (partLang);
    if (partTer)
	free (partTer);
    if (partCodeSet)
	free (partCodeSet);

    *ptr = '\0';
    ptr = strdup (pathName);
    if (ptr == NULL)
	errno = CEErrorMalloc;

    return ptr;
}

/*****************************************************************************
 * Function: char *_DtHelpCeGetLangSubParts (char *lang, char **subLang,
 *					char **subTer, char **subCodeSet)
 *
 * Parameters:
 *		lang		Specifies the language string.
 *		subLang		Returns the language sub part of 'lang'
 *					or NULL.
 *		subTer		Returns the territory sub part of 'lang'
 *					or NULL.
 *		subCodeSet	Returns the code set sub part of 'lang'
 *					or NULL.
 *
 * Memory own by caller:
 *		subLang
 *		subTer
 *		subCodeSet
 *
 * errno Values:
 *		EINVAL
 *		CEErrorMalloc
 *
 * Returns:	0 if successful, -1 if errors.
 *
 * Purpose:	Break a %l_%t.%c language specification into its sub parts.
 *
 *****************************************************************************/
int
_DtHelpCeGetLangSubParts (
    char   *lang,
    char  **subLang,
    char  **subTer,
    char  **subCodeSet )
{
    int   len;
    char *ptr;
    char *sLang = NULL;
    char *sTer  = NULL;
    char *sCode = NULL;

    if (subLang == NULL || subTer == NULL || subCodeSet == NULL)
      {
	errno = EINVAL;
	return -1;
      }

    if (lang != NULL && *lang != '\0')
      {
	/*
	 * look for lang_ter
	 */
	_DtHelpCeStrchr (lang, "_", MB_CUR_MAX, &ptr);
	if (ptr)
	  {
	    /*
	     * do we want this string?
	     */
	    if (subLang != NULL)
	      {
		len = ptr - lang;
		sLang = (char *) malloc (len + 1);
		if (sLang != NULL)
		  {
		    strncpy (sLang, lang, len);
		    sLang[len] = '\0';
		  }
		else
		  {
		    errno = CEErrorMalloc;
		    return -1;
		  }
	      }
	    /*
	     * just mark that the lang part was found
	     */
	    else
		sLang = lang;
    
	    lang = ptr + 1;
	  }
    
	/*
	 * look for lang.codeset
	 */
	_DtHelpCeStrchr (lang, Period, MB_CUR_MAX, &ptr);
	if (ptr)
	  {
	    len = ptr - lang;
    
	    /*
	     * if it was in the form lang_ter.codeset, sLang will non-null
	     */
	    if (sLang != NULL)
	      {
		/*
		 * do we want to save the territory?
		 */
		if (subTer != NULL)
		  {
		    sTer = (char *) malloc (len + 1);
		    if (sTer != NULL)
		      {
			strncpy (sTer, lang, len);
			sTer[len] = '\0';
		      }
		    else
		      {
			errno = CEErrorMalloc;
			return -1;
		      }
		  }
		/*
		 * don't wan to save, but make sure we mark the territory
		 * as being found (non-null).
		 */
		else
		    sTer = lang;
	      }
	    /*
	     * the lang was in the form lang.codeset.
	     * now check to see if want to save the lang portion.
	     */
	    else if (subLang != NULL)
	      {
		sLang = (char *) malloc (len + 1);
		if (sLang != NULL)
		  {
		    strncpy (sLang, lang, len);
		    sLang[len] = '\0';
		  }
		else
		  {
		    errno = CEErrorMalloc;
		    return -1;
		  }
	      }
	    /*
	     * didn't want to save the lang portion, but mark as found.
	     */
	    else
		sLang = lang;
	  }
    
       /*
	* currently pointing at the dot?
	*/
       if (ptr && *ptr == '.')
	 {
	    /*
	     * yes save the code set
	     */
	    ptr++;
	    if (subCodeSet != NULL)
	      {
		sCode = strdup (ptr);
		if (sCode == NULL)
		  {
		    errno = CEErrorMalloc;
		    return -1;
		  }
	      }
	    /*
	     * don't save the code set, but make sure we mark as found
	     */
	    else
		sCode = ptr;
	 }
       /*
	* didn't find a code set, so save the current info.
	* If we haven't already processed a lang portion, save as the
	* lang.
	*/
       else if (sLang == NULL)
	 {
	    if (subLang != NULL)
	      {
		sLang = strdup (lang);
		if (sLang == NULL)
		  {
		    errno = CEErrorMalloc;
		    return -1;
		  }
	      }
	    else
		sLang = lang;
	 }
       /*
	* otherwise this is the territory of the language. Save if desired
	*/
       else if (subTer != NULL)
	 {
	    sTer = strdup (lang);
	    if (sTer == NULL)
	      {
		errno = CEErrorMalloc;
		return -1;
	      }
	  }
      }

    if (subLang)
	*subLang = sLang;
    if (subTer)
	*subTer = sTer;
    if (subCodeSet)
	*subCodeSet = sCode;

    return 0;
}

/*****************************************************************************
 * Function: int _DtHelpCeGetUncompressedFileName (char *name, char **ret_name)
 *
 * Parameters:	name		Specifies the file to open.
 *		ret_name 	Returns the name of the uncompressed file.
 *				This memory must be freed by the caller.
 *
 * Returns:	 0 if required uncompress. ret_name will contain the
 *		   name of the uncompressed file. The caller is required
 *		   to free the memory.
 *		 1 if no uncompression required. ret_name points to name.
 *		-1 if a failure occurs
 *
 * errno Values:	EINVAL		Specifies an invalid parameter was
 *					used.
 *			CEErrorFileSeek
 *					Specifies the seek offset was invalid.
 *
 * Purpose:	Find out if a file is compressed and uncompress it if it is.
 *
 *****************************************************************************/
int
_DtHelpCeGetUncompressedFileName (
	char	 *name,
	char		**ret_name )
{
    char *inFile = NULL;
    char  tmpName[MAXPATHLEN + 1];
    int   result = 1;

    /*
     * check to see if the file exists in uncompressed form
     */
    *ret_name = name;
    if (access (name, F_OK) == -1)
      {
	/*
	 * get a temporary name
	 */
	(void) tmpnam (tmpName);

	/*
	 * malloc memory for the dot Z file name.
	 */
	inFile = (char *) malloc (strlen (name) + 3);
	if (inFile != NULL)
	  {
	    /*
	     * make the dot Z file
	     */
	    strcpy (inFile, name);
	    strcat (inFile, ".Z");

	    /*
	     * do the uncompress
	     */
	    result = _DtHelpCeUncompressFile (inFile, tmpName);
	    free (inFile);

	    if (result != 0)
	      {
		errno = ENOENT;
		return -1;
	      }

	    *ret_name = strdup (tmpName);
	    if (*ret_name == NULL)
	      {
		errno = CEErrorMalloc;
		return -1;
	      }
	  }
	else
	  {
	    errno = CEErrorMalloc;
	    return -1;
	  }
      }

    return result;
}

/******************************************************************************
 * Function:	int _DtHelpCeCompressPathname (char *basePath)
 *
 * Parameters:	basePath	Specifies the path for the file possibily
 *				containing /./, //, and /../.
 *
 * Return Value:	0 for success, -1 for failure.
 *			The number of bytes in basePath will be less than or
 *			equal to the number of bytes in basePath when passed
 *			in.
 *
 * errno Values:	EINVAL
 *
 * Purpose:	This function compresses directory changes found
 *		in a file name path.
 *
 ******************************************************************************/
int
_DtHelpCeCompressPathname ( char	*basePath )
{
    int    len;
    int    result;
    short  char1;
    short  char2;
    short  char3;
    char  *ptr     = basePath;
    char  *prevPtr = NULL;

    if (basePath == NULL || *basePath != '/')
      {
	errno = EINVAL;
	return -1;
      }

    do
      {
	/*
	 * for multi-byte environments, check how far single bytes extend.
	 */
	char1 = False;
	char2 = False;
	char3 = False;
	if (MB_CUR_MAX == 1 || mblen (&ptr[1], MB_CUR_MAX) == 1)
	  {
	    char1 = True;
	    if (MB_CUR_MAX == 1 || mblen (&ptr[2], MB_CUR_MAX) == 1)
	      {
		char2 = True;
		if (MB_CUR_MAX == 1 || mblen (&ptr[3], MB_CUR_MAX) == 1)
		    char3 = True;
	      }
	  }

	/*
	 * check for //
	 */
	if (char1 == True && ptr[1] == '/')
	    strcpy (ptr, (ptr + 1));

	/*
	 * check for /./
	 */
	else if (char2 == True && ptr[1] == '.' && ptr[2] == '/')
	    strcpy (ptr, (ptr + 2));

	/*
	 * check for /../
	 */
	else if (char3 == True && strncmp (&ptr[1], "../", 3) == 0)
	  {
	    /*
	     * if at the top of the path, just ignore the extra
	     * directory change.
	     */
	    if (prevPtr == NULL)
	        strcpy (ptr, (ptr + 3));
	    else
	      {
		/*
		 * compress the /../
		 */
		strcpy (prevPtr, (ptr + 3));

		/*
		 * reset the current pointer
		 */
		ptr    = prevPtr;

		/*
		 * find the previous slash
		 */
		*ptr   = '\0';
		result = _DtHelpCeStrrchr(basePath,Slash,MB_CUR_MAX,&prevPtr);
		if (result == -1)
		    return -1;

		/*
		 * if there is no previous slash, set the pointer to
		 * indicate that we're at the top of the path (NULL).
		 */
		if (result != 0)
		    prevPtr = NULL;

		/*
		 * restore the slash (or null byte)
		 */
		*ptr = '/';
	      }
	  }
	else
	  {
	    /*
	     * remember this slash for /../ directory changes
	     */
	    prevPtr = ptr;

	    /*
	     * skip this slash, and find the next one.
	     */
	    ptr++;
	    result = _DtHelpCeStrcspn (ptr, "/", MB_CUR_MAX, &len);

	    /*
	     * if we run into invalid data, error
	     */
	    if (result == -1)
		return -1;

	    ptr += len;
	  }

      } while (*ptr != '\0');

    return (0);
}

/******************************************************************************
 * Function:    char *_DtHelpCeTracePathName (char *path)
 *
 * Parameters:
 *		 path	Specifies the a path to trace and compress
 *
 * Return Value:	The new string if successful, NULL otherwise.
 *			The new string is owned by the caller and contains
 *			an absolute pathname.
 *
 * errno Values:        EINVAL                  Illegal parameter specified.
 *                      getcwd(2)               errno set via a getcwd call.
 *                      readlink(2)             errno set via a readlink call.
 *                      DtErrorMalloc
 *                      DtErrorExceedMaxSize   The new path will exceed
 *                                              max_size.
 *                      DtErrorIllegalPath     The compression will required
 *                                              the path to change to a parent
 *                                              directory beyond the beginning
 *                                              of basePath.
 *
 * Purpose:     This function is called to trace the path of a file.
 *		It can contain symbolic links, //, /./, and /../.
 *
 ******************************************************************************/
char *
_DtHelpCeTracePathName (char *path)
{
    int     result;
    int     len;
    char    c;
    char   *ptr;
    char   *prev;
    char    newPath  [2 * MAXPATHLEN + 2];
    char    linkPath [MAXPATHLEN + 2];
    char    tempPath [MAXPATHLEN + 2];

    if (path == NULL || *path == '\0')
      {
	errno = EINVAL;
	return NULL;
      }

    /*
     * initialize the new path
     */
    newPath[0] = '\0';

    /*
     * if the path passed in does not start with a slash,
     * get the current working directory and append the path to it.
     */
    if ((MB_CUR_MAX == 1 || mblen(path, MB_CUR_MAX) == 1) && *path != '/')
      {
	if (getcwd (newPath, MAXPATHLEN) == NULL)
	    return NULL;

	strcat (newPath, "/");
      }

    /*
     * put the path in the working path buffer (or append it to
     * the current working directory).
     */
    strcat (newPath, path);

    /*
     * Compress out the slashes and directory changes.
     */
    if (_DtHelpCeCompressPathname (newPath) != 0)
	return NULL;

    ptr = newPath;
    do
      {
	/*
	 * point to the first character of the next directory
	 */
	prev = ptr + 1;

	/*
	 * get the next slash after that
	 */
	result = _DtHelpCeStrcspn (prev, "/", MB_CUR_MAX, &len);
	if (result == -1)
	    return NULL;

	/*
	 * Found either a slash or a null byte.
	 * place the string terminator at this point
	 */
	ptr  = prev + len;
	c    = *ptr;
	*ptr = '\0';

	/*
	 * find out if this path is a symbolic link
	 */
	result = readlink (newPath, linkPath, MAXPATHLEN);

	/*
	 * replace the slash (or null byte).
	 */
	*ptr = c;

	/*
	 * check for the result of the readlink call
	 */
	if (result == -1)
	  {
	    /*
	     * if this was NOT a symbolic link, errno should be EINVAL
	     */
	    if (errno != EINVAL)
		return NULL;
	  }
	else
	  {
	    /*
	     * put the null byte on the end of the symbolic link string.
	     */
	    linkPath [result] = '\0';

	    /*
	     * Save the rest of the string that we haven't processed
	     * for tacking on after the new link path has been
	     * dropped into the path.
	     */
	    snprintf(tempPath, sizeof(tempPath), "%s", ptr);

	    /*
	     * is it an absolute path? Simply replace the path
	     * being search with the new link path.
	     */
	    if (*linkPath == '/')
		strcpy (newPath, linkPath);
	    else
	      {
		/*
		 * this is a relative link.
		 * prev is looking at the first character of this directory.
		 * replace with the link.
		 */
		strcpy (prev, linkPath);
	      }

	    /*
	     * now tack on the rest of the name
	     */
	    strcat (newPath, tempPath);

	    /*
	     * compress out the directory changes.
	     */
	    if (_DtHelpCeCompressPathname (newPath) != 0)
		return NULL;

	    /*
	     * start again from the top, until we have a clean path
	     */
	    ptr = newPath;
	  }

      } while (*ptr != '\0');

    return (strdup (newPath));
}

/******************************************************************************
 * Function:    char *_DtHelpCeTraceFilenamePath (char *file_path)
 *
 * Parameters:
 *		 file_path	Specifies the a path to trace and compress
 *
 * Return Value:	The new string if successful, NULL otherwise.
 *			The new string is owned by the caller and
 *			contains an absolute filename path.
 *
 * errno Values:        EINVAL                  Illegal parameter specified.
 *                      getcwd(2)               errno set via a getcwd call.
 *                      readlink(2)             errno set via a readlink call.
 *                      DtErrorMalloc
 *                      DtErrorExceedMaxSize   The new path will exceed
 *                                              max_size.
 *                      DtErrorIllegalPath     The compression will required
 *                                              the path to change to a parent
 *                                              directory beyond the beginning
 *                                              of basePath.
 *
 * Purpose:     This function is called to trace a filename path.
 *		It can contain symbolic links, //, /./, and /../.
 *
 ******************************************************************************/
char *
_DtHelpCeTraceFilenamePath (char *file_path)
{
    int     result;
    int     done = False;
    char   *newPath;
    char   *oldName;
    char   *namePlace;
    char    workName [MAXPATHLEN + 2];
    char    newName  [MAXPATHLEN + 2];
    char    linkName [MAXPATHLEN + 2];

    if (file_path == NULL || *file_path == '\0')
      {
	errno = EINVAL;
	return NULL;
      }

    workName[0] = '\0';
    if ((MB_CUR_MAX == 1 || mblen(file_path, MB_CUR_MAX) == 1)
							&& *file_path != '/')
      {
	if (getcwd(workName, MAXPATHLEN) == NULL)
	   return NULL;

	strcat(workName, "/");
      }

    strcat (workName, file_path);

    do
      {
        /*
         * find and save the old filename
         */
        result = _DtHelpCeStrrchr(workName, Slash, MB_CUR_MAX, &oldName);
        if (result == -1)
	    return NULL;

	/*
	 * terminate the path
	 */
	*oldName = '\0';

	/*
	 * trace the path, resolving the symbolic links
	 * and directory changes. If /filename given,
	 * skip the path tracing.
	 */
	newName[0] = '\0';
	if (workName[0] != '\0')
	  {
	    newPath = _DtHelpCeTracePathName(workName);
	    if (newPath == NULL)
	        return NULL;

	    /*
	     * copy the new path and free the allocated copy
	     */
	    snprintf(newName, sizeof(newName), "%s", newPath);
	    free (newPath);
	  }

	/*
	 * replace the slash
	 */
	*oldName = '/';

	/*
	 * now append the slash and filename (pointed to by oldName)
	 * onto the end of the new path.
	 */
	namePlace = newName + strlen (newName);
	strcpy (namePlace, oldName);

	/*
	 * See if the absolute path/filename is a symbolic link.
	 */
	result = readlink (newName, linkName, MAXPATHLEN);
	if (result == -1)
	  {
	    if (errno != EINVAL)
		return NULL;

	    done = True;
	  }
	else
	  {
	    /*
	     * put the null byte on the end of the symbolic
	     * link string.
	     */
	    linkName [result] = '\0';
	    if (*linkName == '/')
		strcpy (newName, linkName);
	    else
	      {
		/*
		 * overwrite the filename with the link
		 * but don't overwrite the slash.
		 */
		strcpy ((namePlace + 1), linkName);
	      }

	    /*
	     * make a copy of the new name to work on
	     */
	    strcpy (workName, newName);
	  }
      } while (!done);

    return (strdup (newName));
}


/******************************************************************************
 *                    Core Engine Semi-Public Functions
 ******************************************************************************/
/*****************************************************************************
 * Function: char *_DtHelpCeGetVolumeName (_DtHelpVolumeHdl volume)
 *
 * Parameters:
 *
 * Returns:	ptr to the name of the volume, NULL otherwise.
 *
 * Purpose:	Get the fully qualified volume name.
 *
 *****************************************************************************/
char *
_DtHelpCeGetVolumeName (
    _DtHelpVolumeHdl  volume_handle)
{
    char *volFile;

    _DtHelpProcessLock();
    volFile = ((_DtHelpVolume)volume_handle)->volFile;
    _DtHelpProcessUnlock();
    return volFile;

} /* End __DtHelpCeGetVolumeName */

/*****************************************************************************
 * Function: int _DtHelpCeFileOpenAndSeek (char *name, int offset, int fildes,
 *							BufFilePtr *ret_file)
 *
 * Parameters:	name		Specifies the file to open.
 *		offset		Specifies location within the file to seek to.
 *
 * Returns:	0 if successful, -1 if a failure occurs
 *
 * errno Values:	EINVAL		Specifies an invalid parameter was
 *					used.
 *			CEErrorFileSeek
 *					Specifies the seek offset was invalid.
 *
 * Purpose:	Open a file and seek to a specific place.
 *
 *****************************************************************************/
int
_DtHelpCeFileOpenAndSeek (
	char		*name,
	int		 offset,
	int		 fd,
	BufFilePtr	*ret_file,
	time_t		*ret_time)
{
    unsigned char fileMagic[4];
    int           bytesRead;
    int           result = 0;
    int           tmpFd;
    struct stat   buf;

    /*
     * Get the file descriptor of the uncompressed file
     */
    tmpFd = fd;
    if (fd == -1)
      {
        result = FileOpenRtnFd (name, &tmpFd);
        if (result == -1)
	    return -1;
      }

    if (ret_time != NULL)
      {
	(void) fstat(tmpFd, &buf);
	*ret_time = buf.st_mtime;
      }

    /*
     * make sure we don't go past the end of the file
     */
    result = lseek (tmpFd, 0, SEEK_END);
    if (result != -1)
      {
        if (result > offset)
            result = lseek (tmpFd, offset, SEEK_SET);
        else
          {
            result = -1;
            errno = CEErrorFileSeek;
          }
      }

    if (result == -1)
      {
	if (fd == -1)
	    close (tmpFd);
	return -1;
      }

    bytesRead = read(tmpFd, fileMagic, 4);
    if (bytesRead != 4)
      { /* something's wrong in reading the file */
	if (fd == -1)
	    close (tmpFd);
	return -1;
      }

    if (!*fileMagic)
      { /* started with compressed file magic number */

	CECompressInfoPtr myInfo;
	BufFilePtr	  inputRaw;

	/*
	 * allocate the private information
	 */
	myInfo = (CECompressInfoPtr) malloc(sizeof(CECompressInfo));
	if (myInfo == NULL)
	  {
	    if (fd == -1)
	        close (tmpFd);
	    errno = CEErrorMalloc;
	    return -1;
	  }

	/*
	 * set the information
	 * set the size to the maximum number of bytes we
	 * want to read.
	 */
	myInfo->fd   = tmpFd;
	myInfo->size =
	  (((fileMagic[1] * 256) + fileMagic[2]) * 256) + fileMagic[3];

	/*
	 * start with raw functionality
	 */
	inputRaw = _DtHelpCeBufFileRdRawZ(myInfo);
	if (inputRaw == NULL)
	  {
	    if (fd == -1)
	        close (tmpFd);
	    return -1;
	  }

	*ret_file = _DtHelpCeBufFilePushZ(inputRaw);
	if (*ret_file == NULL)
	  {
	    _DtHelpCeBufFileClose(inputRaw, (fd == -1 ? True : False));
	    return -1;
	  }
      }
    else
      {
	/*
	 * not a compressed file, back up the four bytes we read
	 */
	result = lseek (tmpFd, offset, SEEK_SET);
	if (result == -1)
	  {
	    if (fd == -1)
	        close (tmpFd);
	    return -1;
	  }

	/*
	 * read with raw functionality
	 */
	*ret_file = _DtHelpCeBufFileRdWithFd(tmpFd);
	if (*ret_file == NULL)
	  {
	    if (fd == -1)
	        close (tmpFd);
	    return -1;
	  }
      }

    return 0;

} /* End of _DtHelpCeFileOpenAndSeek */
/******************************************************************************
 *                     Core Engine Public Functions
 ******************************************************************************/
/******************************************************************************
 * Function:	int _DtHelpOpenVolume (char *volFile, _DtHelpVolume *retVol);
 *
 * Parameters:	volFile		Specifies the name of the Help Volume file
 *				to load.
 *
 *		retVol		Returns the handle to the loaded volume.
 *				If a volume is opened several times, the
 *				same handle will be returned each time.
 *
 * Return Value:		0 if successful, -1 if a failure occurred.
 *
 * errno Values:	EINVAL			Illegal parameter specified.
 *			getcwd(2)		errno set via a getcwd call.
 *			readlink(2)		errno set via a readlink call.
 * 			CEErrorMalloc
 *			CEErrorExceedMaxSize	The new path will exceed
 *						max_size.
 *			CEErrorIllegalPath	The compression will required
 *						the path to change to a parent
 *						directory beyond the beginning
 *						of basePath.
 *			CEErrorIllegalDatabaseFile
 *						Specifies that 'volFile' is
 *						an illegal database file.
 *
 *
 * Purpose:	This function must be called to open a Help Volume file
 *		before any of the information in the volume can be
 *		accessed. 
 *
 ******************************************************************************/
int 
_DtHelpOpenVolume (
    char	*volFile, 
    _DtHelpVolumeHdl  *retVol)
{
    int     result  = 0;
    _DtHelpVolume vol, prevVol;

    _DtHelpProcessLock();

    if (volFile == NULL || retVol == NULL)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return -1;
      }

    /*
     * follow all the symbolic links and get the absolute path and filename.
     */
    volFile = _DtHelpCeTraceFilenamePath(volFile);
    if (volFile == NULL)
      {
	_DtHelpProcessUnlock();
	return -1;
      }

    /* Search the volume chain to see if it is already open. */
    prevVol = NULL;
    vol = volChain; 
    while (vol != NULL && strcmp (vol->volFile, volFile))
      {
	prevVol = vol;
	vol     = vol->nextVol;
      }

    if (vol)
      {
	vol->openCount++;
        free(volFile);
      }
    else /* if (vol == NULL) */
      {
    	/* If it isn't open, open it and insert it in the chain. */
	result = VolumeLoad (volFile, &vol);
	if (result == 0)
	  {
	    if (prevVol == NULL)
	        volChain = vol;
	    else
	        prevVol->nextVol = vol;
	  }
      }

    /* Return the volume handle and a status indicating success/failure. */
    *retVol = (_DtHelpVolumeHdl) vol;
    _DtHelpProcessUnlock();
    return result;
}

/******************************************************************************
 * Function:	int _DtHelpCeUpVolumeOpenCnt (_DtHelpVolumeHdl vol);
 *
 * Parameters:	vol	Specifies the loaded volume.
 *
 * Return Value: 0 if successful, -1 if a failure occurs
 *
 * errno Values:	EINVAL		'vol' was NULL, no volumes open or
 *					'vol' does not exist.
 *
 * Purpose:	When the volume is no longer needed, it should be
 *		closed with this call.  If the volume has been opened
 *		several times, closing it will just decrement the 
 *		reference count.  When it has been closed as many times
 *		as it was opened, the memory it is using will be freed
 *		and any handles to the volume will be invalid.
 *
 ******************************************************************************/
int 
_DtHelpCeUpVolumeOpenCnt (
     _DtHelpVolumeHdl	volume)
{
    _DtHelpVolume prevVol;
    _DtHelpVolume vol = (_DtHelpVolume)volume;

    _DtHelpProcessLock();

    if (vol == NULL || volChain == NULL)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return (-1);
     }

    /*
     * check to see if this volume is in our chain
     */
    if (vol != volChain)
      {
	if (CheckVolList (vol, &prevVol) == -1)
          {
	    errno = EINVAL;
	    _DtHelpProcessUnlock();
	    return (-1);
          }
      }

    /*
     * increment it's usage count.
     */
    vol->openCount++;
    _DtHelpProcessUnlock();
    return (0);
}

/******************************************************************************
 * Function:	int _DtHelpCloseVolume (_DtHelpVolumeHdl vol);
 *
 * Parameters:	vol	Specifies the loaded volume.
 *
 * Return Value: 0 if successful, -1 if a failure occurs
 *
 * errno Values:	EINVAL		'vol' was NULL, no volumes open or
 *					'vol' does not exist.
 *
 * Purpose:	When the volume is no longer needed, it should be
 *		closed with this call.  If the volume has been opened
 *		several times, closing it will just decrement the 
 *		reference count.  When it has been closed as many times
 *		as it was opened, the memory it is using will be freed
 *		and any handles to the volume will be invalid.
 *
 ******************************************************************************/
int 
_DtHelpCloseVolume (
     _DtHelpVolumeHdl	volume)
{
    _DtHelpVolume prevVol;
    _DtHelpVolume vol = (_DtHelpVolume)volume;

    _DtHelpProcessLock();

    if (vol == NULL || volChain == NULL)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return (-1);
      }

    /*
     * check to see if this volume is in our chain
     */
    if (vol != volChain)
      {
	if (CheckVolList (vol, &prevVol) == -1)
          {
	    errno = EINVAL;
	    _DtHelpProcessUnlock();
	    return (-1);
          }
      }

    /*
     * decrement it's usage count.
     */
    vol->openCount--;
    if (vol->openCount == 0)
      {
	/* The volume is no longer needed.  Unlink it from the chain
	   and free it. */

	if (vol == volChain) 
	    volChain = volChain->nextVol;

	else
	    prevVol->nextVol = vol->nextVol;

	VolumeUnload (vol);
      }

    _DtHelpProcessUnlock();
    return (0);
}

/*****************************************************************************
 * Function: int _DtHelpCeGetTopTopicId (_DtHelpVolume vol,
 *					char **ret_idString)
 *
 * Parameters:	vol		Specifies the loaded volume
 *		ret_idString 	Returns the location ID of the
 *				the top level topic.
 *
 * Memory own by caller:
 *		ret_idString
 *
 * Returns:	True for success, False if a failure occurs.
 *
 * errno Values:	EINVAL		Specifies an invalid parameter was
 *					used.
 *			CEErrorMissingTopTopicRes
 *					Specifies that the 'TopTopic/topTopic'
 *					resource is missing from the database.
 * 			CEErrorMalloc
 *
 * Purpose:	Get the information to access the top level topic.
 *
 *****************************************************************************/
int
_DtHelpCeGetTopTopicId (
	_DtHelpVolumeHdl	volume,
        char		**ret_idString )
{
    int   found = False;
    _DtHelpVolume vol = (_DtHelpVolume)volume;

    _DtHelpProcessLock();

    if (vol == NULL || ret_idString == NULL || CheckVolList(vol, NULL) == -1)
	errno = EINVAL;
    else
      {
	/*
	 * What type of volume is it?
	 */
	if (vol->sdl_flag == False)
	    (void) _DtHelpCeGetCcdfTopTopic(vol, ret_idString);
	else
	    *ret_idString = _DtHelpCeGetSdlHomeTopicId((_DtHelpVolumeHdl) vol);

	if (*ret_idString != NULL)
	    *ret_idString = strdup(*ret_idString);

	if (*ret_idString != NULL)
	    found = True;
      }

    _DtHelpProcessUnlock();
    return found;

}  /* End _DtHelpCeGetTopTopicId */

/*****************************************************************************
 * Function: int _DtHelpCeFindId (_DtHelpVolume vol, char *target_id,
 *					int fd,
 *					char *ret_name,	int *ret_offset)
 *
 * Parameters:	vol		Specifies the loaded volume
 *		target_id	Specifies target location ID
 *		fd		Specifies the locked file descriptor.
 *		ret_name 	Returns a null terminated string
 *				containing a fully qualified path to
 *				the file that contains 'target_id'.
 *		ret_offset 	Returns the offset into 'ret_name'
 *				to the topic that contains 'target_id'.
 *
 * Memory own by caller:
 *		ret_name
 *
 * Returns:	True if successful, False if a failure occurs
 *
 * errno Values:	EINVAL		Specifies an invalid parameter was
 *					used.
 *			CEErrorMalloc
 *			CEErrorMissingFilenameRes
 *					Specifies that the 'Filename/filename'
 *					resource for 'topic' does not exist.
 *			CEErrorMissingFileposRes
 *					If the resource is not in the
 *					database or if the resource NULL.
 *			CEErrorLocIdNotFound
 *					Specifies that 'locId' was not
 *					found.
 *
 * Purpose:	Find which topic contains a specified locationID.
 *
 *****************************************************************************/
int
_DtHelpCeFindId (
	_DtHelpVolumeHdl	volume,
	char		*target_id,
	int		 fd,
	char		**ret_name,
	int		*ret_offset )
{
    _DtHelpVolume vol = (_DtHelpVolume)volume;
    int result;

    _DtHelpProcessLock();

    /*
     * check the parameters
     */
    if (vol == NULL || target_id == NULL || ret_name == NULL ||
			ret_offset == NULL || CheckVolList (vol, NULL) == -1)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return False;
      }

    /*
     * What type of volume is it?
     */
    if (vol->sdl_flag == False)
      {
	result = _DtHelpCeFindCcdfId(vol, target_id, ret_name, ret_offset);
      }
    else
      {
	result = _DtHelpCeFindSdlId(vol, target_id, fd, ret_name, ret_offset);
      }

    _DtHelpProcessUnlock();
    return result;

}  /* End _DtHelpCeFindId */

/*****************************************************************************
 * Function: int _DtHelpCeGetKeywordList (_DtHelpVolume vol, char ***ret_keywords)
 *
 * Parameters:	vol		Specifies the loaded volume
 *		ret_keywords	Returns a NULL-terminated string array
 *				containing the sorted list of keywords in the
 *				volume.  This array is NOT owned by the caller
 *				and should only be read or copied.
 *
 * Returns:	The count of keywords associated with the volume if successful.
 *		-1 if a failure occurs;
 *
 * errno Values:	EINVAL		Specifies an invalid parameter was
 *					used.
 *			CEErrorMalloc
 *			CEErrorIllegalDatabaseFile
 *					Specifies that the keyword file is
 *					invalid or corrupt.
 *			CEErrorMissingKeywordsRes
 *					Specifies that the keyword file does
 *					not contain the 'Keywords/keywords'
 *					resource or the resource is NULL
 *
 * Purpose:	Get the list of keywords contained in a volume.
 *
 *****************************************************************************/
int
_DtHelpCeGetKeywordList (
	_DtHelpVolumeHdl	volume,
	char		***ret_keywords )
{
    int       nameCount = -1;
    _DtHelpVolume vol = (_DtHelpVolume)volume;

    if (vol == NULL || ret_keywords == NULL || CheckVolList (vol, NULL) == -1)
	errno = EINVAL;
    else if (GetVolumeKeywords (vol, ret_keywords) == 0)
      {
	nameCount = 0;
	while (*ret_keywords && (*ret_keywords)[nameCount])
	    nameCount++;
      }

    return nameCount;

}  /* End _DtHelpCeGetKeywordList */

/*****************************************************************************
 * Function: int _DtHelpCeFindKeyword (_DtHelpVolume vol, char *target, char ***ret_ids)
 *
 * Parameters:	vol		Specifies the loaded volume
 *		target		The target keyword.
 *		ret_ids 	Returns a null terminated list of location
 *				ids associated with the target keyword.
 *
 * Returns:	The count of ids associated with the keyword if successful.
 *		-1 if a failure occurs;
 *
 * errno Values:	EINVAL		Specifies an invalid parameter was
 *					used.
 *			CEErrorNoKeywordList
 *					Specifies that the volume does not
 *					have a keyword list.
 *			CEErrorIllegalKeyword
 *					Specifies that 'target' was not
 *					found.
 *			CEErrorMalloc
 *			CEErrorIllegalDatabaseFile
 *					Specifies that the keyword file is
 *					invalid or corrupt.
 *			CEErrorMissingKeywordsRes
 *					Specifies that the keyword file does
 *					not contain the 'Keywords/keywords'
 *					resource or the resource is NULL
 *
 * Purpose:	Get the list of location ids associated with a keyword
 *
 *****************************************************************************/
int
_DtHelpCeFindKeyword (
	_DtHelpVolumeHdl	volume,
	char		*target,
	char		***ret_ids )
{
    int       nameCount = -1;
    _DtHelpVolume vol = (_DtHelpVolume)volume;

    if (vol == NULL || target == NULL || ret_ids == NULL ||
					CheckVolList (vol, NULL) == -1)
	errno = EINVAL;
    else if (GetKeywordTopics (vol, target, ret_ids) == 0)
      {
	nameCount = 0;
	while (*ret_ids && (*ret_ids)[nameCount])
	    nameCount++;
      }

    return nameCount;

}  /* End _DtHelpCeFindKeyword */

/*****************************************************************************
 * Function: int _DtHelpGetTopicTitle (
 *                                      _DtHelpVolumeHdl volume,
 *                                      char *id, char **ret_title)
 *
 * Parameters:  volume          Specifies the volume containing the id.
 *              id              Specifies the id for the topic desired.
 *              ret_title       Returns a null terminated string containing
 *                              the title.
 *
 * Memory own by caller:
 *              ret_title
 *
 * Returns:     0 if successful, -2 if didn't find the id,
 *              otherwise -1.
 *
 * errno Values:        EINVAL          Specifies an invalid parameter was
 *                                      used.
 *
 * Purpose:     Get the title of a topic.
 *
 *****************************************************************************/
int
_DtHelpGetTopicTitle (
    _DtHelpVolumeHdl          volume,
    char                 *id,
    char                **ret_title)
{
    int            result;
    char          *abbrevTitle;
    _DtHelpVolume  vol = (_DtHelpVolume)volume;

    if (volume == NULL || id == NULL ||
			CheckVolList (vol, NULL) == -1 || ret_title == NULL)
      {
        errno = EINVAL;
        return -1;
      }

    /*
     * Try to get the title via the <TOPIC> and <ABBREV> tags.
     */
    result = GetTopicTitleAndAbbrev(vol, id, ret_title, &abbrevTitle);
    if (result == 0)
      {
        /*
         * If we have a abbreviated title, return it instead.
         */
        if (abbrevTitle)
          {
            if (*ret_title)
                free ((char *) *ret_title);
            *ret_title = abbrevTitle;
          }
      }

    return result;

}  /* End _DtHelpGetTopicTitle */

/*****************************************************************************
 * Function: int _DtHelpCeMapTargetToId (_DtHelpVolume vol,
 *					char *target_id,
 *					char *ret_id)
 *
 * Parameters:	vol		Specifies the loaded volume
 *		target_id	Specifies target location ID
 *		ret_id 		Returns the id containing the target_id.
 *				This memory *IS NOT* owned by the caller.
 *
 * Returns:	0 if successful, -1 if a failure occurs
 *
 * Purpose:	Find which topic contains a specified locationID.
 *
 *****************************************************************************/
int
_DtHelpCeMapTargetToId (
	_DtHelpVolumeHdl	volume,
	char		*target_id,
	char		**ret_id)
{
    _DtHelpVolume vol = (_DtHelpVolume)volume;
    int result;

    _DtHelpProcessLock();

    /*
     * check the parameters
     */
    if (vol == NULL || target_id == NULL || ret_id == NULL ||
					CheckVolList (vol, NULL) == -1)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return -1;
      }

    /*
     * What type of volume is it?
     */
    if (vol->sdl_flag == False)
      {
	result = _DtHelpCeMapCcdfTargetToId(vol, target_id, ret_id);
      }
    else
      {
	result = _DtHelpCeMapIdToSdlTopicId(vol, target_id, ret_id);
      }

    _DtHelpProcessUnlock();
    return result;

}  /* End _DtHelpCeMapTargetToId */

/*****************************************************************************
 * Function: char * _DtHelpGetVolumeLocale (_DtHelpVolume vol)
 *
 * Parameters:	vol		Specifies the loaded volume
 *
 * Returns:	The pointer to the locale string if successful. Otherwise
 *		NULL.
 *
 * Purpose:	Get the locale of the specified volume.
 *		Returns the locale in a unix specific format
 *		- locale[_ter][.charset] - This memory is owned by
 *		the caller.
 *
 *****************************************************************************/
char *
_DtHelpGetVolumeLocale (
	_DtHelpVolumeHdl	volume)
{
    _DtHelpVolume  vol = (_DtHelpVolume)volume;
    char *result;

    _DtHelpProcessLock();

    /*
     * check the parameters
     */
    if (vol == NULL || CheckVolList (vol, NULL) == -1)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return NULL;
      }

    /*
     * What type of volume is it?
     */
    if (vol->sdl_flag == False)
      {
	result = _DtHelpCeGetCcdfVolLocale(vol);
      }
    else
      {
	result = _DtHelpCeGetSdlVolumeLocale(vol);
      }

    _DtHelpProcessUnlock();
    return result;

}  /* End _DtHelpGetVolumeLocale */

/*****************************************************************************
 * Function: int _DtHelpCeGetDocStamp (_DtHelpVolumeHdl volume, char **ret_doc,
 *						char **ret_time)
 *
 * Parameters:	volume		Specifies the loaded volume
 *		ret_doc		Returns the doc id.
 *		ret_time	Returns the time stamp.
 *
 * Memory:	Caller owns the string in ret_doc and ret_time.
 *
 * Returns:	0 if successful, -2 if the volume does not contain
 *		one or the other, -1 if any other failure.
 *
 * Purpose:	Get doc id and time stamp of a volume.
 *
 *****************************************************************************/
int
_DtHelpCeGetDocStamp (
    _DtHelpVolumeHdl	volume,
    char		**ret_doc,
    char		**ret_time)
{
    _DtHelpVolume  vol = (_DtHelpVolume)volume;
    int result;

    _DtHelpProcessLock();

    /*
     * check the parameters
     */
    if (vol == NULL || CheckVolList (vol, NULL) == -1)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return 0;
      }

    /*
     * What type of volume is it?
     */
    if (vol->sdl_flag == False)
      {
	result = _DtHelpCeGetCcdfDocStamp (vol, ret_doc, ret_time);
      }
    else
      {
	result = _DtHelpCeGetSdlDocStamp(vol, ret_doc, ret_time);
      }

    _DtHelpProcessUnlock();
    return result;

}  /* End _DtHelpCeGetDocStamp */

/*****************************************************************************
 * Function: char * _DtHelpCeGetTopicChilren (_DtHelpVolumeHdl vol)
 *
 * Parameters:	vol		Specifies the loaded volume
 *		topic_id	Speicifes the topic id of which the
 *				children are to be found.
 *		retTopics	Returns the null terminated array of
 *				ids. This memory is owned by the caller
 *				and must be freed.
 *
 * Returns:	> 0 if successful, -1 if failures.
 *
 * Purpose:	Get the children of a topic.
 *
 *****************************************************************************/
int 
_DtHelpCeGetTopicChildren (
    _DtHelpVolumeHdl   volume,
    char	  *topic_id,
    char	***retTopics)
{
    _DtHelpVolume  vol = (_DtHelpVolume)volume;
    int result;

    _DtHelpProcessLock();

    /*
     * check the parameters
     */
    if (vol == NULL || CheckVolList (vol, NULL) == -1)
      {
	errno = EINVAL;
	_DtHelpProcessUnlock();
	return -1;
      }

    /*
     * What type of volume is it?
     */
    if (vol->sdl_flag == False)
      {
	result = _DtHelpCeGetCcdfTopicChildren(volume, topic_id, retTopics);
      }
    else
      {
	result = _DtHelpCeGetSdlTopicChildren(volume, topic_id, retTopics);
      }

    _DtHelpProcessUnlock();
    return result;

}  /* End _DtHelpCeGetTopicChildren */

/*****************************************************************************
 * Function: int _DtHelpCeGetVolumeFlag (_DtHelpVolumeHdl vol)
 *
 * Parameters:	vol		Specifies the loaded volume
 *
 * Returns:	0 if CCDF volume, 1 if SDL, -1 if failures.
 *
 * Purpose:	Determine the type of volume.
 *
 *****************************************************************************/
int 
_DtHelpCeGetVolumeFlag (
    _DtHelpVolumeHdl   volume)
{
    _DtHelpVolume  vol = (_DtHelpVolume)volume;

    /*
     * check the parameters
     */
    if (vol == NULL || CheckVolList (vol, NULL) == -1)
      {
	errno = EINVAL;
	return -1;
      }

    /*
     * What type of volume is it?
     */
    return((int) vol->sdl_flag);

}  /* End _DtHelpCeGetVolumeFlag */

/*****************************************************************************
 * Function: int _DtHelpCeLockVolume (_DtHelpVolumeHdl vol)
 *
 * Parameters:	vol		Specifies the loaded volume
 *
 * Returns:	> 0 if successful, -1 if failures.
 *
 * Purpose:	Lock the volume so that it can't get change out from
 *		under the caller.
 *
 *****************************************************************************/
int 
_DtHelpCeLockVolume (
    _DtHelpVolumeHdl	 volume,
    _DtHelpCeLockInfo	*lock_info)
{
    struct stat    buf;
    _DtHelpVolume  vol = (_DtHelpVolume)volume;

    _DtHelpProcessLock();

    /*
     * check the parameters
     */
    if (vol == NULL || CheckVolList (vol, NULL) == -1)
      {
	_DtHelpProcessUnlock();
	return -1;
      }

    /*
     * lock it by opening it.
     */
    lock_info->fd = open(vol->volFile, O_RDONLY);
    if (lock_info->fd == -1)
      {
	_DtHelpProcessUnlock();
	return -1;
      }

    (void) fstat(lock_info->fd, &buf);
    if (buf.st_mtime != vol->check_time)
      {
	if (RereadVolume(vol) != 0)
	  {
	    close(lock_info->fd);
	    _DtHelpProcessUnlock();
	    return -1;
	  }

	vol->check_time = buf.st_mtime;
      }

    /*
     * Synthetic open
     */
    vol->openCount++;
    lock_info->volume = volume;
    _DtHelpProcessUnlock();
    return 0;

}  /* End _DtHelpCeLockVolume */

/*****************************************************************************
 * Function: int _DtHelpCeUnlockVolume (_DtHelpVolumeHdl vol)
 *
 * Parameters:	vol		Specifies the loaded volume
 *
 * Returns:	> 0 if successful, -1 if failures.
 *
 * Purpose:	Unlock the volume.
 *
 *****************************************************************************/
int 
_DtHelpCeUnlockVolume (
    _DtHelpCeLockInfo	lock_info)
{
    _DtHelpVolume  vol = (_DtHelpVolume)(lock_info.volume);

    _DtHelpProcessLock();

    /*
     * check the parameters
     */
    if (vol == NULL || CheckVolList (vol, NULL) == -1)
      {
	_DtHelpProcessUnlock();
	return -1;
      }

    /*
     * check to see if it needs to be unlocked.
     */
    if (lock_info.fd != -1)
	close(lock_info.fd);

    /*
     * Synthetic close
     */
    vol->openCount--;
    _DtHelpProcessUnlock();
    return 0;

}  /* End _DtHelpCeUnlockVolume */

/*****************************************************************************
 * Function: int _DtHelpCeIsTopTopic (_DtHelpVolumeHdl volume, const char *id)
 *
 * Parameters:	vol		Specifies the loaded volume
 *              id              Specifies a location id.
 *
 * Returns:	= 0 if successful, != 0 if failures.
 *
 * Purpose:	Tests to see if the location id is in the top topic of
 *		the volume.
 *
 *****************************************************************************/
int 
_DtHelpCeIsTopTopic (
    _DtHelpVolumeHdl	 volume,
    const char		*id)
{
    int		   result  = -1;
    char          *topicId = NULL;
    char          *topId   = NULL;
    _DtHelpVolume  vol = (_DtHelpVolume) volume;

    _DtHelpProcessLock();
    /*
     * check the parameters
     */
    if (vol == NULL || CheckVolList (vol, NULL) == -1)
      {
	_DtHelpProcessUnlock();
	return -1;
      }

    /*
     * What type of volume is it?
     */
    if (vol->sdl_flag == False)
      {
	if (_DtHelpCeMapCcdfTargetToId(vol, id, &topicId) == 0 &&
				_DtHelpCeGetCcdfTopTopic(vol, &topId) == 0)
            result = _DtHelpCeStrCaseCmpLatin1(topId, topicId);
      }
    else if (_DtHelpCeMapIdToSdlTopicId(vol, id, &topicId) == 0)
      {
	topId = _DtHelpCeGetSdlHomeTopicId(volume);
	if (topId != NULL)
	    result = _DtHelpCeStrCaseCmpLatin1(topId, topicId);
      }

    _DtHelpProcessUnlock();
    return result;

}  /* End _DtHelpCeIsTopTopic */
