/* misc.c
   miscellaneous utility functions. */


     /*---------------------------------------------------------------*/
     /* Xgopher        version 1.1     20 April 1991                  */
     /*                version 1.0     04 March 1991                  */
     /* X window system client for the University of Minnesota        */
     /*                                Internet Gopher System.        */
     /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
     /*                Computing Services Office                      */
     /* Copyright 1992 by                                             */
     /*           the Board of Trustees of the University of Illinois */
     /* Permission is granted to freely copy and redistribute this    */
     /* software with the copyright notice intact.                    */
     /*---------------------------------------------------------------*/


#include <stdio.h>
#include <sys/time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

#include "conf.h"
#include "gopher.h"
#include "globals.h"
#include "resources.h"
#include "list.h"
#include "misc.h"

/* try to find malloc's decl */

#if defined(_POSIX_SOURCE)
#include <stdlib.h>
#else
#  if !defined(NeXt) && !defined(sequent) && !defined(__bsdi__) && !defined(unix)
#include <malloc.h>
#  else 
char	*malloc();
char	*realloc();
#  endif
#endif



/* timeNow
   return the current time in some useful units, e.g., seconds. */

gopherTime
timeNow()
{
	struct timeval	tp;
	struct timezone	tzp;

	gettimeofday(&tp, &tzp);
	return tp.tv_sec;
}



/* timeSince
   return the units (e.g., seconds) of time elapsed since the given time. */

gopherTime
timeSince(t)
gopherTime	t;
{
	struct timeval	tp;
	struct timezone	tzp;

	return (timeNow() - t);
}


/* setDirTime
   set the creation time field on a gopher directory */

void
setDirTime(d)
gopherDirP	d;
{
	d->created = timeNow();
	return;
}


/* clearDirWhenOld
   cause a directory to be updated if it's creation time was a while ago. */

void
clearDirWhenOld(d)
gopherDirP	d;
{
	if (d == NULL  || d == getCurrentDirectory()) return;

	if (d->created == NOT_LOADED) return;

	if (timeSince(d->created) > appResources->directoryTime) {
		LOG(logFP, "Freeing old directory: %s\n",
						USER_STRING(d->selectorItem));
		freeItemList(&(d->contents));
		d->created = NOT_LOADED;
	}

	return;
}


/* makeWordList
   turn a string of words into a list of words, ignoring reserved words. */

char	**
makeWordList(string)
char	*string;
{
	char	*c, *start=string;
	char	*wordSpace;
	char	**word;
	int	next = 0;
	int	n, len, i;

	if (string == NULL) return NULL;

	if ( ((wordSpace = malloc(INDEX_WORD_LEN)) == NULL)  ||
	     ((word = (char **) malloc(INDEX_WORD_COUNT * sizeof(char *))) ==
						      NULL) ) {
	  fprintf(stderr,
	      "Memory allocation error; trying to make index word list.\n");
	  fprintf(stderr,
	      "Recovery attempt by ignoring word list.\n");
	  return NULL;
	}

	n = 0;
	while (*start != NULL) {
		while (! isalnum(*start) ) start++;
		c = start + 1;
		while ( isalnum(*c) ) c++;

		len = c - start;
		word[n] = &(wordSpace[next]);
		for (i=0; i<len; i++) {
			word[n][i] = isupper(*start) ? tolower(*start) : *start;
			start++;
		}
		word[n][i] = '\0';

		if (len == 3  &&
			(strncmp(word[n], "and", 3) == 0  ||
			 strncmp(word[n], "not", 3) == 0)) {
			start = c;
			continue;
			}
		if (len == 2  &&  strncmp(word[n], "or", 2) == 0) {
			start = c;
			continue;
			}

		next = next + len + 1;
		n++;

		start = c;
	}


	if (n == 0) {
		free (wordSpace);
		free (word);
		return NULL;
	}

	word[n] = NULL;
	return (word);
}


/* freeWordList
   free a word list created by makeWordList. */

void
freeWordList(list)
char	**list;
{
	if (list != NULL  &&  list[0] != NULL) {
		free (list[0]);
		free (list);
	}
}


/* nextInPath 
   is an auxiliary function to support cmdPath (below) */

static char *
nextInPath (scan)
   char **scan;
   {
      char *pc, *start;
   
      /*---Null scan pointer says we are done---*/

      if (*scan == 0) return(0);
   
      /*---Advance scan pointer until ':' or '\0'---*/

      for (pc=(*scan); (*pc != ':') &&
             (*pc != '\0'); pc++) ;
   
      /*---Remember where we started---*/

      start = *scan;
   
      /*---Did we get to the end of the path string?---*/

      if (*pc == '\0') {
         /*---Yes, tell the next time that we're done---*/
         *scan = 0;
      } else {
         /*---No, make the ':' into a NULL (terminates returned string)
             and advance next scan to char after ':'---*/
         *pc = '\0';
         *scan = pc+1;
      }
   
      return (start);
   }


/* cmdPath
   this code finds the path to an executable program using the user's
   search path.   If an absolute path name is provided, it is used.
   Otherwise, we use envp to find the $PATH environment
   variable; gets its value; then looks for the program name in
   that path.  The absolute path name of the first executable program
   in the path is returned or NULL if none is found with execute
   permission.
   
   This routine is dependent on the Unix operating system.  Mostly
   because the notion of what constitutes an executable file is
   OS specific.  If you have porting problems, you can either rewrite
   this routing or just specify absolute path names everywhere. */

/* These symbols are on most, but not all flavors of Unix, usually
   in <sys/stat.h>.  They are defined here just in case they are missing.
   convex doesn't even have S_IEXEC.  */

#ifndef S_IEXEC
#define         S_IEXEC 0000100 /* execute/search permission, owner */
#endif

#ifndef S_IXGRP
#define         S_IXGRP 0000010 /* execute/search permission, group */
#endif

#ifndef S_IXOTH
#define         S_IXOTH 0000001 /* execute/search permission, other */
#endif

char *
cmdPath (pgmName)
char	*pgmName;
{
	/* cmdPath uses stat(2) to look for the executable file by trying
	   each directory in the users path.  Each time that fails, try the
	   next directory in the path.  The first time it succeeds, return
	   the path.  If the name is not found at all in the path, then
	   return NULL.  */

	char	*getenv();

	char	path[PATH_NAME_LEN], tempbuf[PATH_NAME_LEN];
	char	*scan, *dir;
	char	*name, *pathVar;
	struct stat	buf;
	char	*absPathName = (char *) malloc (PATH_NAME_LEN);
	int	myUid, myGid;
   
	/* according to the man page for sh(1), if the command name
	 contains a '/', then the $path variable is not searched. */
   
	if (index (pgmName, '/')) {
		strcpy (absPathName, pgmName);
		return (absPathName);
	} else {
		if (((pathVar = getenv ("PATH")) == NULL)  ||
		     (*pathVar == '\0')) {
			strcpy (absPathName, "./");
			strcat (absPathName, pgmName);
			return (absPathName);
		}
		strncpy (path, pathVar, PATH_NAME_LEN);
		*absPathName = '\0';
   
		scan = path;
		myUid = (int) getuid();
		myGid = (int) getgid();

		while( (dir = nextInPath (&scan)) != 0 ) {
			strncpy (tempbuf, dir, PATH_NAME_LEN);
			strcat (tempbuf, "/");
			name = strcat (tempbuf, pgmName);

			if (stat (name, &buf) != 0) continue;

			if (buf.st_mode & S_IXOTH) {
				strcpy (absPathName, name);
				return (absPathName);
			}
			if ((buf.st_gid == myGid) && (buf.st_mode & S_IXGRP)) {
				strcpy (absPathName, name);
				return (absPathName);
			}
			if (buf.st_uid == myUid  &&  buf.st_mode & S_IEXEC) {
				strcpy (absPathName, name);
				return (absPathName);
			}
		}
		return (NULL);
	}
}



/* vStringSet
   set the value of a variable length string, length and data.

   This routine was inspired by those in STRstring.* from the
   gopher Unix client, version 0.9.  The main reason for changing
   them at all is that the chosen data type name in the original
   routines, String, conflicts with the X Window System data
   type of the same name, but different type.

   For our purposes it is not worth allocating less than 128
   bytes.  The primary use is for selector strings.  These will 
   generally grow , but eventually stabalize around 100 bytes.
   Except for WAIS searches, when they will grow REALLY big. */

void vStringSet(vs, value)
vString *vs;
char	*value;
{
	int	need;

	if (value == NULL) return;

	if (*value == '\0') 
		need = 1;
	else
		need = strlen(value) + 1;
	
	if (vs->data == NULL) {
		if (need < 128) need = 128;
		if ((vs->data = malloc (need * sizeof(char *))) == NULL) {
			fprintf (stderr,
				"Cannot allocate more memory for a string.");
			exit(1);
		}
		strcpy (vs->data, value);
		vs->len = need;
	} else {
		if (vs->len >= need)
			strcpy(vs->data, value);
		else {
			if ((vs->data = realloc(vs->data, need)) == NULL) {
				fprintf (stderr,
				  "Cannot allocate more memory for a string.");
				exit(1);
			}

			strcpy(vs->data, value);
			vs->len = need;
		}
	}
}
