/*
 * 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
 */
/*
 * (c) Copyright 1993, 1994 Hewlett-Packard Company                     *
 * (c) Copyright 1993, 1994 International Business Machines Corp.       *
 * (c) Copyright 1993, 1994 Sun Microsystems, Inc.                      *
 * (c) Copyright 1993, 1994 Novell, Inc.                                *
 */
/******************************************************************************
 ******************************************************************************
 **
 ** DtEnvMap.c
 **
 ** $TOG: DtEnvMap.c /main/7 1998/07/30 12:11:59 mgreess $
 **
 ** Map the path elements of environment strings as specified by yet
 ** another environment string from the local host to a remote host.
 ** When possible, cache the results since filename mapping is expensive.
 **
 ** DTENVMAPFORREMOTE="NAME1[:NAME2[...]]"
 **
 ** NAME1=path1:path2
 ** NAME2=path3:path4:...
 **
 ******************************************************************************
 *****************************************************************************/

#define X_INCLUDE_STRING_H
#define XOS_USE_XT_LOCKING
#include <X11/Xos_r.h>

#include <DtNlUtils.h>
#include <Tt/tt_c.h>
#include "DtSvcLock.h"

/******************************************************************************
 ******************************************************************************
 **
 ** Data Structures and Defines.
 **
 **/

/**********************************************************
 *
 * What to map...
 *
 * ... is specified by either an environment variable or 
 * a global X resource.  If present, the X resource acts
 * globally, and is referred to on every attempt to map.
 * If an environment variable is set, it overrides the X
 * resource setting, thereby allowing for local control
 * of individual process threads (by way of propagated
 * environments).
 */

#define _DTENV_MAP_RESOURCE_NAME   "dtEnvMapForRemote"
#define _DTENV_MAP_RESOURCE_CLASS  "DtEnvMapForRemote"
#define _DTENV_MAP_ENV_VAR         "DTENVMAPFORREMOTE"

/**********************************************************
 *
 * The primary caching/mapping data structures.
 */

typedef struct {
    char          *localEnvVarPtr;	/* original ptr, used for restoring */
    char          *localEnvVarCopy;	/* eg, /users/foo:/usr/bar */
    char          *mappedEnvVarPtr;	/* eg, PATH=/nfs/.../foo:/nfs/.../bar */
} cachedEnvVar;

typedef struct {
    char          *remoteHost;		/* host to map env vars to */
    int            cacheHit;		/* usage rate for this mapping info */
    char          *mapListStr;		/* copy of DTENVMAPFORREMOTE=... */

    int            mapListCnt;		/* how many _real_ env vars to map */
    char         **mapList;		/* simple list of env var names to map*/
    cachedEnvVar  *mapListDetails;	/* list of map info per env var name */
} cacheForTargetHost; 

/*
 * Cache information.  Keep mapping information for MAX_HOSTS_CACHED
 * targetHosts.
 */
#define MAX_HOSTS_CACHED 8
static cacheForTargetHost cachePoolG[MAX_HOSTS_CACHED];

/*
 * Track the most recent targetHost.
 */
static char *mostRecentRemoteHostG;

/**********************************************************
 *
 * Misc defines.
 *
 * To reduce malloc calls when creating lists, malloc
 * "list" in blocks:
 *
 *      char *list[0..15 , 16..31, 32..63, ...]
 */
#define MALLOC_BUMP_SIZE 16

#define freeAndNull(ptr)   if (ptr) free(ptr);    ptr = NULL;
#define ttfreeAndNull(ptr) if (ptr) tt_free(ptr); ptr = NULL;

/**********************************************************
 *
 * Functionality hooks.
 *
 *    - _DTENV_SUPPORT_COMMA_SEPARATED - turn on code that
 *           special cases some environment variables and
 *           treats them as comma separated and possibly
 *           host qualified.
 *
 *    - _DTENV_SUPPORT_MAPERROR_CACHING - turn on code that
 *           would cache error codes when mapping files in
 *           addition to caching successfully mapped files.
 *           In configurations where mapping errors are
 *           common, this could be a big help, but on the
 *           downside, a mapping error that is the result
 *           of a temporary network error (for example)
 *           could get locked into a cache.
 *
 *    - _DTENV_OPTIMIZATION_LOCALHOST - turn on code that
 *           would prevent even trying to map/cache environment
 *           variables if the targetHost was the localhost.
 */           
#define _DTENV_SUPPORT_COMMA_SEPARATED  1
#undef  _DTENV_SUPPORT_MAPERROR_CACHING
#undef  _DTENV_OPTIMIZATION_LOCALHOST


/**********************************************************
 *
 * Performance hooks.
 *
 *    - DTENV_PERF_HOOK - instrunment file mapping code for
 *                        performance measuring.
 */
#undef DTENV_PERF_HOOK

#ifdef DTENV_PERF_HOOK
#include <time.h>

int stopwatch_repeat_rate = 10;
unsigned long stopwatch_tt_file_netfile;
unsigned long stopwatch_tt_netfile_file;
#endif /* DTENV_PERF_HOOK */


/******************************************************************************
 ******************************************************************************
 **
 ** _DtEnv_tt_file_netfile()
 ** _DtEnv_tt_netfile_file()
 **
 ** Both are caching versions built on the non-caching Tooltalk versions.
 **
 ** The one flaw in both is that if the filesystem topology changes
 ** (eg, NFS mounts, symlinks, etc), old and incorrect mapping information
 ** may be returned. To minimize this, a cached mapping will only be used
 ** so many times before being recomputed.  An alternative (not implemented
 ** here) would be to recompute based on age of the mapping via
 ** gettimeofday().
 **
 **     ******************* XXX Alert **************************
 **
 **     Tooltalk currently allocates a range of char*'s
 **     and then associates them with a range of error
 **     messages (ala tt_ptr_error()).  In effect, the
 **     allocated char*'s become constants within Tooltalk.
 **
 **     This code takes this into account when returning
 **     values.   Non-reserved char*'s are strdup()ed
 **     often, while reserved char*'s are not to preserve
 **     their ptr value.
 **
 ** tjg: Other than differing cache repositories, all the onion-skin
 ** routines below look the same.   Probably better to have one
 ** core routine to minimize code size.
 **/

/*
 * Per cache setup (ie, one for _DtEnv_tt_file_netfile(), one for
 * _DtEnv_tt_netfile_file()), start off by caching SIZE_START
 * mappings, growing by SIZE_BUMP as needed, while limiting the
 * total cache growth to SIZE_MAX mappings.
 */
#define CACHE_FILEFRAG_SIZE_START     15
#define CACHE_FILEFRAG_SIZE_BUMP      15
#define CACHE_FILEFRAG_SIZE_MAX       45

/*
 * After a cached mapping is used REMAP_AFTER times, age it out
 * and force a recomputation of the mapping from scratch.
 */
#define CACHE_FILEFRAG_REMAP_AFTER    25

/*
 * After RESET_PRI hits on a cache of size SIZE_MAX, reset all
 * cacheHit counters.  This will allow new mappings to work into
 * the cache against mappings that might have been popular long
 * ago.
 */
#define CACHE_FILEFRAG_RESET_PRI     200


/******************************************************************************
 *
 * _DtEnv_tt_file_netfile()
 *
 * A caching version of tt_file_netfile().
 */
typedef struct {
    char          *pathFragOrig;	/* eg, /usr/dt */
    char          *pathFragMapped;	/* eg, <from tt_file_netfile> */
    int           cacheHit;		/* usage of */
} cachedFileFrag;

char *_DtEnv_tt_file_netfile(
    const char *filename)
{
    static int             first_time  = 1;
    static int             fragListAvail = CACHE_FILEFRAG_SIZE_START;
    static int             fragListCnt = 0;
    static cachedFileFrag *fragList;
    static int             cacheGen = 0;
    static int             hitIdxStart = 0;

    char           *netpath;
    int             hitval, hitIdx, i;
    cachedFileFrag *tmpCffP;
    char           *tmpStr;
    int			   newCount = fragListCnt;

    _DtSvcProcessLock();

    if (first_time) {
	fragList = (cachedFileFrag *) calloc( fragListAvail,
						sizeof(cachedFileFrag) );
	first_time = 0;
    }

    /*
     * Take care of the obvious.
     */
    if (!filename) {
	_DtSvcProcessUnlock();
	return( (char *) NULL );
    }

    /*
     * Look for existing answer in cache.
     *
     * While at it, also look for least used entry just in case.
     */
    if (fragListCnt)
	hitIdxStart = (hitIdxStart + 7) % fragListCnt;
    else
	hitIdxStart = 0;

    hitIdx = hitIdxStart;
    hitval = fragList[hitIdx].cacheHit;
    tmpCffP = fragList;				/* walk rather than index */

    for ( i = 0; i < fragListCnt; i++ ) {
	if (tmpCffP->cacheHit && !strcmp( filename, tmpCffP->pathFragOrig ) ) {
	    break;
	}

	if (tmpCffP->cacheHit < hitval) {
	    hitIdx = i;
	    hitval = tmpCffP->cacheHit;
	}
	tmpCffP++;
    }

    /*
     * Decide what was found.
     */
    if ( i != fragListCnt ) {
	/*
	 * Found a cached entry.
	 */
	hitIdx = i;
	if ( fragList[hitIdx].cacheHit++ > CACHE_FILEFRAG_REMAP_AFTER ) {
	    /*
	     * This looks like an old entry, so re-compute it.
	     */
	    freeAndNull( fragList[hitIdx].pathFragOrig );
	    ttfreeAndNull( fragList[hitIdx].pathFragMapped );
	    fragList[hitIdx].cacheHit = 0;	/* 0 means remap below */
	}
    }
    else {
	/*
         * Did not find a cache entry, so scrounge around for
	 * a new entry.
	 */
	if ( fragListCnt < fragListAvail ) {
	    /*
	     * Use next already-malloc'ed cacheEntry.
	     */
	    hitIdx = fragListCnt;
            newCount = fragListCnt + 1;
	}
	else if ( fragListCnt < CACHE_FILEFRAG_SIZE_MAX ) {
	    /*
	     * Can grow fragList[]
	     */
	    fragListAvail += CACHE_FILEFRAG_SIZE_BUMP;
	    fragList = (cachedFileFrag *) realloc( (char *) fragList,
					sizeof(cachedFileFrag) * fragListAvail);
	    /*
	     * Zero out new memory.
	     */
	    memset( fragList + (fragListAvail-CACHE_FILEFRAG_SIZE_BUMP),
		    0, CACHE_FILEFRAG_SIZE_BUMP*sizeof(cachedFileFrag) );
	    hitIdx = fragListCnt;
            newCount = fragListCnt + 1;
	}
	else {
	    /*
	     * Last resort - bump out the least used entry.
	     */
	    freeAndNull( fragList[hitIdx].pathFragOrig );
	    ttfreeAndNull( fragList[hitIdx].pathFragMapped );

	    /*
	     * Since the cache is 100% full, occasionally reset
	     * everyone's cacheHit rate so entries that were only
	     * popular long ago don't get locked in.
	     */
	    if ( cacheGen++ > CACHE_FILEFRAG_RESET_PRI ) {
		cacheGen = 0;
		tmpCffP = fragList;
		for ( i = 0; i < fragListCnt; i++ ) {
		    tmpCffP->cacheHit = 1;
		    tmpCffP++;
		}
	    }
	}

	fragList[hitIdx].cacheHit = 0;		/* 0 means remap below */
    }

    if ( ! fragList[hitIdx].cacheHit ) {
	/*
	 * Need to perform mapping.
	 */
	netpath = tt_file_netfile( filename );

#ifdef _DTENV_SUPPORT_MAPERROR_CACHING
	fragList[hitIdx].pathFragOrig = strdup( filename );
	fragList[hitIdx].cacheHit = 1;
	fragList[hitIdx].pathFragMapped = netpath;

        fragListCnt = newCount;
#else
	if ( tt_ptr_error(netpath) == TT_OK ) {
	    fragList[hitIdx].pathFragOrig = strdup( filename );
	    fragList[hitIdx].cacheHit = 1;
	    fragList[hitIdx].pathFragMapped = netpath;
 
            /*
             * Only change the count if we are successful in adding 
             * a new entry.
             */
            fragListCnt = newCount;
	}
	else {
	    /*
	     * Don't cache errors.   Leave this cache slot empty
	     * and it will be rediscovered and used in the future.
	     */
	    fragList[hitIdx].cacheHit = 0;
            /*
             * Do not change the fragListCount since we don't want to 
             * add in error entries.
             */
	}
#endif /* _DTENV_SUPPORT_MAPERROR_CACHING */
    }

    /*
     * Dig out answer and return it.
     */
#ifdef _DTENV_SUPPORT_MAPERROR_CACHING
    if ( tt_ptr_error(netpath) == TT_OK )
#else
    if ( fragList[hitIdx].cacheHit )
#endif /* _DTENV_SUPPORT_MAPERROR_CACHING */
    {
	/*
	 * Return a tt_free-able copy of the answer.
	 */
	tmpStr = tt_malloc( strlen(fragList[hitIdx].pathFragMapped) + 1 );
	strcpy( tmpStr, fragList[hitIdx].pathFragMapped );

	_DtSvcProcessUnlock();
	return(tmpStr);
    }
    else {
	/*
	 * See XXX comment.
	 *
	 * Since netpath is an error code, return as is.
	 */ 
	_DtSvcProcessUnlock();
	return(netpath);
    }
}


/******************************************************************************
 *
 * _DtEnv_tt_netfile_file()
 *
 * A caching version of tt_netfile_file().
 */
typedef struct {
    char          *targetHost;
    char          *pathFragOrig;	/* eg, <from tt_file_netfile> */
    char          *pathFragMapped;	/* eg, /nfs/hostb/usr/dt */
    int           cacheHit;		/* usage of */
} cachedNetfileFrag;

char *_DtEnv_tt_host_netfile_file(
    const char *host,
    const char *filename)
{
    static int             first_time  = 1;
    static int             fragListAvail = CACHE_FILEFRAG_SIZE_START;
    static int             fragListCnt = 0;
    static cachedNetfileFrag *fragList;
    static int             cacheGen = 0;
    static int             hitIdxStart = 0;

    char           *newfile;
    int             hitval, hitIdx, i;
    cachedNetfileFrag *tmpCffP;
    char           *tmpStr;
    int            newCount = fragListCnt;

    _DtSvcProcessLock();
    if (first_time) {
	fragList = (cachedNetfileFrag *) calloc( fragListAvail,
						sizeof(cachedNetfileFrag) );
	first_time = 0;
    }

    /*
     * Take care of the obvious.
     */
    if (!filename) {
	_DtSvcProcessUnlock();
	return( (char *) NULL );
    }

    if (!host) {
	/*
	 * Return a tt_free-able un-mapped copy.
	 */
	tmpStr = tt_malloc( strlen(filename) + 1 );
	strcpy( tmpStr, filename );

	_DtSvcProcessUnlock();
	return(tmpStr);
    }

    /*
     * Look for existing answer in cache.
     *
     * While at it, also look for least used entry just in case.
     */
    if (fragListCnt)
	hitIdxStart = (hitIdxStart + 7) % fragListCnt;
    else
	hitIdxStart = 0;

    hitIdx = hitIdxStart;
    hitval = fragList[hitIdx].cacheHit;
    tmpCffP = fragList;				/* walk rather than index */

    for ( i = 0; i < fragListCnt; i++ ) {
	if (tmpCffP->cacheHit && !strcmp( filename, tmpCffP->pathFragOrig ) ) {
	    if (!strcmp( host, tmpCffP->targetHost ) ) {
		break;
	    }
	}

	/*
	 * Save index of least used entry
	 */
	if (tmpCffP->cacheHit < hitval) {
	    hitIdx = i;
	    hitval = tmpCffP->cacheHit;
	}
	tmpCffP++;
    }

    /*
     * Decide what was found.
     */
    if ( i != fragListCnt ) {
	/*
	 * Found a cached entry.
	 */
	hitIdx = i;
	if ( fragList[hitIdx].cacheHit++ > CACHE_FILEFRAG_REMAP_AFTER ) {
	    /*
	     * This looks like an old entry, so re-compute it.
	     */
	    freeAndNull( fragList[hitIdx].targetHost );
	    freeAndNull( fragList[hitIdx].pathFragOrig );
	    ttfreeAndNull( fragList[hitIdx].pathFragMapped );
	    fragList[hitIdx].cacheHit = 0;	/* 0 means remap below */
	}
    }
    else {
	/*
         * Did not find a cache entry, so scrounge around for
	 * a new entry.
	 */
	if ( fragListCnt < fragListAvail ) {
	    /*
	     * Use next already-malloc'ed cacheEntry.
	     */
	    hitIdx = fragListCnt;
            newCount = fragListCnt + 1;
	}
	else if ( fragListCnt < CACHE_FILEFRAG_SIZE_MAX ) {
	    /*
	     * Can grow fragList[]
	     */
	    fragListAvail += CACHE_FILEFRAG_SIZE_BUMP;
	    fragList = (cachedNetfileFrag *) realloc( (char *) fragList,
					sizeof(cachedNetfileFrag) * fragListAvail);
	    /*
	     * Zero out new memory.
	     */
	    memset( fragList + (fragListAvail-CACHE_FILEFRAG_SIZE_BUMP),
		    0, CACHE_FILEFRAG_SIZE_BUMP*sizeof(cachedNetfileFrag) );
	    hitIdx = fragListCnt;
            newCount = fragListCnt + 1;
	}
	else {
	    /*
	     * Last resort - bump out the least used entry.
	     */
	    freeAndNull( fragList[hitIdx].targetHost );
	    freeAndNull( fragList[hitIdx].pathFragOrig );
	    ttfreeAndNull( fragList[hitIdx].pathFragMapped );

	    /*
	     * Since the cache is 100% full, occasionally reset
	     * everyone's cacheHit rate so entries that were only
	     * popular long ago don't get locked in.
	     */
	    if ( cacheGen++ > CACHE_FILEFRAG_RESET_PRI ) {
		cacheGen = 0;
		tmpCffP = fragList;
		for ( i = 0; i < fragListCnt; i++ ) {
		    tmpCffP->cacheHit = 1;
		    tmpCffP++;
		}
	    }
	}

	fragList[hitIdx].cacheHit = 0;		/* 0 means remap below */
    }

    if ( ! fragList[hitIdx].cacheHit ) {
	/*
	 * Need to perform mapping.
	 */
	newfile = tt_host_netfile_file( host, filename );

#ifdef _DTENV_SUPPORT_MAPERROR_CACHING
	fragList[hitIdx].targetHost = strdup ( host );
	fragList[hitIdx].pathFragOrig = strdup( filename );
	fragList[hitIdx].cacheHit = 1;
	fragList[hitIdx].pathFragMapped = newfile;

        fragListCnt = newCount;
#else
	if ( tt_ptr_error(newfile) == TT_OK ) {
	    fragList[hitIdx].targetHost = strdup ( host );
	    fragList[hitIdx].pathFragOrig = strdup( filename );
	    fragList[hitIdx].cacheHit = 1;
	    fragList[hitIdx].pathFragMapped = newfile;
            /*
             * Only change the count if we are successful in adding 
             * a new entry.
             */
            fragListCnt = newCount;
	}
	else {
	    /*
	     * Don't cache errors.   Leave this cache slot empty
	     * and it will be rediscovered and used in the future.
	     */
	    fragList[hitIdx].cacheHit = 0;

	    /*
	     * Do not change the fragListCount since we are not saving
	     * error entries.
	     */
	}
#endif /* _DTENV_SUPPORT_MAPERROR_CACHING */
    }

    /*
     * Dig out answer and return it.
     */
#ifdef _DTENV_SUPPORT_MAPERROR_CACHING
    if ( tt_ptr_error(newfile) == TT_OK )
#else
    if ( fragList[hitIdx].cacheHit )
#endif /* _DTENV_SUPPORT_MAPERROR_CACHING */
    {
	/*
	 * Return a tt_free-able copy of the answer.
	 */
	tmpStr = tt_malloc( strlen(fragList[hitIdx].pathFragMapped) + 1 );
	strcpy( tmpStr, fragList[hitIdx].pathFragMapped );

	_DtSvcProcessUnlock();
	return(tmpStr);
    }
    else {
	/*
	 * See XXX comment.
	 *
	 * Since newfile is an error code, return as is.
	 */ 
	_DtSvcProcessUnlock();
	return(newfile);
    }
}


/******************************************************************************
 ******************************************************************************
 **
 ** Environment variable mapping code.
 **
 */

/******************************************************************************
 *
 * _DtEnvGetMapList()
 *
 * Fetch the environment variable who's value is a colon separated list
 * of environment variable names who's values need to be mapped.   Then
 * break down the list into an indexable array.
 */
static char **_DtEnvGetMapList(
    char  *mapListStr,
    int   *mapListCount)
{
    char **mapList;		/* decomposition of above */
    char  *tmpPtr, *tmpPtr2;
    int    availListSize;
    int    firstTime;
    _Xstrtokparams	strtok_buf;

    /*
     * Handle NULL mapListStr.
     */
    *mapListCount = 0;
    if ( !mapListStr ) {
	return( (char **) NULL );
    }

    /*
     * Create a block of string pointers - remalloc() as needed.
     */
    availListSize = MALLOC_BUMP_SIZE;
    mapList = (char **) malloc( sizeof(char *) * (availListSize) );

    /*
     * Break up the colon separated string into an indexable array.
     */
    tmpPtr = strdup(mapListStr);		/* work copy for strtok */

    firstTime = 1;
    while (1) {
	if (firstTime) {
	    tmpPtr2 = _XStrtok( tmpPtr, ":", strtok_buf );
	    firstTime = 0;
	}
	else
	    tmpPtr2 = _XStrtok( (char *) NULL, ":", strtok_buf );

	if (tmpPtr2) {
	    /*
	     * Have possible env var name to map - make sure it exists.
	     */
	    if ( getenv(tmpPtr2) ) {
		(*mapListCount)++;
		if (*mapListCount > availListSize) {
		    availListSize += MALLOC_BUMP_SIZE;
		    mapList = (char **) realloc( (char *) mapList,
					     sizeof(char *) * (availListSize) );
	        }
		mapList[*mapListCount-1] = strdup(tmpPtr2);
	    }
	}
	else {
	    break;
	}
    }

    free(tmpPtr);

    return( (char **) mapList );
}


/******************************************************************************
 *
 * _DtEnvCleanCacheSlot()
 *
 * Free up all memory associated with a cache slot for a targetHost.
 */
static void _DtEnvCleanCacheSlot( cacheForTargetHost *targetCache )
{
    int i;


    freeAndNull( targetCache->remoteHost );

    targetCache->cacheHit = 1;

    freeAndNull( targetCache->mapListStr );

    for ( i = 0; i < targetCache->mapListCnt; i++ ) {
	freeAndNull( targetCache->mapList[i] );
	freeAndNull( targetCache->mapListDetails[i].localEnvVarCopy );
	freeAndNull( targetCache->mapListDetails[i].mappedEnvVarPtr );
	/* do not free .localEnvVarPtr - belongs to environ */
    }
    freeAndNull( targetCache->mapList );
    targetCache->mapListCnt = 0;
}


/******************************************************************************
 *
 * _DtEnvGetTargetCache()
 *
 * For a specified targetHost, find existing cache information and
 * optionally create a cache for a targetHost if one doesn't exist.
 *
 * The define MAX_HOSTS_CACHED controls how many targetHosts can
 * be cached.
 */
static cacheForTargetHost *_DtEnvGetTargetCache(
    char *targetHost,
    int   createIfNeeded)
{
    static int cacheHitGen = 0;
    static int hitIdxStart;
    int i, hitidx, hitval;


    /*
     * Handle obvious.
     */
    if (!targetHost) {
	return( (cacheForTargetHost *) NULL );
    }

    _DtSvcProcessLock();
    /*
     * Look for targetHost in current cache pool.
     */
    for ( i = 0; i < MAX_HOSTS_CACHED; i++ ) {
	if ( cachePoolG[i].remoteHost ) {
	    if ( !strcmp( targetHost, cachePoolG[i].remoteHost ) ) {
		cachePoolG[i].cacheHit++;
		break;
	    }
	}
    }

    if ( i != MAX_HOSTS_CACHED ) {
	/*
	 * targetHost is in a cache slot already.
	 */
	_DtSvcProcessUnlock();
	return( &cachePoolG[i] );
    }
    else if ( !createIfNeeded ) {
	/*
	 * No cache slot for, and we shouldn't create one either.
	 */
	_DtSvcProcessUnlock();
	return( (cacheForTargetHost *) NULL );
    }
    else {
	/*
	 * Find an empty cache slot or take over a rarely used slot.
	 */
	hitIdxStart = (hitIdxStart + 7) % MAX_HOSTS_CACHED;

	hitidx = hitIdxStart;
	hitval = cachePoolG[hitidx].cacheHit;

	for ( i = 0; i < MAX_HOSTS_CACHED; i++ ) {
	    if ( ! cachePoolG[i].remoteHost ) {
		/*
		 * Empty slot - take it.
		 */
		hitidx = i;
		break;
	    }
	    else if ( cachePoolG[i].cacheHit < hitval ) {
		hitidx = i;
		hitval = cachePoolG[i].cacheHit;
	    }
	}

	if ( cachePoolG[hitidx].remoteHost ) {
	    /*
	     * Cache was in use, clean first.
	     */
	    _DtEnvCleanCacheSlot( &cachePoolG[hitidx] );
	    cachePoolG[hitidx].remoteHost = strdup( targetHost );

	    /*
	     * Since all the slots are full, occasionally reset everyones
	     * cacheHit counters.   This gives new targetHosts a chance
	     * to compete with targetHosts that were popular long ago.
	     */
	    if ( cacheHitGen++ > 50 ) {
		for ( i = 0; i < MAX_HOSTS_CACHED; i++ ) {
		    cachePoolG[i].cacheHit = 1;
		}
		cacheHitGen = 1;
	    }
	}
	_DtSvcProcessUnlock();
	return( &cachePoolG[hitidx] );
    }
}


/******************************************************************************
 *
 * _DtEnvMapIt()
 *
 * Fill out a map cache for a single environment variable.
 */
static void _DtEnvMapIt(
    char *envVar,
    cachedEnvVar *envVarCache,
    char *targetHost)
{
    char  *separator, *tmpPtr, *tmpPtr2, swapout, *netpath;
    char  *prePend, *postPend, *newPrePend;

    char **pathList;

    int    availPathListSize, pathListCount, availEnvStrSize, len, tmpi, i;
    int    considerMapping;
    _Xstrtokparams	strtok_buf;

    /*
     * Information Layout:
     *
     *    localEnvVarPtr  = ptr to original "PATH=/users/foo:/users/bar"
     *    localEnvVarCopy = copy of original "/users/foo:/users/bar"
     *    mappedEnvVarPtr = mapped "PATH=/nfs/.../users/foo:/nfs/.../users/bar"
     */
    if ( (envVarCache->localEnvVarPtr = getenv( envVar )) ) {
	envVarCache->localEnvVarCopy = strdup( envVarCache->localEnvVarPtr );

	/* sneak back past "NAME=" portion. */
	envVarCache->localEnvVarPtr -= strlen( envVar ) + 1;
    }
    else {
	/*
	 * Nothing to map.   Punt.
	 */
	envVarCache->localEnvVarCopy = (char *) NULL;
	envVarCache->localEnvVarPtr  = (char *) NULL;
	return;
    }

#ifdef _DTENV_SUPPORT_COMMA_SEPARATED
    /*
     * Pick between colon-separated and comma-separated host-qualified
     * mapping code.
     */
    if ( !strcmp(envVar, "DTDATABASESEARCHPATH") ) {
	/*
	 * comma-separated and host-qualified mapping.
	 */
	separator = ",";
    }
    else {
	/*
	 * colon-separated mapping.
	 */
	separator = ":";
    }
#else
    separator = ":";
#endif /* _DTENV_SUPPORT_COMMA_SEPARATED */

    /*
     * Break path list into elements
     */
    availPathListSize = MALLOC_BUMP_SIZE;
    pathListCount = 0;
    pathList = (char **) malloc( sizeof(char *) * availPathListSize );

    /*
     * Break up path list into an array of path elements.
     */
    tmpPtr = strdup( envVarCache->localEnvVarCopy );		/* work copy */

    while (1) {
	if (!pathListCount)
	    tmpPtr2 = _XStrtok( tmpPtr, separator, strtok_buf );
	else
	    tmpPtr2 = _XStrtok( (char *) NULL, separator, strtok_buf );

	if (tmpPtr2) {
	    pathListCount++;
	    if (pathListCount > availPathListSize) {
		availPathListSize += MALLOC_BUMP_SIZE;
		pathList = (char **) realloc( (char *) pathList,
					sizeof(char *) * availPathListSize );
	    }
	    pathList[pathListCount-1] = strdup( tmpPtr2 );
	}
	else {
	    break;
	}
    }
    free( tmpPtr );

    /*
     * Setup new "NAME=....." string.
     */
    availEnvStrSize = strlen( envVar ) + 64;
    envVarCache->mappedEnvVarPtr = (char *) calloc( availEnvStrSize, sizeof(char) );
    strcpy( envVarCache->mappedEnvVarPtr, envVar );
    strcat( envVarCache->mappedEnvVarPtr, "=" );

    /*
     * Start mapping each path element.
     */
    for ( i = 0; i < pathListCount; i++ ) {
	prePend  = pathList[i];
	postPend = (char *) NULL;
	newPrePend = (char *) NULL;

	/*
	 * Assume we need to map this path element.
	 */
	considerMapping = 1;

#ifdef _DTENV_SUPPORT_COMMA_SEPARATED
	if ( !strcmp( separator, "," ) ) {
	    if ( DtStrchr(prePend, ':' ) ) {
		/*
		 * Host qualified elements in a comma separated list
		 * will NOT be mapped.
		 */
		considerMapping = 0;
	    }
	}
#endif /* _DTENV_SUPPORT_COMMA_SEPARATED */

	if (considerMapping) {
	    /*
	     * Tear apart and check for so called substitution characters.
	     */
	    if (( tmpPtr = DtStrchr(prePend, '%') )) {
		/*
		 * Temporarly shorten path up to substitution character.
		 */
		swapout = *tmpPtr;
		*tmpPtr = '\0';

		/*
		 * Move the dividing point back to a directory element.
		 */
		tmpPtr2 = DtStrrchr( prePend, '/' );

		/*
		 * Restore the send half of the string.
		 */
		*tmpPtr = swapout;

		if (tmpPtr2) {
		    /*
		     * Can do a split around the "/".
		     *
		     * Will have "<prePath>/" and "/<postPath>".
		     */
		    postPend = strdup( tmpPtr2 );
		    *(tmpPtr2 + mblen(tmpPtr2, MB_CUR_MAX)) = '\0';
		}
	    }

#ifdef DTENV_PERF_HOOK
	    {
		int tpi;
		extern unsigned long stopwatch_tt_file_netfile;
		extern int stopwatch_repeat_rate;

		struct timeval  start, stop;
		struct timezone junk;

		gettimeofday( &start, &junk );

		for ( tpi = 0; tpi < stopwatch_repeat_rate-1; tpi++ ) {
		    netpath = _DtEnv_tt_file_netfile( prePend );
		    if ( tt_ptr_error(netpath) == TT_OK )
			ttfreeAndNull( netpath );
		}
		netpath = _DtEnv_tt_file_netfile( prePend );

		gettimeofday( &stop, &junk );

		if (start.tv_usec > stop.tv_usec) {
		    stop.tv_usec += 1000000;
		    stop.tv_sec--;
		}

		stopwatch_tt_file_netfile += (stop.tv_usec - start.tv_usec);
		stopwatch_tt_file_netfile += (stop.tv_sec  - start.tv_sec) * 1000000;
	    }
#else
	    netpath = _DtEnv_tt_file_netfile( prePend );
#endif /* DTENV_PERF_HOOK */
	    if ( tt_ptr_error(netpath) != TT_OK ) {
		newPrePend = (char *) NULL;
	    }
	    else {
#ifdef DTENV_PERF_HOOK
		{
		    int tpi;
		    extern unsigned long stopwatch_tt_netfile_file;
		    extern int stopwatch_repeat_rate;

		    struct timeval  start, stop;
		    struct timezone junk;

		    gettimeofday( &start, &junk );

		    for ( tpi = 0; tpi < stopwatch_repeat_rate-1; tpi++ ) {
			newPrePend = _DtEnv_tt_host_netfile_file (targetHost, netpath);
			if ( tt_ptr_error(newPrePend) == TT_OK )
			    ttfreeAndNull( newPrePend );

		    }
		    newPrePend = _DtEnv_tt_host_netfile_file (targetHost, netpath);

		    gettimeofday( &stop, &junk );

		    if (start.tv_usec > stop.tv_usec) {
			stop.tv_usec += 1000000;
			stop.tv_sec--;
		    }

		    stopwatch_tt_netfile_file += (stop.tv_usec - start.tv_usec);
		    stopwatch_tt_netfile_file += (stop.tv_sec  - start.tv_sec) * 1000000;
		}
#else
		newPrePend = _DtEnv_tt_host_netfile_file (targetHost, netpath);
#endif /* DTENV_PERF_HOOK */
		if ( tt_ptr_error(newPrePend) != TT_OK ) {
		    newPrePend = (char *) NULL;
		}
		ttfreeAndNull( netpath );
	    }
	}

	/*
	 * Calculate length of the new path element to the new path list.
	 */
	tmpi = strlen(envVarCache->mappedEnvVarPtr)+1;	/* current list + ... */
	if ( i != 0 )
	    tmpi += 1;					/* separator */
	if (newPrePend)
		tmpi += strlen(newPrePend);		/* new prePend or ... */
	else
		tmpi += strlen(prePend);		/* ... old prePend */
	if (postPend)
		tmpi += strlen(postPend);		/* new postPend */

	if ( tmpi > availEnvStrSize ) {
	    /*
	     * Grow new mappedEnvVar space.
	     */
	    availEnvStrSize = tmpi + 64;
	    envVarCache->mappedEnvVarPtr = (char *) realloc(
				(char *) envVarCache->mappedEnvVarPtr,
				availEnvStrSize );
	}

	/*
	 * Add the new path element.
	 */
	if ( i != 0 )
	    strcat( envVarCache->mappedEnvVarPtr, separator );

	if (newPrePend)
	    strcat( envVarCache->mappedEnvVarPtr, newPrePend );
	else
	    strcat( envVarCache->mappedEnvVarPtr, prePend );

	if (postPend)
	    strcat( envVarCache->mappedEnvVarPtr, postPend );

	freeAndNull( prePend );		/* aka pathList[i] */
	ttfreeAndNull( newPrePend );
	freeAndNull( postPend );
    }
    freeAndNull( pathList );
}

/******************************************************************************
 *
 * _DtEnvGetMapInformation()
 */
#define _DtEnv_MAX_BUF_SIZE 1024
#define _DtEnv_NULL_GUARD(s) ((s) ? (s) : "")

static char *_DtEnvGetMapInformation( void )
{
   char        *mapInfo;
   char         nameBuf[_DtEnv_MAX_BUF_SIZE];
   char         classBuf[_DtEnv_MAX_BUF_SIZE];
   XrmValue     resource_value;
   XrmDatabase  db;
   char         *rep_type;
   int          bytesNeeded;
   char        *name;
   char        *class;

   extern char    *_DtApplicationName;		/* set in DtUtil.c */
   extern char    *_DtApplicationClass;
   extern Display *_DtDisplay;


   /*
    * See if an environment variable has been set.   If so, get
    * the map info from there.
    */
   mapInfo = getenv( _DTENV_MAP_ENV_VAR );

   if (mapInfo)
	return( XtNewString( mapInfo ) );

   /*
    * Try to get map info from the resource database.
    */
   bytesNeeded = strlen(_DTENV_MAP_RESOURCE_NAME)
                       + strlen(_DtApplicationName) + 4;
   if ( bytesNeeded > _DtEnv_MAX_BUF_SIZE )
        name = XtMalloc(bytesNeeded);
   else
        name = nameBuf;

   sprintf (name, "%s*%s",
        _DtEnv_NULL_GUARD( _DtApplicationName) , _DTENV_MAP_RESOURCE_NAME);

   bytesNeeded = strlen(_DTENV_MAP_RESOURCE_CLASS)
                       + strlen(_DtApplicationClass) + 4;
   if ( bytesNeeded > _DtEnv_MAX_BUF_SIZE )
        class = XtMalloc(bytesNeeded);
   else
        class = classBuf;
   sprintf (class, "%s*%s",
        _DtEnv_NULL_GUARD(_DtApplicationClass) , _DTENV_MAP_RESOURCE_CLASS);

   db = XtDatabase (_DtDisplay);
   if (XrmGetResource (db, nameBuf, classBuf, &rep_type, &resource_value))
      mapInfo = (char *) resource_value.addr;
   else
      mapInfo = (char *) NULL;

   if ( name != nameBuf )
        XtFree(name);
   if ( class != classBuf )
        XtFree(class);

   if (mapInfo)
	return( XtNewString( mapInfo ) );
   else
	return( (char *) NULL );
}

/******************************************************************************
 *
 * _DtEnvMapForRemote()
 *
 * Perform filename mapping on the current environment so it makes
 * sense on the target host.   The original environment is saved
 * for later restoring, and caching is used to minimize mapping
 * computations.
 */
void _DtEnvMapForRemote (char *targetHost)
{
    int    i, cacheRegen;
    char  *mapListStr, *tmpPtr;
    char **mapList;

    cacheForTargetHost *targetCache;
    int ttMark = 0;

    extern char *mostRecentRemoteHostG;

    _DtSvcProcessLock();
    if (mostRecentRemoteHostG) {
	/*
	 * Warning - a _DtEnvRestoreLocal() was not called for
	 * the most recent _DtEnvMapForRemote().  Tossing.
	 */
	freeAndNull(mostRecentRemoteHostG);
    }

    if (!targetHost) {
	/*
	 * No target host to cache.
	 */
	_DtSvcProcessUnlock();
	return;
    }

    /*
     * Performance enhancement:  Check if we can map our $HOME directory 
     * to the remote host.  If we fail because the host cannot be accessed
     * then give up the attempt to map the environment now because we'll
     * run into a lot of timeouts on remote mapping failures otherwise.
     */
    ttMark = tt_mark();
    switch ( tt_ptr_error(
                _DtEnv_tt_host_netfile_file(targetHost, 
                    _DtEnv_tt_file_netfile(getenv("HOME")) ) ) )
    {
	case TT_ERR_DBEXIST:	/* cannot contact remote host */
        case TT_ERR_DBAVAIL:	/* timeouts occur trying to make contact */
        case TT_ERR_UNIMP:	/* remote server doesn't support file naming */
	    /*
             * It will do no good to attempt to map filenames to this remote host
             * So forget it -- the user may try again later.
             */
            tt_release(ttMark); /* free up tooltalk memory used for test */
	    _DtSvcProcessUnlock();
            return;  
            break;
         default:
            tt_release(ttMark); /* free up tooltalk memory used for test */
            break;
    }


#ifdef _DTENV_OPTIMIZATION_LOCALHOST
    /*
     * _DtEnvMapForRemote() is normally called from the remote execution
     * code within libDtSvc.   If by chance it gets called when spawning
     * local processes, then a test should be done here to bypass mapping
     * environment variables for a local fork/exec.
     */
    if ( <unimplemented - targetHost is local test> ) {
	mostRecentRemoteHostG = "localhost";
	_DtSvcProcessUnlock();
	return;
    }
#endif /* _DTENV_OPTIMIZATION_LOCALHOST */

    /*
     * Get list of env vars to be mapped.
     */
    mapListStr = _DtEnvGetMapInformation();

    if (!mapListStr) {
	/*
	 * Nothing to map.
	 */
	_DtSvcProcessUnlock();
	return;
    }

    /*
     * We have a targetHost that needs some mapping done.   Start
     * stashing data away.
     */
    mostRecentRemoteHostG = strdup( targetHost );
    _DtSvcProcessUnlock();

    /*
     * Find or allocate a cache entry.
     */
    targetCache = _DtEnvGetTargetCache( targetHost, 1 );

    /*
     * See if cache information for targetHost is available, and
     * if so, if it still looks valid.
     *
     * To maximize performance, an all-or-nothing regeneration of
     * the cache will be done rather than incremental regeneration
     * of some portions of the cache.   One would expect that the
     * list of variables to map and their contents would remain
     * fairly static.
     */
    if (targetCache->mapListStr) {
	if ( !strcmp( targetCache->mapListStr, mapListStr ) ) {
	    /*
	     * Atleast the list of environment variables that need
	     * to be mapped is the same as previous.
	     */
	    cacheRegen = 0;
	    for ( i = 0; i < targetCache->mapListCnt; i++ ) {
		if (( tmpPtr = getenv(targetCache->mapList[i]) )) {
		    if ( strcmp( tmpPtr,
			     targetCache->mapListDetails[i].localEnvVarCopy) ) {
			cacheRegen = 1;	/* one map entry is no longer valid */
			break;
		    }
		}
		else {
		    /*
		     * Env Var does not exist, but maybe it never did.
		     */
		    if (targetCache->mapListDetails[i].localEnvVarCopy) {
			/*
			 * Was in cache, but now no longer exists.
			 */
			cacheRegen = 1;	/* env var no longer exists */
			break;
		    }
		}
	    }
	}
	else {
	    cacheRegen = 1;	/* map list changed - need to regen cache */
	}
    }
    else {
	cacheRegen = 1;		/* need to create cache */
    }

    if (cacheRegen) {
	/*
	 * Toss out the old.
	 */
	_DtEnvCleanCacheSlot(targetCache);

	/*
	 * Bring in the new.
	 */
	targetCache->remoteHost = strdup( targetHost );
	targetCache->mapListStr = strdup( mapListStr );
	targetCache->mapList = _DtEnvGetMapList( mapListStr,
					&(targetCache->mapListCnt) );

	targetCache->mapListDetails = (cachedEnvVar *)
					malloc( sizeof(cachedEnvVar) *
					    targetCache->mapListCnt );

	for ( i = 0; i < targetCache->mapListCnt; i++ ) {
	    _DtEnvMapIt( targetCache->mapList[i],
			 &(targetCache->mapListDetails[i]),
			 targetHost );
	}
    }
    else {
	/*
	 * We can use cached information.  Even though all the
	 * environment variable "strings" match, the users
	 * (environ **) pointers may be different, so re-cache
	 * the restoration pointers.
	 */
	for ( i = 0; i < targetCache->mapListCnt; i++ ) {
	    if (( targetCache->mapListDetails[i].localEnvVarPtr =
					getenv( targetCache->mapList[i] ) )) {
		targetCache->mapListDetails[i].localEnvVarPtr -=
					strlen( targetCache->mapList[i] ) + 1;
	    }
	}
    }

    /*
     * Install the mapped environment variables.
     */
    for ( i = 0; i < targetCache->mapListCnt; i++ ) {
	putenv( targetCache->mapListDetails[i].mappedEnvVarPtr );
    }

    XtFree(mapListStr);
}


/******************************************************************************
 *
 * _DtEnvRestoreLocal()
 *
 * Presuming a _DtEnvMapForRemote() was called, _DtEnvRestoreLocal()
 * restores the original envirnment settings for a number of
 * environment variables.
 */
void _DtEnvRestoreLocal (void)
{
    extern char   *mostRecentRemoteHostG;
    cacheForTargetHost  *targetCache;
    char          *tmpP;
    int            i;

    _DtSvcProcessLock();
    if (mostRecentRemoteHostG) {
#ifdef _DTENV_OPTIMIZATION_LOCALHOST
	/*
	 * See comment with the other ifdef'ed block.
	 *
	 * If localhost, then nothing to restore.
	 */
	if ( !strcmp(mostRecentRemoteHostG, "localhost") ) {
	    mostRecentRemoteHostG = (char *) NULL;
	    _DtSvcProcessUnlock();
	    return;
	}
#endif /* _DTENV_OPTIMIZATION_LOCALHOST */

	targetCache = _DtEnvGetTargetCache( mostRecentRemoteHostG, 0 );

	if (targetCache) {
	    /*
	     * Install the mapped environment variables.
	     */
	    for ( i = 0; i < targetCache->mapListCnt; i++ ) {
		tmpP = targetCache->mapListDetails[i].localEnvVarPtr;
		if ( tmpP ) {
		    putenv( targetCache->mapListDetails[i].localEnvVarPtr );
		}
	    }
	}

	freeAndNull( mostRecentRemoteHostG );
    }
    _DtSvcProcessUnlock();
}

