/*
 * built-in Korn commands: c_*
 */

#ifndef lint
static char *RCSid = "$Id: c_ksh.c,v 1.4 93/05/05 21:16:14 sjg Exp $";
#endif

#include "stdh.h"
#include <errno.h>
#include <setjmp.h>
#include "sh.h"


int
c_hash(wp)
	register char **wp;
{
	register struct tbl *tp, **p;

	wp++;
	if (*wp == NULL) {
		for (p = tsort(&commands); (tp = *p++) != NULL; )
			if ((tp->flag&ISSET))
				printf("%s\n", tp->val.s);
		return 0;
	}

	if (strcmp(*wp, "-r") == 0)
		flushcom(1);
	while (*wp != NULL)
		findcom(*wp++, 1);
	return 0;
}

int
c_cd(wp)
	register char **wp;
{
	char path [PATH];
	char newd [PATH];
	register char *cp;
	register char *dir;
	register char *cdpath;
	register char *rep;
	register char *pwd = NULL, *oldpwd = NULL;
	register int done = 0;
	register int prt = 0;
	register struct tbl *v_pwd = NULL, *v_oldpwd = NULL;

	if ((dir = wp[1]) == NULL && (dir = strval(global("HOME"))) == NULL)
		errorf("no home directory");

	v_pwd = global("PWD");
	if ((pwd = strval(v_pwd)) == null) {
		setstr(v_pwd, getcwd(path, (size_t)PATH));
		pwd = strval(v_pwd);
	}

	if (wp[1] != NULL && (rep = wp[2]) != NULL) {
		/*
		 * Two arg version: cd pat rep
		 */
		if (strlen(pwd) - strlen(dir) + strlen(rep) >= PATH)
			errorf("substitution too long\n");
		cp = strstr(pwd, dir);
		if (cp == NULL)
			errorf("substitution failed\n");
		strncpy(path, pwd, cp - pwd);		/* first part */
		strcpy(path + (cp - pwd), rep);		/* replacement */
		strcat(path, cp + strlen(dir)); 	/* last part */
		dir = strsave(path, ATEMP);
		prt = 1;
	} else if (dir[0] == '-' && dir[1] == '\0') {
		/*
		 * Change to previous dir: cd -
		 */
		dir = strval(v_oldpwd = global("OLDPWD"));
		prt = 1;
	}
	if (dir[0] == '/' ||
	    (dir[0] == '.' &&
	     (dir[1] == '.' || dir[1] == '/' || dir[1] == '\0'))) {

		/*
		 * dir is an explicitly named path, so no CDPATH search
		 */
		cleanpath(pwd, dir, newd);
		if (chdir(newd) < 0)
			errorf("%s: bad directory\n", newd);
		else if (prt)
			shellf("%s\n", newd);
		flushcom(0);
	} else {
		/*
		 * search CDPATH for dir
		 */
		cdpath = strval(global("CDPATH"));
		while ( !done && cdpath != NULL ) {
			cp = path;
			while (*cdpath && *cdpath != ':')
				*cp++ = *cdpath++;
			if (*cdpath == '\0')
				cdpath = NULL;
			else
				cdpath++;
			if (prt = (cp > path)) {
				*cp++ = '/';
				(void) strcpy( cp, dir );
				cp = path;
			} else
				cp = dir;

			cleanpath(pwd, cp, newd);
			if (chdir(newd) == 0)
				done = 1;
		} while (!done && cdpath != NULL);
		if (!done)
			errorf("%s: bad directory\n", dir);
		if (prt)
			shellf("%s\n", newd);
		flushcom(0);
	}

	/*
	 * Keep track of OLDPWD and PWD
	 */
	oldpwd = pwd;
	pwd = newd;
	if (!v_oldpwd)
		v_oldpwd = global("OLDPWD");
	if (oldpwd && *oldpwd)
		setstr(v_oldpwd, oldpwd);
	else
		unset(v_oldpwd);
	if (*pwd)
		setstr(v_pwd, pwd);
	else
		unset(v_pwd);

	return 0;
}

int
c_print(wp)
	register char **wp;
{
	int nl = 1;
	int expand = 1;
	FILE *f = stdout;

	for (wp++; *wp != NULL && **wp == '-'; wp++) {
		register char *s = *wp + 1;
		if (*s == '\0') {
			wp++;
			break;
		}
		while (*s) switch (*s++) {
		  case 'n':
			nl = 0;
			break;
		  case 'e':
			expand = 1;
			break;
		  case 'r':
			expand = 0;
			break;
		  case 'u':
			if (!digit(*s) || (f = shf[*s++-'0']) == NULL)
				errorf("bad -u argument\n");
			break;
		}
	}

	while (*wp != NULL) {
		register char *s = *wp;
		register int c;
		while ((c = *s++) != '\0')
			if (expand && c == '\\') {
				switch ((c = *s++)) {
				case 'b': c = '\b'; break;
				case 'c': nl = 0; continue; /* AT&T brain damage */
				case 'f': c = '\f'; break;
				case 'n': c = '\n'; break;
				case 'r': c = '\r'; break;
				case 't': c = '\t'; break;
				case 'v': c = 0x0B; break;
				case '0': case '1': case '2': case '3':
				case '4': case '5': case '6': case '7':
					c = c - '0';
					if (*s >= '0' && *s <= '7')
						c = 8*c + *s++ - '0';
					if (*s >= '0' && *s <= '7')
						c = 8*c + *s++ - '0';
					break;
				case '\\': break;
				default:
					putc('\\', f);
				}
				putc(c, f);
			} else
				putc(c, f);
		if (*++wp != NULL)
			putc(' ', f);
	}
	if (nl)
		putc('\n', f);
	return 0;
}

/* todo: handle case where id is both lexical and command */
int
c_whence(wp)
	register char **wp;
{
	register struct tbl *tp;
	char *id;
	int vflag = 0;
	int ret = 0;

	for (wp++; (id = *wp) != NULL && *id == '-'; wp++)
		if (id[1] == 'v')
			vflag = 1;

	while ((id = *wp++) != NULL) {
		tp = tsearch(&lexicals, id, hash(id));
		if (tp == NULL)
			tp = findcom(id, 0);
		if (vflag)
			switch ((tp == NULL) ? CNONE : tp->type) {
			  case CNONE:
				printf("%s is unknown\n", id);
				ret = 1;
				break;
			  case CSHELL:
				printf("%s is a shell builtin\n", id);
				break;
			  case CFUNC:
				printf("%s is a function\n", id);
				fptreef(stdout, "function %s %T\n", id, tp->val.t);
				break;
			  case CEXEC:
				printf("%s is %s\n", id,
				       (tp->flag&ISSET) ? tp->val.s : "unknown");
				if (!(tp->flag&ISSET))
					ret = 1;
				break;
			  case CALIAS:
				printf("%s is the alias '%s'\n", id, tp->val.s);
				break;
			  case CKEYWD:
				printf("%s is a shell keyword\n", id);
				break;
			  default:
				printf("%s is *GOK*\n", id);
				break;
			}
		else
			switch ((tp == NULL) ? CNONE : tp->type) {
			  case CNONE:
				printf("\n");
				ret = 1;
				break;
			  case CSHELL:
				printf("builtin %s\n", id);
				break;
			  case CFUNC:
				printf("%s\n", id);
				break;
			  case CEXEC:
				printf("%s\n", (tp->flag&ISSET) ? tp->val.s : "");
				if (!(tp->flag&ISSET))
					ret = 1;
				break;
			  case CALIAS:
				printf("%s\n", tp->val.s);
				break;
			  case CKEYWD:
				printf("%s\n", id);
				break;
			  default:
				printf("*GOK*\n");
				break;
			}
	}
	return ret;
}

/* typeset, export, and readonly */
int
c_typeset(wp)
	register char **wp;
{
	register char *id;
	struct block *l = e.loc;
	register struct tbl *vp, **p;
	int fset = 0, fclr = 0;
	int thing = 0, func = 0, local = 0;

	switch (**wp) {
	  case 'e':		/* export */
		fset |= EXPORT;
		break;
	  case 'r':		/* readonly */
		fset |= RDONLY;
		break;
	  case 't':		/* typeset */
		local = 1;
		break;
	}

	for (wp++; (id = *wp) != NULL && (*id == '-' || *id == '+'); wp++) {
		int flag = 0;
		thing = *id;
		while (*++id != '\0') switch (*id) {
		  case 'f':
			flag |= FUNCT;
			func = 1;
			break;
		  case 'i':
			flag |= INTEGER;
			break;
		  case 'r':
			flag |= RDONLY;
			break;
		  case 'x':
			flag |= EXPORT;
			break;
		  case 't':
			flag |= TRACE;
			break;
		  default:
			errorf("unknown flag -%c\n", *id);
		}
		if (flag != 0) { /* + or - with options */
			if (thing == '-')
				fset |= flag;
			else
				fclr |= flag;
			thing = 0;
		}
	}

	/* list variables and attributes */
	if (*wp == NULL) {
		for (l = e.loc; l != NULL; l = l->next) {
		    for (p = tsort((func==0) ? &l->vars : &l->funs);
			 (vp = *p++) != NULL; )
			if ((vp->flag&ISSET))
			    if (thing == 0 && fclr == 0 && fset == 0) {
				printf("typeset ");
				if ((vp->flag&INTEGER))
					printf("-i ");
				if ((vp->flag&EXPORT))
					printf("-x ");
				if ((vp->flag&RDONLY))
					printf("-r ");
				if ((vp->flag&TRACE)) 
					printf("-t ");
				printf("%s\n", vp->name);
			    } else
			    if (thing == '+' ||
				(fclr && (vp->flag&fclr) == fclr)) {
				printf("%s\n", vp->name);
			    } else
			    if (thing == '-' ||
				(fset && (vp->flag&fset) == fset)) {
				if (fset&FUNCT)
				    printf("function %s\n", vp->name);
				else
				    printf("%s=%s\n", vp->name, strval(vp));
			    }
		}
		return (0);
	}

	if (local)
		fset |= LOCAL;
	for (; *wp != NULL; wp++)
#if 0
		if (func) {
		} else
#endif
		if (typeset(*wp, fset, fclr) == NULL)
			errorf("%s: not identifier\n", *wp);
	return 0;
}
	
int
c_alias(wp)
	register char **wp;
{
	register struct table *t = &lexicals;
	register struct tbl *ap, **p;
	int rv = 0;

	if (*++wp != NULL && strcmp(*wp, "-d") == 0) {
		t = &homedirs;
		wp++;
	}

	if (*wp == NULL)
		for (p = tsort(t); (ap = *p++) != NULL; )
			if (ap->type == CALIAS && (ap->flag&DEFINED))
				printf("%s='%s'\n", ap->name, ap->val.s);

	for (; *wp != NULL; wp++) {
		register char *id = *wp;
		register char *val = strchr(id, '=');

		if (val == NULL) {
			ap = tsearch(t, id, hash(id));
			if (ap != NULL && ap->type == CALIAS && (ap->flag&DEFINED))
				printf("%s='%s'\n", ap->name, ap->val.s);
			else
				rv = 1;
		} else {
			*val++ = '\0';
			ap = tenter(t, id, hash(id));
			if (ap->type == CKEYWD)
				errorf("cannot alias keyword\n");
			if ((ap->flag&ALLOC)) {
				afree((void*)ap->val.s, APERM);
				ap->flag &= ~(ALLOC|ISSET);
			}
			ap->type = CALIAS;
			ap->val.s = strsave(val, APERM);
			ap->flag |= DEFINED|ALLOC|ISSET;
		}
	}
	return rv;
}

int
c_unalias(wp)
	register char **wp;
{
	register struct table *t = &lexicals;
	register struct tbl *ap;

	if (*++wp != NULL && strcmp(*wp, "-d") == 0) {
		t = &homedirs;
		wp++;
	}

	for (; *wp != NULL; wp++) {
		ap = tsearch(t, *wp, hash(*wp));
		if (ap == NULL || ap->type != CALIAS)
			continue;
		if ((ap->flag&ALLOC))
			afree((void*)ap->val.s, APERM);
		ap->flag &= ~(DEFINED|ISSET|ALLOC);
	}
	return 0;
}

int
c_let(wp)
	char **wp;
{
	int rv = 1;

	for (wp++; *wp; wp++)
		rv = evaluate(*wp) == 0;
	return rv;
}

int
c_jobs(wp)
	char **wp;
{
	j_jobs();
	return 0;
}

#ifdef JOBS
int
c_fgbg(wp)
	register char **wp;
{
	int bg = strcmp(*wp, "bg") == 0;

	if (!flag[FMONITOR])
		errorf("Job control not enabled\n");
	wp++;
	j_resume(j_lookup((*wp == NULL) ? "%" : *wp), bg);
	return 0;
}
#endif

int
c_kill(wp)
	register char **wp;
{
	register char *cp;
	int sig = 15;		/* SIGTERM */
	int rv = 0;
	int n;
	int gotsig = FALSE;

	if (*++wp == NULL)
		errorf("Usage: kill [-l] [-signal] {pid|job} ...\n");
	if (strcmp(*wp, "-l") == 0) {
		register struct trap *p = sigtraps;
		for (sig = 0; sig < SIGNALS; sig++, p++)
			if (p->signal)
				printf("%2d %8s %s\n", p->signal, p->name, p->mess);
		return 0;
	}

	for (; (cp = *wp) != NULL; wp++)
		if (*cp == '-' && gotsig == FALSE && *(wp+1) != NULL) {
			struct trap *p;
			gotsig = FALSE;
			if (digit(*(cp+1))) {
				if ((n = atoi(cp+1)) < SIGNALS) {
					sig = n;
					gotsig = TRUE;
				} else if (kill(n, sig) < 0) {
					shellf("%s: %s\n", cp, strerror(errno));
					rv++;
				}
			} else {
				p = gettrap(cp+1);
				if (p == NULL)
					errorf("bad signal %s\n", cp+1);
				sig = p->signal;
				gotsig = TRUE;
			}
		} else {
			gotsig = FALSE;
			if (digit(*cp) || (*cp == '-' && digit(*(cp+1)))) {
				if (kill(atoi(cp), sig) < 0) {
					shellf("%s: %s\n", cp, strerror(errno));
					rv++;
				}
			} else
			if (*cp == '%')
				j_kill(j_lookup(cp), sig);
			else
				errorf("invalid argument\n");
		}
	return rv;
}

#ifdef EMACS
int
c_bind(wp)
	register char **wp;
{
	int macro = 0;
	register char *cp;

	for (wp++; (cp = *wp) != NULL && *cp == '-'; wp++)
		if (cp[1] == 'm')
			macro = 1;

	if (*wp == NULL)	/* list all */
		x_bind((char*)NULL, (char*)NULL, 0);

	for (; *wp != NULL; wp++) {
		cp = strchr(*wp, '=');
		if (cp != NULL)
			*cp++ = 0;
		x_bind(*wp, cp, macro);
	}

	return 0;
}
#endif

extern	c_fc();
extern	c_getopts();

const struct builtin kshbuiltins [] = {
	{"cd", c_cd},
	{"print", c_print},
	{"getopts", c_getopts},
	{"=typeset", c_typeset},
	{"=export", c_typeset},
	{"=readonly", c_typeset},
	{"whence", c_whence},
	{"=alias", c_alias},
	{"unalias", c_unalias},
	{"hash", c_hash},
	{"let", c_let},
	{"fc", c_fc},
	{"jobs", c_jobs},
	{"kill", c_kill},
#ifdef JOBS
	{"fg", c_fgbg},
	{"bg", c_fgbg},
#endif
#ifdef EMACS
	{"bind", c_bind},
#endif
	{NULL, NULL}
};

