/* misc.c
   miscellaneous utility functions. */

     /*---------------------------------------------------------------*/
     /* Xgopher        version 1.3     08 April 1993                  */
     /*                version 1.2     20 November 1992               */
     /*                version 1.1     20 April 1992                  */
     /*                version 1.0     04 March 1992                  */
     /* X window system client for the University of Minnesota        */
     /*                                Internet Gopher System.        */
     /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
     /*                Computing and Communications Services Office   */
     /* Copyright 1992, 1993 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 "conf.h"
#include "gopher.h"
#include "globals.h"
#include "appres.h"
#include "itemList.h"
#include "dirList.h"
#include "misc.h"

#include "osdep.h"


/* 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 == getCurrentDir()) 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;
}


/* getTempFile
   generate a temperary file name and return a pointer to it.
   The caller should check for a NULL file pointer.
   (The Unix routines mktemp(), tmpnam(), or tempnam() could be
   used as the body of this routine.) */

void
getTempFile(tempName)
char    *tempName;
{
	static int	fileCount = 0;
	static int	thisPID = 0;

	if (thisPID == 0) thisPID = getpid();
	sprintf (tempName, "%s/gop%d.%d",
			tildePath(appResources->tempDirectory),
			thisPID, fileCount++);

	return;
}


/* These symbols are on most, but not all flavors of Unix, usually
   in <sys/stat.h> or <sys/mode.h>.  They are defined here just in
   case they are missing.  The symbols here are the POSIX names. */

#ifndef S_IWUSR
#ifdef	S_IWRITE
#define         S_IWUSR S_IWRITE /* write permission, owner */
#else	/* !def S_IWRITE */
#define         S_IWUSR 0000200 /* write permission, owner */
#endif	/* def S_IWRITE */
#endif

#ifndef S_IWGRP
#define         S_IWGRP 0000020 /* write permission, group */
#endif

#ifndef S_IWOTH
#define         S_IWOTH 0000002 /* write permission, other */
#endif


/* isWriteableMode
   return true if the given path name is that of an writable directory */

BOOLEAN
isWriteableMode(statBuf)
struct stat	*statBuf;
{
	int	myUid, myGid;

	if (statBuf->st_mode & S_IWOTH) {
		return TRUE;
	}

	myGid = (int) getgid();
	if ((statBuf->st_gid == myGid) && (statBuf->st_mode & S_IWGRP)) {
		return TRUE;
	}

	myUid = (int) getuid();
	if (statBuf->st_uid == myUid  &&  statBuf->st_mode & S_IWUSR) {
		return TRUE;
	}

	return FALSE;
}

/* 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


/* isExecutable
   return true if the given path name is that of an executable file */

BOOLEAN
isExecutable(pathName)
char	*pathName;
{
	struct stat	buf;
	int	myUid, myGid;

	if (stat (pathName, &buf) != 0) return FALSE;

	if (buf.st_mode & S_IXOTH) {
		return TRUE;
	}

	myGid = (int) getgid();
	if ((buf.st_gid == myGid) && (buf.st_mode & S_IXGRP)) {
		return TRUE;
	}

	myUid = (int) getuid();
	if (buf.st_uid == myUid  &&  buf.st_mode & S_IEXEC) {
		return TRUE;
	}

	return FALSE;
}


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

char	**
makeWordList(string)
char	*string;
{
	char	*wordSpace;
	char	**word;
	char	*cp;
	char	*wp;
	int	next = 0;
	int	n = 0;

	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;
	}

	cp = string;
	while (*cp != '\0') {
		while ((! isalnum(*cp)) && *cp != '\0' ) cp++;
		if ( *cp == '\0' ) break;
		wp = word[n] = &(wordSpace[next]);
		while ( isalnum(*cp) ) {
			*wp++ = isupper(*cp) ? tolower(*cp) : *cp;
			cp++;
		}
		*wp = '\0';

		if ( strcmp( word[n], "and") == 0  ||
		     strcmp(word[n], "not") == 0 ||
		     strcmp(word[n], "or") == 0) {
			continue;
		}

		next = next + strlen( word[n]) + 1;
		n++;
	}


	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 routine or just specify absolute path names everywhere. */

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	path[PATH_NAME_LEN], tempbuf[PATH_NAME_LEN];
	char	*scan, *dir;
	char	*name, *pathVar;
	struct stat	buf;
	char	*absPathName = (char *) malloc (PATH_NAME_LEN);
   
	/* 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;

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

			if (isExecutable(name)) {
				strcpy (absPathName, name);
				return (absPathName);
			}
		}
		return (NULL);
	}
}


#include <pwd.h>
/* tildePath
   expand a leading "~/" on a pathname to be the value of $HOME/.
   The value returned is a pointer to static storage and must
   be copied by the caller to preserve it.   If there is no leading "~/",
   then the input pathname is copied to the static storage. */

char *
tildePath(path)
char	*path;
{
	char		*home;
	static char	newPath[PATH_NAME_LEN];

	*newPath = '\0';

	if (path != NULL) {
	    if (*path == '~') {
	        if (*(path+1) == '/'  ||  *(path+1) == NULLC) {
		    if ((home = getenv("HOME")) != NULL) {
				strcpy(newPath, home);
		    }
		    path++;
		} else {
		    char *slash = index (path+1, '/');
		    struct passwd   *pwdentry;
		    if (slash != NULL) {
			*slash = '\0';
		        if ((pwdentry = getpwnam(path+1)) != NULL) {
			    strcpy(newPath, pwdentry->pw_dir);
			    path = slash;
		        }
			*slash = '/';
		    } else {
		        if ((pwdentry = getpwnam(path+1)) != NULL) {
			    strcpy(newPath, pwdentry->pw_dir);
			    path += strlen(path);
		        }
		    }
		}
	    }
	    strcat(newPath, path);
	}

	return newPath;
}





/* 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;
		}
	}
}
