/*****
 ** ** Module Header ******************************************************* **
 ** 									     **
 **   Modules Revision 3.0						     **
 **   Providing a flexible user environment				     **
 ** 									     **
 **   File:		cmdPath.c					     **
 **   First Edition:	1991/10/23					     **
 ** 									     **
 **   Authors:	John Furlan, jlf@behere.com				     **
 **		Jens Hamisch, jens@Strawberry.COM			     **
 ** 									     **
 **   Description:	The path manipulation routines. Much of the heart of **
 **			Modules is contained in this file. These routines    **
 **			are responsible for adding and removing directories  **
 **			from given PATH-like variables.			     **
 ** 									     **
 **   Exports:		cmdSetPath					     **
 **			cmdRemovePath					     **
 ** 									     **
 **   Notes:								     **
 ** 									     **
 ** ************************************************************************ **
 ****/

/** ** Copyright *********************************************************** **
 ** 									     **
 ** Copyright 1991-1994 by John L. Furlan.                      	     **
 ** see LICENSE.GPL, which must be provided, for details		     **
 ** 									     ** 
 ** ************************************************************************ **/

static char Id[] = "@(#)$Id: 1aa261a485ed8fb00714dd83cca77b3d187368ec $";
static void *UseId[] = { &UseId, Id };

/** ************************************************************************ **/
/** 				      HEADERS				     **/
/** ************************************************************************ **/

#include "modules_def.h"
#ifdef	HAS_SYS_PARAM_H
#include <sys/param.h>
#endif

/** ************************************************************************ **/
/** 				  LOCAL DATATYPES			     **/
/** ************************************************************************ **/

/** not applicable **/

/** ************************************************************************ **/
/** 				     CONSTANTS				     **/
/** ************************************************************************ **/

#ifdef	MAXPATHLEN
#define	PATH_BUFLEN	MAXPATHLEN
#else
#define	PATH_BUFLEN	1024
#endif

/** ************************************************************************ **/
/**				      MACROS				     **/
/** ************************************************************************ **/

#define _TCLCHK(a)	\
	{if (*(TCL_RESULT(a))) ErrorLogger(ERR_EXEC,LOC,TCL_RESULT(a),NULL);}

/** ************************************************************************ **/
/** 				    LOCAL DATA				     **/
/** ************************************************************************ **/

static	char	module_name[] = "cmdPath.c";	/** File name of this module **/

#if WITH_DEBUGGING_CALLBACK
static	char	_proc_cmdSetPath[] = "cmdSetPath";
static	char	_proc_cmdRemovePath[] = "cmdRemovePath";
static	char	_proc_Remove_Path[] = "Remove_Path";
#endif

static char buffer[ PATH_BUFLEN];

/** ************************************************************************ **/
/**				    PROTOTYPES				     **/
/** ************************************************************************ **/

static int	Remove_Path( Tcl_Interp	*interp, char *variable, char *item, 
			char *sw_marker, const char *delim);


/*++++
 ** ** Function-Header ***************************************************** **
 ** 									     **
 **   Function:		cmdSetPath					     **
 ** 									     **
 **   Description:	Add the passed value (argv[2]) to the specified vari-**
 **			able (argv[1]). argv[0] specifies, if the variable   **
 **			is to be appended or prepended.  Each directory in   **
 **			the path is checked to see whether it is already     **
 **			in the path.  If so it is not added.		     **
 **			(Handles options -d C, or --delim[=]C )		     **
 ** 									     **
 **   First Edition:	1991/10/23					     **
 ** 									     **
 **   Parameters:	ClientData	 client_data			     **
 **			Tcl_Interp	*interp		According Tcl interp.**
 **			int		 argc		Number of arguments  **
 **			char		*argv[]		Argument array	     **
 ** 									     **
 **   Result:		int	TCL_OK		Successful completion	     **
 **				TCL_ERROR	Any error		     **
 ** 									     **
 **   Attached Globals:	g_flags		These are set up accordingly before  **
 **					this function is called in order to  **
 **					control everything		     **
 ** 									     **
 ** ************************************************************************ **
 ++++*/

int	cmdSetPath(	ClientData	 client_data,
	       		Tcl_Interp	*interp,
	       		int		 argc,
	       		CONST84 char	*argv[])
{
    Tcl_RegExp	chkexpPtr;			/** Regular expression for   **/
						/** marker checking	     **/
    char	 *oldpath,			/** Old value of 'var'	     **/
		 *newpath,			/** New value of 'var'	     **/
		 *sw_marker = APP_SW_MARKER,	/** arbitrary default	     **/
		 *startp=NULL, *endp=NULL,	/** regexp match endpts	     **/
		 *qualifiedpath,		/** List of dirs which 
						    aren't already in path   **/
		**pathlist;			/** List of dirs	     **/
    const char	 *delim = _colon;		/** path delimiter	     **/
    int		  append = 1,			/** append or prepend	     **/
		  numpaths,			/** number of dirs in path   **/
		  qpathlen,			/** qualifiedpath length     **/
		  arg1 = 1,			/** arg start		     **/
		  x;				/** loop index		     **/
    Tcl_Obj	 *np_obj;			/** new path Tcl Obj	     **/

#if WITH_DEBUGGING_CALLBACK
    ErrorLogger( NO_ERR_START, LOC, _proc_cmdSetPath, NULL);
#endif

    /**
     **  Whatis mode?
     **/
    if( g_flags & (M_WHATIS | M_HELP))
        goto success0;
	
    /**
     **   Check arguments. There should be at least 3 args:
     **     argv[0]  -  prepend/append
     **     ...
     **     argv[n-1]-  varname
     **     argv[n]  -  value
     **/
    if(argc < 3)
	if( OK != ErrorLogger(ERR_USAGE, LOC, argv[0],
	    " path-variable directory", NULL))
	    goto unwind0;

    /**
     **  Should this guy be removed from the variable ... If yes, do so!
     **/
    if(g_flags & M_REMOVE) 
	return( cmdRemovePath(client_data, interp, argc, argv));   /** ----> **/

    /**
     **  prepend or append. The default is append.
     **/
    if( !( append = !!strncmp( argv[0], "pre", 3)))
	sw_marker = PRE_SW_MARKER;
  
    /**
     **  Non-persist mode?
     **/
    
    if (g_flags & M_NONPERSIST) {
	return (TCL_OK);
    }

    /**
     **  Display only ... ok, let's do so!
     **/
    if(g_flags & M_DISPLAY) {
	fprintf( stderr, "%s\t ", argv[ 0]);
	while( --argc)
	    fprintf( stderr, "%s ", *++argv);
	fprintf( stderr, "\n");
        goto success0;
    }

    /**
     **  Check for the delimiter option
     **/
    if(*(argv[arg1]) == '-') {
	if (!strcmp(argv[arg1], "-d")) {
		delim = argv[arg1+1];
		arg1 += 2;
	} else if (!strcmp(argv[arg1], "--delim")) {
		delim = argv[arg1+1];
		arg1 += 2;
	} else if (!strncmp(argv[arg1], "--delim=", 8)) {
		delim = argv[arg1]+8;
		arg1++;
	}
    }

    /**
     **  Get the old value of the variable. MANPATH defaults to a configure
     **  generated value.
     **  Put a \ in front of each '.' and '+'.
     **  (this is an intentional memory leak)
     **/
    oldpath =  EMGetEnv( interp, argv[arg1]);
    _TCLCHK(interp)

    if(!oldpath || !*oldpath) {
	null_free((void *) &oldpath);
	oldpath = !strcmp( argv[arg1], "MANPATH")
		? stringer(NULL,0,DEFAULTMANPATH,NULL)
		: stringer(NULL,0,"",NULL);
    }

    /**
     **  Split the new path into its components directories so each
     **  directory can be checked to see whether it is already in the 
     **  existing path.
     **/
    if( !( pathlist = SplitIntoList( interp, (char *) argv[arg1+1], &numpaths,
	delim)))
	goto unwind0;

    /**
     **  Some space for the list of paths which
     **  are not already in the existing path.
     **/
    if((char *) NULL == (qualifiedpath = stringer(NULL,0, argv[arg1+1],
	delim, NULL)))
	if( OK != ErrorLogger( ERR_STRING, LOC, NULL))
	    goto unwind1;

    qpathlen = strlen(qualifiedpath)+1;
    *qualifiedpath = '\0';		/** make sure null for later	     **/

    for( x = 0; x < numpaths; x++) {

	regex_quote( pathlist[x], buffer, PATH_BUFLEN);

	/**
	 **  Check to see if path is already in this path variable.
	 **  It could be at the 
	 **     beginning ... ^path:
	 **     middle    ... :path:
	 **     end       ... :path$
	 **     only one  ... ^path$
	 **/
	if((char *) NULL == (newpath = stringer(NULL,0,
		"(^", buffer, delim, ")|(",delim, buffer, delim,")|(",delim,
		buffer, "$)|(^", buffer, "$)",NULL)))
	    if( OK != ErrorLogger( ERR_STRING, LOC, NULL))
		goto unwind2;

	np_obj = Tcl_NewStringObj(newpath,strlen(newpath));
	chkexpPtr = Tcl_GetRegExpFromObj(interp, np_obj, TCL_REG_ADVANCED);
	_TCLCHK(interp)
	null_free((void *) &newpath);

	/**
	 **  If the directory is not already in the path, 
	 **  add it to the qualified path.
	 **/
	if( !Tcl_RegExpExec(interp, chkexpPtr, oldpath, oldpath))
	    if (!stringer(qualifiedpath + strlen(qualifiedpath),
		    qpathlen - strlen(qualifiedpath),
		    pathlist[x], delim, NULL))
		if( OK != ErrorLogger( ERR_STRING, LOC, NULL))
		    goto unwind2;

    }					/** End of loop that checks for 
					 ** already existent path
					 **/
    /**
     **  If all of the directories in the new path already exist,
     **  exit doing nothing.
     **/
    if( ! *qualifiedpath)
	goto success1;

    /* remove trailing delimiter */
    qualifiedpath[strlen(qualifiedpath) - 1] = '\0';

    /**
     **  Some space for our newly created path.
     **  We size at the oldpath plus the addition.
     **/
    if(!(newpath = stringer(NULL, strlen( oldpath) +
	strlen(qualifiedpath) + 2,NULL)))
	if( OK != ErrorLogger( ERR_STRING, LOC, NULL))
	    goto unwind2;
    *newpath = '\0';

    /**
     **  Easy job to do, if the old path has not been set up so far ...
     **/
    if( !strcmp( oldpath, "")) {
	strcpy( newpath, qualifiedpath);

    /**
     **  Otherwise we have to take care on prepending vs. appending ...
     **  If there is a append or prepend marker within the variable (see
     **  modules_def.h) the changes are made according to this markers. Other-
     **  wise append and prepend will be relative to the strings begin or end.
     **/

    } else {

	Tcl_Obj		*sw_obj =
		Tcl_NewStringObj(sw_marker,strlen(sw_marker));
	Tcl_RegExp	 markexpPtr = Tcl_GetRegExpFromObj(interp,
		sw_obj,TCL_REG_ADVANCED);
	_TCLCHK(interp)

	strcpy( newpath, oldpath);

	if( Tcl_RegExpExec(interp, markexpPtr, oldpath, oldpath)) {
	    _TCLCHK(interp)
	    Tcl_RegExpRange(markexpPtr, 0,
		(CONST84 char **) &startp, (CONST84 char **) &endp);

	    /**
	     **  Append/Prepend marker found
	     **/
	    if( append) {
		char ch = *startp;
		*startp = '\0';
		strcpy(newpath, oldpath);
                /**
                 ** check that newpath has a value before adding delim
                 **/
		if (strlen(newpath) > 0 && newpath[strlen(newpath)-1] != *delim)
			strcat(newpath, delim);
		strcat(newpath, qualifiedpath);
		if (newpath[strlen(newpath)-1] != *delim)
			strcat(newpath, delim);
		*startp = ch;
		strcat(newpath, startp);
               
	    } else {
                char ch = *endp;
		*endp = '\0';
		strcpy(newpath, oldpath);
		if (newpath[strlen(newpath)-1] != *delim)
			strcat(newpath, delim);
		strcat(newpath, qualifiedpath);
		*endp = ch;
		strcat(newpath, endp);
	    }

	} else {

	    /**
	     **  No marker set
	     **/
	    if(append) {
		strcpy(newpath, oldpath);
		if (newpath[strlen(newpath)-1] != *delim)
			strcat(newpath, delim);
		strcat(newpath, qualifiedpath);
	    } else {
		strcpy(newpath, qualifiedpath);
		if (*oldpath != *delim)	strcat(newpath, delim);
		strcat(newpath, oldpath);
	    }

	} /** if( marker) **/

    } /** if( strcmp) **/

    /**
     **  Now the new value to be set resides in 'newpath'. Set it up.
     **/
    moduleSetenv( interp, (char *) argv[arg1], newpath, 1);
    _TCLCHK(interp)

#if WITH_DEBUGGING_CALLBACK
    ErrorLogger( NO_ERR_END, LOC, _proc_cmdSetPath, NULL);
#endif

    /**
     ** Free resources
     **/
    null_free((void *) &newpath);
success1:
    null_free((void *) &oldpath);
    null_free((void *) &qualifiedpath);
    FreeList( pathlist, numpaths);
success0:
    return( TCL_OK);			/** -------- EXIT (SUCCESS) -------> **/

unwind2:
    null_free((void *) &qualifiedpath);
unwind1:
    FreeList( pathlist, numpaths);
unwind0:
    null_free((void *) &oldpath);
    return( TCL_ERROR);			/** -------- EXIT (FAILURE) -------> **/

} /** End of 'cmdSetPath' **/

/*++++
 ** ** Function-Header ***************************************************** **
 ** 									     **
 **   Function:cmdRemovePath					     **
 ** 									     **
 **   Description:	Remove the passed value (argv[2]) from the specified **
 **			variable (argv[1]). In case of switching this pro-   **
 **			cedure removes markers from the path, too. argv[0]   **
 **			specifies, if the append- or prepend-marker is af-   **
 **			fected						     **
 ** 									     **
 **   First Edition:	1991/10/23					     **
 ** 									     **
 **   Parameters:	ClientData	 client_data			     **
 **			Tcl_Interp	*interp		According Tcl interp.**
 **			int		 argc		Number of arguments  **
 **			char		*argv[]		Argument array	     **
 ** 									     **
 **   Result:		int	TCL_OK		Successful completion	     **
 **				TCL_ERROR	Any error		     **
 ** 									     **
 **   Attached Globals:	g_flags		These are set up accordingly before  **
 **					this function is called in order to  **
 **					control everything		     **
 ** 									     **
 ** ************************************************************************ **
 ++++*/

int	cmdRemovePath(	ClientData	 client_data,
	       		Tcl_Interp	*interp,
	       		int		 argc,
	       		CONST84 char	*argv[])
{
    char	 *sw_marker = APP_SW_MARKER,	/** arbitrary default	     **/
		**pathlist;			/** List of dirs	     **/
    const char	 *delim = _colon;		/** path delimiter	     **/
    int		  numpaths,			/** number of dirs in path   **/
		  arg1 = 1,			/** arg start		     **/
		  x;				/** loop index		     **/


#if WITH_DEBUGGING_CALLBACK
    ErrorLogger( NO_ERR_START, LOC, _proc_cmdRemovePath, NULL);
#endif

    /**
     **   Check arguments. There should be give 3 args:
     **     argv[0]  -  prepend/append/remove-path
     **     ...
     **     argv[n-1]-  varname
     **     argv[n]  -  value
     **/
    if(argc < 3)
	if( OK != ErrorLogger(ERR_USAGE,LOC,argv[0],"path-variable directory",
	    NULL))
	    goto unwind0;

  
    /**
     **  Non-persist mode?
     **/
    
    if (g_flags & M_NONPERSIST) {
	return (TCL_OK);
    }

    /**
     **  Display only ... ok, let's do so!
     **/
    if(g_flags & M_DISPLAY) {
	fprintf( stderr, "%s\t ", argv[ 0]);
	while( --argc)
	    fprintf( stderr, "%s ", *++argv);
	fprintf( stderr, "\n");
        goto success0;
    }
  
    /**
     **  prepend or append. The default is append.
     **/
    if( ! strncmp( argv[0], "pre", 3))
	sw_marker = PRE_SW_MARKER;
  
    /**
     **  Check for the delimiter option
     **/
    if(*(argv[arg1]) == '-') {
	if (!strcmp(argv[arg1], "-d")) {
		delim = argv[arg1+1];
		arg1 += 2;
	} else if (!strcmp(argv[arg1], "--delim")) {
		delim = argv[arg1+1];
		arg1 += 2;
	} else if (!strncmp(argv[arg1], "--delim=", 8)) {
		delim = argv[arg1]+8;
		arg1++;
	}
    }

    /**
     ** For switch state3, we're looking to remove the markers.
     **/
    if( g_flags & M_SWSTATE3) 
	argv[arg1+1] = sw_marker;

    /**
     **  Split the path into its components so each item can be removed
     **  individually from the variable.
     **/
    if( !( pathlist = SplitIntoList( interp, (char *) argv[arg1+1], &numpaths,
	delim)))
	goto unwind0;

    /**
     ** Remove each item individually
     **/
    for( x = 0; x < numpaths; x++)
	if(TCL_OK != Remove_Path(interp,(char *) argv[arg1],pathlist[x],
	sw_marker, delim))
	    goto unwind1;

#if WITH_DEBUGGING_CALLBACK
    ErrorLogger( NO_ERR_END, LOC, _proc_cmdRemovePath, NULL);
#endif

    /**
     ** Free resources
     **/
    FreeList(pathlist, numpaths);

success0:
    return( TCL_OK);			/** -------- EXIT (SUCCESS) -------> **/

unwind1:
    FreeList(pathlist, numpaths);
unwind0:
    return( TCL_ERROR);			/** -------- EXIT (FAILURE) -------> **/

} /** End of 'cmdRemovePath' **/

/*++++
 ** ** Function-Header ***************************************************** **
 ** 									     **
 **   Function:		Remove_Path					     **
 ** 									     **
 **   Description:	This function actually does the work of removing     **
 **			the item from the path.  It is done this way to      **
 **			support multiple items (often directories)	     **
 **			separated by colons in the variable value.	     **
 ** 									     **
 **   First Edition:	2001/08/08					     **
 ** 									     **
 **   Parameters:	Tcl_Interp	*interp		According Tcl interp.**
 **			char		*variable	Variable from which  **
 **							to remove item	     **
 **			char		*item		Item to remove	     **
 **			char		*sw_marker	Switch marker	     **
 **			const char	*delim		path delimiter	     **
 ** 									     **
 **   Result:		int	TCL_OK		Successful completion	     **
 **				TCL_ERROR	Any error		     **
 ** 									     **
 **   Attached Globals:	g_flags		These are set up accordingly before  **
 **					this function is called in order to  **
 **					control everything		     **
 ** 									     **
 ** ************************************************************************ **
 ++++*/

static int Remove_Path(
	Tcl_Interp * interp,
	char *variable,
	char *item,
	char *sw_marker,
	const char *delim
) {
	char           *oldpath,	/** current path  **/
	               *olditem;	/** item from path **/
	int             i,		/** counter **/
	                found = 0,	/** flag to indicate item was found **/
			pcount = 0,	/** count of items in path **/
			addsep = 0,	/** flag to add separator **/
			marked = 0,	/** flag path contains sw_marker **/
			oldpathlen = 0;
	Tcl_DString     _newpath;
	Tcl_DString    *newpath = &_newpath;
	Tcl_DStringInit(newpath);
    /**
     **  Get the current value of the "PATH" environment variable
     **/
	oldpath = (char *) EMGetEnv(interp, variable);
	if (!oldpath || !*oldpath) {
		null_free((void *) &oldpath);
		_TCLCHK(interp);
		goto success0;		/** -------- EXIT (SUCCESS) -------> **/
	}
	/* copy oldpath to not mess with the TCL value of env(PATH) */
	if (!(oldpath = stringer(NULL,0, oldpath, NULL)))
		if (OK != ErrorLogger(ERR_STRING, LOC, NULL))
			goto unwind0;

	/* get length of oldpath before it gets modified by xstrtok */
	oldpathlen = strlen(oldpath);

	/* determine if sw_marker is in the path */
	olditem = xstrtok(oldpath, delim);
	while (olditem) {
		if (!strcmp(olditem, sw_marker)) {
			marked = 1;
		}
		pcount++;
		olditem = xstrtok(NULL, delim);
	}

	/** pointer arithmetic on oldpath
	 ** if olditem starts at terminating null string of oldpath,
	 ** it means the last character in oldpath was ":", meaning
	 ** the last element was the empty string.  use <= to catch
	 **  this case and process the last empty element
	 */
	for (olditem = oldpath; olditem <= oldpath + oldpathlen;
	     olditem += strlen(olditem) + 1) {

		if (strcmp(olditem, item)) {
			/* not the droids we're looking for */
			if (Tcl_DStringLength(newpath)) {
				if (!Tcl_DStringAppend(newpath, delim, 1))
					if (OK !=
					    ErrorLogger(ERR_STRING, LOC, NULL))
						goto unwind1;
			}
			if (!Tcl_DStringAppend(newpath, olditem, -1))
				if (OK != ErrorLogger(ERR_STRING, LOC, NULL))
					goto unwind1;
		} else {
			/* bingo! Don't add it to new path  */
			found++;

			if ((g_flags & M_SWSTATE1) && !marked) {
			/**
			 **  In state1, we're actually replacing old paths with
			 **  the markers for future appends and prepends.
			 **  
			 **  We only want to do this once to mark the location
			 **  the module was formed around.
			 **/
				marked = 1;
				if (Tcl_DStringLength(newpath)) {
					if (!Tcl_DStringAppend
					    (newpath, delim, 1))
						if (OK !=
						    ErrorLogger(ERR_STRING, LOC,
								NULL))
							goto unwind1;
				}
				if (!Tcl_DStringAppend(newpath, sw_marker, -1))
					if (OK !=
					    ErrorLogger(ERR_STRING, LOC, NULL))
						goto unwind1;
			}
		}
	}

	if (!found) {
		goto success1;
	}

	if (Tcl_DStringLength(newpath)) {
		/**
		**  Cache the set.  Clear the variable from the unset table just
		**  in case it was previously unset.
		**/
		store_hash_value(setenvHashTable, variable,
			Tcl_DStringValue(newpath));
		clear_hash_value(unsetenvHashTable, variable);

		/**
		**  Store the new PATH value into the environment.
		**/
		(void) EMSetEnv( interp, variable, Tcl_DStringValue(newpath));

		_TCLCHK(interp);
	} else {
		/**
		 **  In this case, I should go ahead and unset the variable
		 **  from the environment because I'm removing the very last
		 **  path.
		 **
		 **  First I'm going to clear the variable from the
		 **  setenvHashTable just in case its already been altered
		 **  and had a significant value at the time. It's very
		 **  possible that I'm removing the only two or three paths
		 **  from this variable. If that's the case, then all the
		 **  earlier paths were marked for output in this hashTable.
		 **
		 **  Secondly, I actually mark the the environment variable
		 **  to be unset when output.
		 **/
		clear_hash_value(setenvHashTable, variable);
		moduleUnsetenv(interp, variable);

		/**
		 **  moduleUnsetenv doesn't unset the variable in the Tcl
		 **  space because the $env variable might need to be
		 **  used again in the modulefile for locating other
		 **  paths.  BUT, since this was a path-type environment
		 **  variable, the user is expecting this to be empty
		 **  after removing the only remaining path.  So, I set
		 **  the variable empty here.
		 **/
		(void) EMSetEnv( interp, variable, "");
		_TCLCHK(interp);
	}

/**
 **  Free what has been used and return on success
 **/
success1:
	null_free((void *)&oldpath);
success0:
	Tcl_DStringFree(newpath);
	return (TCL_OK);		/** -------- EXIT (SUCCESS) -------> **/

unwind1:
	null_free((void *)&oldpath);
unwind0:
	Tcl_DStringFree(newpath);
	return (TCL_ERROR);		/** -------- EXIT (FAILURE) -------> **/

} /** End of 'Remove_Path' **/
