/*
 *	Copyright 1990 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

#include "hostenv.h"
#include <stdio.h>
#if HAVE_STRING_H || STDC_HEADERS
# include <string.h>
#else
# include <strings.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <sys/file.h>
#include "shconfig.h"
#include "flags.h"
#include "interpret.h"
#include "listutils.h"
#include "io.h"		/* redefines stdio routines */
#include "splay.h"

extern char *getenv();
extern int errno;
extern int pathcmp();
extern struct conscell *envarlist;
extern struct sptree *spt_searchpath;
extern void sp_null();
extern void *tmalloc();

/*
 * Take a path specification, e.g. ":/bin:/usr/bin" and iterate through it
 * composing "./name" "/bin/name" and "/usr/bin/name" in the path buffer.
 * State is maintained by returning the next position pointer in near-proper
 * co-routine fashion.
 */

char *
prepath(pathspec, name, buf, buflen)
	register char *pathspec, *name;
	char *buf;
	register int buflen;
{
	register char *cp;

	if (pathspec == NULL || *pathspec == '\0')
		return NULL;
	cp = buf;
	while (buflen > 2 && *pathspec != '\0' && *pathspec != ':')
		*cp++ = *pathspec++, --buflen;
	if (cp == buf)
		*cp++ = '.', --buflen;
	if (name != NULL) {	/* name == 0 means we're dealing with files */
		*cp++ = '/', --buflen;
		while (buflen > 1 && *name != '\0')
			*cp++ = *name++, --buflen;
		if (*name != '\0') {
			fprintf(stderr, "prepath: path too long\n");
			return NULL;
		}
	}
	*cp = '\0';
	if (*pathspec == '\0')
		return NULL;
	return ++pathspec;
}

/*
 * Maintain a quick index to the 'first place' to look for a unix command.
 * The method is to keep a pointer into the PATH string value, namely to
 * the directory the command seems to be in.  This pointer is then returned
 * to the caller who uses it instead of the PATH string value itself, to
 * feed to a prepath()-based iteration.  That way even if the command isn't
 * found in the indicated directory, the directories afterwards will still
 * be searched.  If that fails, the caller should use the PATH string value
 * until prepend() returns the same pointer that hashpath() did.  Because
 * the stuff hashpath() stashes away is invalidated by resetting path,
 * it is a good idea to call path_flush() to reset the hash tables when this
 * happens.
 */

char *
path_hash(command)
	char *command;
{
	char *dir, *odir, *path;
	spkey_t n;
	struct conscell *d;
	struct spblk *spl;
	u_int pathlen;

	if (!isset('h'))
		return NULL;
	if (command == NULL || *command == '\0')
		return NULL;
	if (strchr(command, '/') != NULL)
		return NULL;
	n = symbol((u_char *)command);
	if ((spl = sp_lookup(n, spt_searchpath)) != NULL
	    && (dir = (char *)spl->data) != NULL)
		return dir;
	d = v_find("PATH");
	if (d == NULL || cdr(d) == NULL || LIST(cdr(d)))
		return NULL;	/* try doing a real execvp() */
	dir = (char *)cdr(d)->string;
	pathlen = strlen(dir)+strlen(command)+1+1;
#ifdef	xxUSE_ALLOCA
	path = alloca(pathlen);
#else
	path = emalloc(pathlen);
#endif
	while (dir != NULL) {
		odir = dir;
		dir = prepath(dir, command, path, pathlen);
#ifndef	X_OK
#define	X_OK	1
#endif
		if (access(path, X_OK) == 0) {
			sp_install(n, (u_char *)odir, 0, spt_searchpath);
#ifndef	xxUSE_ALLOCA
			free(path);
#endif
			return odir;
		}
	}
#ifndef	xxUSE_ALLOCA
	free(path);
#endif
	return NULL;	/* can't find the thing! use errno for details */
}

/* flush all the cached command locations */

void
path_flush()
{
	if (isset('h'))
		sp_null(spt_searchpath);
}

int
execvp(command, argv)
	const char *command;
	char *const *argv;
{
	char *dir, *odir, *path;
	struct conscell *d;
	struct spblk *spl;
	u_int pathlen;

	if (command == NULL || *command == '\0') {
		errno = EINVAL;
		return -1;
	} else if (strchr(command, '/') != NULL) {
		return execv(command, argv);
	} else if (!isset('h')) {
		dir = odir = NULL;
	} else if ((dir = path_hash(command)) == NULL) {
		dir = getenv("ZSHPATH");
		odir = NULL;
	} else {
		/* printf("found hash: '%s'\n", dir); */
		odir = dir;
	}
	if (dir != NULL) {
		pathlen = strlen(dir)+strlen(command)+1+1;
#ifdef	xxUSE_ALLOCA
		path = alloca(pathlen);
#else
		path = emalloc(pathlen);
#endif
		while (dir != NULL) {
			dir = prepath(dir, command, path, pathlen);
			/* printf("execv '%s'\n", path); */
			execv(path, argv);
		}
#ifndef	xxUSE_ALLOCA
		free(path);
#endif
	}
	if ((d = v_find("PATH")) != NULL && cdr(d) != NULL && STRING(cdr(d))) {
		if (odir != NULL) {
			/* Hmm, wasn't in the hashed location, clear that out */
			spl = sp_lookup(symbol((u_char *)command),
					spt_searchpath);
			if (spl != NULL)
				sp_delete(spl, spt_searchpath);
			/*
			 * We shouldn't rehash right now or we might get
			 * into loops trying to use the result.
			 */
		}
		dir = (char *)cdr(d)->string;
		pathlen = strlen(dir)+strlen(command)+1+1;
#ifdef	xxUSE_ALLOCA
		path = alloca(pathlen);
#else
		path = emalloc(pathlen);
#endif
		while (dir != NULL && dir != odir) {
			dir = prepath(dir, command, path, pathlen);
			/* printf("reexecv '%s'\n", path); */
			execv(path, argv);
		}
#ifndef	xxUSE_ALLOCA
		free(path);
#endif
	}
	/* oh well... */
	return -1;
}

int
execv(command, argv)
	const char *command;
	char *const *argv;
{
	register struct conscell *scope, *l;
	register int n, len;
	register char **envp, *buf;

	if (envarlist == NULL)
		abort();
	for (scope = car(envarlist); cdr(scope) != NULL; scope = cdr(scope))
		continue;
	for (n = 1, len = 0, l = car(scope); l != NULL; l = cdr(l))
		if (STRING(l))
			++n, len += strlen((char *)l->string);
	envp = (char **)tmalloc((n/2 + 1) * sizeof (char *));
	buf = (char *)tmalloc(len + n /* terminating NUL */ + n /* = */);
	for (n = 0, l = car(scope); l != NULL; l = cddr(l)) {
		if (!(STRING(l) && cdr(l) != NULL && STRING(cdr(l))))
			continue;
		envp[n++] = buf;
		strcpy(buf, (char *)l->string);
		buf += strlen((char *)l->string);
		*buf++ = '=';
		strcpy(buf, (char *)cdr(l)->string);
		buf += strlen((char *)cdr(l)->string);
		*buf++ = '\0';
	}
	qsort(envp, n, sizeof envp[0], pathcmp);
	envp[n] = NULL;

	n = execve(command, argv, envp);

	if (errno == ENOEXEC) {	/* maybe the kernel doesn't understand #! */
		int nargs;
		char **av;

		for (nargs = 0; argv[nargs] != NULL; ++nargs)
			continue;
		av = (char **)tmalloc((nargs + 2) * sizeof argv[0]);
		av[0] = "sh";
		for (nargs = 0; argv[nargs] != NULL; ++nargs)
			av[nargs+1] = argv[nargs];
		av[nargs+1] = NULL;
		execve("/bin/sh", av, envp);
		errno = ENOEXEC;
	}
	return n;
}
